在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

有没有简单的方法可以做到这一点?

Answers:


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

应Radu的要求,我将解释该查询:

to_char(date,'Mon') as mon, :将“日期”属性转换为月的简短形式的定义格式。

extract(year from date) as yyyy :Postgresql的“提取”功能用于从“日期”属性中提取YYYY年。

sum("Sales") as "Sales" :SUM()函数将所有“ Sales”值相加,并提供区分大小写的别名,并使用双引号保持区分大小写。

group by 1,2:GROUP BY函数必须包含SELECT列表中不属于聚合的所有列(aka,所有列均不在SUM / AVG / MIN / MAX等函数之内)。这告诉查询应该将SUM()应用于每个唯一的列组合,在这种情况下为月和年列。尽管可以最好使用完整的“ to_char(...)”和“ extract(...)”表达式,但“ 1,2”部分是简化的方式,而不是使用列别名。


5
我认为没有解释就给出答案不是一个好主意,特别是对于初学者。您应该已经解释了答案背后的逻辑,至少有一点(尽管对我们其他人来说似乎很简单)。
Radu Gheorghiu

1
@BurakArslan结果是否像OP特定要求的那样?
bma

2
@rogerdpack,输出的date_trunc内容与select date_trunc('month', timestamp '2001-02-16 20:38:40')::date2001-02-01
质询

2
我喜欢date_truncgroup by子句中使用的想法。
pisaruk 2015年

1
可能出现“字段必须在group by子句中”的问题...最好使用OVER(PARTITION BY)。
Zon的

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不是asker期望的: select date_trunc('month', timestamp '2001-02-16 20:38:40')=> 2001-02-01 00:00:00
pisaruk 2015年

4
我同意这种方法更好。我不确定,但我认为它也更有效,因为只有一个分组而不是两个分组。如果您需要重新格式化日期日后可以使用在其他的答案中描述的方法做:to_char(date_trunc('month', txn_date), 'YY-Mon')
帕维尔Sokołowski

1
是的,被接受的答案的投票数量令人难以置信。date_trunc就是为此目的而创建的。没有理由创建两列
allenwlee 2015年

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可能有意义,但是在某些情况下,最好使用格式化的日期字符串,并且如果您使用的是高性能数据仓库,那么其他计算可能不会破坏交易。例如,如果您使用redshift运行快速分析报告,通常需要3秒钟,则6秒钟的查询可能就可以了(尽管如果您正在运行报告,则额外的计算可能会使速度降低一小部分,因为有更大的计算开销)
mgoldwasser'2

1
您仍然可以做到这一点-只需按查询“包装”组即可将其格式化为一个单独的步骤。例如SELECT to_char(d,'YYYY-DD')FROM(SELECT date_trunc('month',d)AS“ d” FROM tbl)AS foo。两全其美!
克里斯·克拉克

1
该解决方案简单而优雅。我喜欢它,就我而言,它足够快。感谢您的回答!
guettli

5

使用postgres中的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

看一下本教程的示例E-> 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'

要添加特定于DST的+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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.