【问题标题】:Group query results by month and year in postgresql在 postgresql 中按月和年对查询结果进行分组
【发布时间】:2013-07-03 18:01:00
【问题描述】:

我在 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

我想创建一个查询,给出Sales 列的SUM,并将结果按月和年分组,如下所示:

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

有简单的方法吗?

【问题讨论】:

    标签: sql postgresql


    【解决方案1】:
    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 的 "extract" 函数用于从 "date" 属性中提取 YYYY 年份。

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

    group by 1,2:GROUP BY 函数必须包含 SELECT 列表中不属于聚合的所有列(也就是不在 SUM/AVG/MIN/MAX 等函数中的所有列)。这告诉查询应该对每个唯一的列组合应用 SUM(),在本例中是月份和年份列。 "1,2" 部分是一种简写形式,而不是使用列别名,但为了便于阅读,最好使用完整的 "to_char(...)" 和 "extract(...)" 表达式。

    【讨论】:

    • 我不认为没有解释就给出答案是一个很好的主意,尤其是对于初学者。你应该已经解释了你的答案背后的逻辑,也许至少一点(尽管它对我们其他人来说似乎简单明了)。
    • @BurakArslan 结果看起来像 OP 特别要求的吗?
    • @rogerdpack,date_trunc 的输出并不是提问者想要的:select date_trunc('month', timestamp '2001-02-16 20:38:40')::date => 2001-02-01
    • 我喜欢在group by 子句中使用date_trunc 的想法。
    • 可能的“字段必须在 group by 子句中”的问题...最好使用 OVER (PARTITION BY)。
    【解决方案2】:

    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;
    

    【讨论】:

    • 如果您的表中有大量数据,我强烈建议您不要这样做。在执行分组依据时,这比date_trunc 方法执行很多。在我方便的数据库上进行实验,在有 270k 行的表上,date_trunc 方法的速度是 TO_CHAR 的两倍以上
    • @ChrisClark 如果性能是一个问题,我同意使用 date_trunc 可能有意义,但在某些情况下,最好使用格式化的日期字符串,如果您使用的是高性能数据仓库额外的计算可能不会破坏交易。例如,如果您正在使用 redshift 运行快速分析报告,并且通常需要 3 秒,那么 6 秒的查询可能是可以的(尽管,如果您正在运行报告,额外的计算可能会减慢速度的百分比,因为计算开销较大)
    • 您仍然可以这样做——只需通过查询“包装”组来将格式化作为一个单独的步骤。例如。 SELECT to_char(d, 'YYYY-DD') FROM (SELECT date_trunc('month', d) AS "d" FROM tbl) AS foo.两全其美!
    • 这个解决方案简单而优雅。我喜欢它,就我而言,它足够快。谢谢你的回答!
    【解决方案3】:

    我不敢相信接受的答案有这么多赞成票——这是一种可怕的方法。

    这是正确的做法,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)
    

    如果您不想选择日期。

    【讨论】:

    • 不幸的是,date_trunc 的输出不是提问者所期望的:select date_trunc('month', timestamp '2001-02-16 20:38:40') => 2001-02-01 00:00:00
    • 我同意这种方法更好。我不确定,但我认为它也更有效,因为只有一个分组而不是两个分组。如果您需要重新格式化日期,您可以在之后使用其他答案中描述的方法进行操作:to_char(date_trunc('month', txn_date), 'YY-Mon')
    • 是的,接受答案的票数令人难以置信。 date_trunc 正是为此目的而创建的。没有理由创建两列
    • 非常好!这是一个很好的答案,特别是因为您也可以订购。点赞!
    • 又一个例子,最受好评的答案应该出现在接受的答案之前
    【解决方案4】:

    Postgres 的时间戳类型很少:

    timestamp without timezone - (最好存储 UTC 时间戳)您可以在多国数据库存储中找到它。在这种情况下,客户将负责每个国家/地区的时区偏移。

    timestamp with timezone - 时区偏移已包含在时间戳中。

    在某些情况下,您的数据库不使用时区,但您仍需要根据本地时区和夏令时对记录进行分组(例如 https://www.timeanddate.com/time/zone/romania/bucharest

    要添加时区,您可以使用此示例并将时区偏移替换为您的。

    "your_date_column" at time zone '+03'
    

    要添加特定于 DST 的 +1 夏季时间偏移,您需要检查您的时间戳是否属于夏季 DST。由于这些间隔随 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)
    

    【讨论】:

      【解决方案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)
      

      谢谢

      【讨论】:

        【解决方案6】:

        看看本教程的示例 6) -> 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)
        

        【讨论】:

        • 您可能的建议:SELECT to_char(date_trunc('month', happened_on), 'MM/YYYY') AS month FROM "orders" GROUP BY date_trunc('month', created_at) 另外,按“虚拟”属性分组是完全有效的。
        【解决方案7】:

        为什么不直接使用date_part 函数。 https://www.postgresql.org/docs/8.0/functions-datetime.html

        SELECT date_part('year', txn_date) AS txn_year,
               date_part('month', txn_date) AS txn_month,
               sum(amount) as monthly_sum
        FROM payment
        GROUP BY txn_year, txn_month
        order by txn_year;
        

        【讨论】:

          猜你喜欢
          • 2020-06-27
          • 1970-01-01
          • 2015-07-07
          • 2023-03-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-01-07
          • 2020-03-22
          相关资源
          最近更新 更多