Результати групових запитів за місяцем та роком у postgresql


156

У мене на сервері Postgres є така таблиця баз даних:

id      date          Product Sales
1245    01/04/2013    Toys    1000     
1245    01/04/2013    Toys    2000
1231    01/02/2013    Bicycle 50000
456461  01/01/2014    Bananas 4546

Я хотів би створити запит , який дає SUMз Salesколони і груп результатів по місяцю і року наступним чином :

Apr    2013    3000     Toys
Feb    2013    50000    Bicycle
Jan    2014    4546     Bananas

Чи є простий спосіб це зробити?

Відповіді:


217
select to_char(date,'Mon') as mon,
       extract(year from date) as yyyy,
       sum("Sales") as "Sales"
from yourtable
group by 1,2

На прохання Раду я поясню цей запит:

to_char(date,'Mon') as mon, : перетворює атрибут "дата" у визначений формат короткої форми місяця.

extract(year from date) as yyyy : Функція "екстрагування" Postgresql використовується для вилучення року "РРРР" з атрибута "дата".

sum("Sales") as "Sales" : Функція SUM () додає всі значення "Продажі" та постачає чутливий до регістру псевдонім, при цьому чутливість регістру підтримується за допомогою подвійних лапок.

group by 1,2: Функція GROUP BY повинна містити всі стовпці зі списку SELECT, які не входять до складу сукупності (він же, всі стовпці, які не знаходяться у функціях SUM / AVG / MIN / MAX тощо). Це говорить про запит, що SUM () слід застосовувати для кожної унікальної комбінації стовпців, що в цьому випадку є стовпцями місяць та рік. Частина "1,2" - це скорочення замість використання псевдонімів стовпців, хоча, мабуть, найкраще використовувати повні вирази "to_char (...)" та "extra (...)" для читабельності.


5
Я не думаю, що відповідь без пояснень - це дуже гарна ідея, особливо для початківців. Вам слід було б пояснити логіку, що стоїть у вашій відповіді, хоча б хоч трохи (хоча це може здатися простим і простим для всіх нас).
Раду Георгіу

1
@BurakArslan Чи схожі результати на те, що спеціально просила ОП?
bma

2
@rogerdpack, результат date_truncне є саме тим, що хотів select date_trunc('month', timestamp '2001-02-16 20:38:40')::date2001-02-01
шукати

2
Мені подобається ідея використання date_truncв group byпункті.
пісарук

1
Можливі питання "поле повинно бути в групі за допомогою пункту" ... Краще скористатись НАДЕЖ (ДЕТАЛЬНОСТІ).
Зона

317

Я не можу повірити, що прийнята відповідь має стільки відгуків - це жахливий метод.

Ось правильний спосіб зробити це за допомогою date_trunc :

   SELECT date_trunc('month', txn_date) AS txn_month, sum(amount) as monthly_sum
     FROM yourtable
 GROUP BY txn_month

Це погана практика, але ви можете пробачити, якщо використовуєте

 GROUP BY 1

у дуже простому запиті.

Ви також можете використовувати

 GROUP BY date_trunc('month', txn_date)

якщо ви не хочете вибирати дату.


6
на жаль, результат date_truncне є тим, що очікував запитувач: select date_trunc('month', timestamp '2001-02-16 20:38:40')=> 2001-02-01 00:00:00.
пісарук

4
Я згоден, що цей метод краще. Я не впевнений, але я думаю, що це також більш ефективно, оскільки існує лише одне угруповання замість двох. Якщо вам потрібно переформатувати дату, ви зможете зробити це згодом, використовуючи методи, описані в інших відповідях:to_char(date_trunc('month', txn_date), 'YY-Mon')
Paweł Sokołowski

1
так, кількість голосів за прийняту відповідь викликає сумніви. date_truncбув створений саме для цієї мети. немає причин створювати дві колонки
allenwlee

2
Дуже хороша! Це чудова відповідь, тим більше, що ви також можете замовити. Оголошено!
bobmarksie

1
Ще один приклад, коли найбільш прийнятна відповідь повинна з’явитися до прийнятої відповіді
Brian Risk

33

to_char насправді дозволяє витягнути рік і місяць одним махом!

select to_char(date('2014-05-10'),'Mon-YY') as year_month; --'May-14'
select to_char(date('2014-05-10'),'YYYY-MM') as year_month; --'2014-05'

або у випадку вищевказаного прикладу користувача:

select to_char(date,'YY-Mon') as year_month
       sum("Sales") as "Sales"
from some_table
group by 1;

6
Я б радив не робити цього, якщо у вашій таблиці є пристойний обсяг даних. Це виконує набагато гірше, ніж date_truncметод при виконанні групи. Експериментуючи на БД, який я маю під рукою, на столі з 270k рядками метод date_trunc перевищує швидкість TO_CHAR
Кріс Кларк

@ChrisClark, якщо продуктивність викликає занепокоєння, я погоджуюся, що може мати сенс використовувати date_trunc, але в деяких випадках краще відформатований рядок дати, і якщо ви використовуєте складний сховище даних, додаткові обчислення можуть не бути вимикачем угод . Наприклад, якщо ви запускаєте звіт про швидку аналітику, використовуючи червону зміну, і це зазвичай займає 3 секунди, 6-секундний запит, ймовірно, добре (хоча, якщо ви запускаєте звіти, додаткові обчислення можуть уповільнити роботу на менший відсоток, оскільки є більший обчислювальний наклад)
mgoldwasser

1
Ви все ще можете це зробити - просто виконайте форматування як окремий крок, "загорнувши" групу за запитом. Наприклад, SELECT to_char (d, 'YYYY-DD') FROM (SELECT date_trunc ('місяць', d) AS "d" ВІД tbl) AS foo. Найкраще з обох світів!
Кріс Кларк

1
Це рішення просте і елегантне. Мені це подобається, і в моєму випадку це досить швидко. Дякую за цю відповідь!
guettli

5

Є ще один спосіб досягти результату, використовуючи функцію date_part () у постгресах.

 SELECT date_part('month', txn_date) AS txn_month, date_part('year', txn_date) AS txn_year, sum(amount) as monthly_sum
     FROM yourtable
 GROUP BY date_part('month', txn_date)

Дякую


1

bma відповідь чудова! Я використовував це з ActiveRecords, ось це, якщо комусь це потрібно в Rails:

Model.find_by_sql(
  "SELECT TO_CHAR(created_at, 'Mon') AS month,
   EXTRACT(year from created_at) as year,
   SUM(desired_value) as desired_value
   FROM desired_table
   GROUP BY 1,2
   ORDER BY 1,2"
)

3
або ви можете зробити це, yourscopeorclass.group("extract(year from tablename.colname)")і ви можете зв'язати це разом 3 рази, щоб отримати рік, місяць, день
nruth

1

Погляньте на приклад Е цього підручника -> https://www.postgresqltutorial.com/postgresql-group-by/

Вам потрібно викликати функцію на вашому GROUP BY, а не називати ім'я віртуального атрибута, створеного у select. Я робив те, що рекомендував усі відповіді вище, і отримував column 'year_month' does not existпомилку.

Що для мене працювало:

SELECT 
    date_trunc('month', created_at), 'MM/YYYY' AS month
FROM 
    "orders"  
GROUP BY 
    date_trunc('month', created_at)

0

Postgres має кілька типів часових позначок:

часова марка без часового поясу - (бажано зберігати часові позначки UTC) Ви знаходите її у багатонаціональному сховищі бази даних. Клієнт у цьому випадку подбає про зміщення часового поясу для кожної країни.

позначка часу з часовим поясом - зсув часового поясу вже включений у часову позначку.

У деяких випадках у вашій базі даних не використовується часовий пояс, але все ж потрібно групувати записи відповідно до місцевого часового поясу та літнього часу (наприклад, https://www.timeanddate.com/time/zone/romania/bucharest )

Щоб додати часовий пояс, ви можете використовувати цей приклад і замінити зсув часового поясу на ваш.

"your_date_column" at time zone '+03'

Щоб додати зміщення +1 літнього часу, характерне для літнього часу, потрібно перевірити, чи позначається ваша часова марка на літній літній час. Оскільки ці інтервали змінюються від 1 до 2 днів, я буду використовувати апроксимацію, яка не впливає на записи на кінець місяця, тому в цьому випадку я можу ігнорувати щорічний точний інтервал.

Якщо потрібно побудувати більш точний запит, тоді вам доведеться додати умови для створення більшої кількості справ. Але, приблизно, це буде добре працювати при розподілі даних за місяць стосовно часового поясу та SummerTime, коли ви знайдете часові позначки без часового поясу у вашій базі даних:

SELECT 
    "id", "Product", "Sale",
    date_trunc('month', 
        CASE WHEN 
            Extract(month from t."date") > 03 AND
            Extract(day from t."date") > 26 AND
            Extract(hour from t."date") > 3 AND
            Extract(month from t."date") < 10 AND
            Extract(day from t."date") < 29 AND
            Extract(hour from t."date") < 4
        THEN 
            t."date" at time zone '+03' -- Romania TimeZone offset + DST
        ELSE
            t."date" at time zone '+02' -- Romania TimeZone offset 
        END) as "date"
FROM 
    public."Table" AS t
WHERE 1=1
    AND t."date" >= '01/07/2015 00:00:00'::TIMESTAMP WITHOUT TIME ZONE
    AND t."date" < '01/07/2017 00:00:00'::TIMESTAMP WITHOUT TIME ZONE
GROUP BY date_trunc('month', 
    CASE WHEN 
        Extract(month from t."date") > 03 AND
        Extract(day from t."date") > 26 AND
        Extract(hour from t."date") > 3 AND
        Extract(month from t."date") < 10 AND
        Extract(day from t."date") < 29 AND
        Extract(hour from t."date") < 4
    THEN 
        t."date" at time zone '+03' -- Romania TimeZone offset + DST
    ELSE
        t."date" at time zone '+02' -- Romania TimeZone offset 
    END)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.