Группировать результаты запросов по месяцам и годам в postgresql

157

У меня есть следующая таблица базы данных на сервере 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

Есть ли простой способ сделать это?

Frechi
источник

Ответы:

219
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 «extract» используется для извлечения года YYYY из атрибута «date».

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

group by 1,2: Функция GROUP BY должна содержать все столбцы из списка SELECT, которые не являются частью агрегата (то есть все столбцы, не входящие в функции SUM / AVG / MIN / MAX и т. Д.). Это говорит запросу, что SUM () должен применяться для каждой уникальной комбинации столбцов, которые в этом случае являются столбцами месяца и года. Часть "1,2" является сокращением вместо использования псевдонимов столбцов, хотя, вероятно, лучше всего использовать полные выражения "to_char (...)" и "extract (...)" для удобства чтения.

BMA
источник
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
Возможные проблемы "поле должно быть в группе по предложению" ... Лучше использовать OVER (PARTITION BY).
Зон
318

Я не могу поверить, что принятый ответ имеет так много откликов - это ужасный метод.

Вот правильный способ сделать это с помощью 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')
Павел Соколовский
1
да, количество голосов за принятый ответ ошеломляет. date_truncбыл создан именно для этой цели. нет причин создавать две колонки
allenwlee
2
Очень хорошо! Это превосходный ответ, тем более что вы также можете заказать. Upvoted!
bobmarksie
1
Еще один пример, когда наиболее одобренный ответ должен появиться перед принятым ответом
Брайан Риск,
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;
mgoldwasser
источник
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 ('month', d) AS "d" FROM tbl) AS foo. Лучшее из обоих миров!
Крис Кларк,
1
Это простое и элегантное решение. Мне это нравится, и в моем случае это достаточно быстро. Спасибо за этот ответ!
Геттли
5

Есть еще один способ добиться результата, используя функцию date_part () в postgres.

 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)

Спасибо

Nayan
источник
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"
)
mekdigital
источник
3
или вы можете сделать, yourscopeorclass.group("extract(year from tablename.colname)")и вы можете связать это вместе 3 раза, чтобы получить год, месяц, день
nruth
1

Посмотрите на пример E этого урока -> https://www.postgresqltutorial.com/postgresql-group-by/

Вам нужно вызвать функцию в вашем GROUP BY, а не вызывать имя виртуального атрибута, который вы создали при выборе. Я делал то, что рекомендовали все ответы выше, и я получал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)
profimedica
источник