如何显示一年中每周发生的行?

时间:2016-03-31 00:33:16

标签: sql postgresql timestamp aggregate

我正在尝试解析PostgreSQL 9.5 中的日志表。让我们想象一下,我正在记录从属于我公司的所有手机发送的短信。对于每条记录,我都有一个时间戳和电话号码 我希望显示按周发送的短信数量,但仅适用于一年中每周发送短信的电话。

我的表格如下:

╔════════════╦══════════╗
║ event_date ║ phone_id ║
╠════════════╬══════════╣
║ 2016-01-05 ║    1     ║
║ 2016-01-06 ║    2     ║
║ 2016-01-13 ║    1     ║
║ 2016-01-14 ║    1     ║
║ 2016-01-14 ║    3     ║
║ 2016-01-20 ║    1     ║
║ 2016-01-21 ║    1     ║
║ 2016-01-22 ║    2     ║
╚════════════╩══════════╝

我希望以下显示

╔══════════════╦══════════╦══════════════╗
║ week_of_year ║ phone_id ║ count_events ║
╠══════════════╬══════════╬══════════════╣
║  2016-01-04  ║    1     ║       1      ║
║  2016-01-11  ║    1     ║       2      ║
║  2016-01-18  ║    1     ║       2      ║
╚══════════════╩══════════╩══════════════╝

仅显示phone_id 1,因为这是唯一包含一年中每周活动的ID。

现在,我可以通过week_of_year和phone_ID查询分组。我有以下结果:

╔══════════════╦══════════╦══════════════╗
║ week_of_year ║ phone_id ║ count_events ║
╠══════════════╬══════════╬══════════════╣
║  2016-01-04  ║    1     ║       1      ║
║  2016-01-04  ║    2     ║       1      ║
║  2016-01-11  ║    1     ║       2      ║
║  2016-01-11  ║    3     ║       1      ║
║  2016-01-18  ║    1     ║       2      ║
║  2016-01-18  ║    2     ║       1      ║
╚══════════════╩══════════╩══════════════╝

如何过滤以便仅为每年的每周保持phone_ids?我尝试了各种子查询,但我必须承认我被卡住了。 : - )

关于week_of_year的定义:因为我想每周合并数据,我在我的选择中使用:date_trunc('week', event_date)::date as interval。然后我按interval分组,以获得每周phone_id的短信数量。

关于日期范围,我只想在2016年开始,我在查询中使用where条件忽略之前的所有内容:WHERE event_date > '2016-01-01'

我看到了创建一个SQL Fiddle的请求,但是我有这样做的问题,如果我没有足够的幸运来解决这个问题,我会做的。

创建了quick SQL Fiddle,希望它有用。

4 个答案:

答案 0 :(得分:0)

以下假设您的表代表一整年。你没有指定。

要查找每周发送短信的所有手机,您可以执行类似

的操作
select phone_id, count(distinct extract(week from event_date)) as cnt
from table
having cnt >= 51

注意,我使用51,但一年中一周的概念有点模糊,他们 实际上有52或53(partila)周。但是51应该没问题。

无论如何,你只需要做

select phone_id, date_trunc('week', event_date), count(*) 
from table
where phone_id in (.. query above ..)
group by 1, 2

如果您在SQLFiddle

中提供了示例数据,那就太棒了

答案 1 :(得分:0)

HAVING子句允许您过滤掉日历年中每周没有活动的任何手机。在下面的回答中,我使用了一个公用表表达式(CTE),因为我实际上执行了两个类似性质的查询。 X上的第一个查询会再现您已有的结果,而子查询会查找一年中每周(即52周)内有活动的所有电话。我假设您要查询的年份是2015年,但您可以随意更改此内容。

WITH X AS (
    SELECT DATE_TRUNC('week', event_date)::date AS week_of_year,
        phone_id, COUNT(*) AS count_events
    FROM messages
    GROUP BY week_of_year, phone_id
    WHERE EXTRACT(YEAR FROM event_date)::text = '2016'
)
SELECT x1.week_of_year, x1.phone_id, x1.count_events
FROM X x1 INNER JOIN
(
    SELECT phone_id, COUNT(*)
    FROM X
    GROUP BY phone_id
    HAVING COUNT(*) =
    (
        SELECT COUNT(DISTINCT DATE_TRUNC('week', event_date)::date)
        FROM messages
        WHERE EXTRACT(YEAR FROM event_date)::text = '2016'
    )
) x2
    ON x1.phone_id = x2.phone_id

答案 2 :(得分:0)

你的年度概念似乎非常模糊。相反,我假设你的意思是你的数据范围超过一段时间。

with w as (
      select date_trunc('week', event_date) as wk, phone_id, count(*) as cnt
      from messages
      group by 1, 2
     ),
     ww as (
      select w.*,
             min(wk) over () as min_wk,
             max(wk) over () as max_wk,
             count(*) over (partition by phone_id) as numweeks
      from w
     )
select ww.wk, ww.phone_id, ww.cnt
from ww
where (max_wk - min_wk) / 7 = cnt - 1;

第一个CTE只是按周和手机ID汇总数据。第二个CTE计算数据中的第一周和最后一周(这些可以用常数替换),以及给定手机的周数。

最后,where条款确保周数跨越一段时间。

答案 3 :(得分:0)

  

我想显示按周发送的短信数量,但仅适用于一年中每周发送短信的手机。

棘手的部分是“年”和“周”的确切定义。默认情况下,我会假设 ISO定义

一年中的几周定义如下(quoting the Postgres manual):

  

每个ISO 8601周编号年从本周一开始   包含1月4日

从逻辑上讲,12月28日属于每年的 last 周。将当前时间假设为当前年份的上边界是有意义的 - 否则您将必须明确定义它。

无论哪种方式,第一周的第一天仍然可以是前一年的一部分,反之亦然。例如,2015年的ISO周数包括2014年和2016年的部分。周数是52 最多的时间,但并非总是如此 - 这使至少一个无效到目前为止提供的查询。考虑一下这个演示:

SELECT extract(year FROM jan1)::text              AS year
     , date_trunc('week', jan4)::date             AS ts_min  -- incl. lower bound
     , date_trunc('week', dec28_or_now)::date + 7 AS ts_max  -- excl. upper bound
     , extract(week FROM  dec28_or_now)           AS weeks
FROM  (SELECT jan1, jan1 + interval '3 days' AS jan4
            , least(jan1 + interval '1 year - 4 days', now()) AS dec28_or_now
       FROM   generate_series(date '2010-01-01'
                            , date '2016-01-01'
                            , interval '1 year') jan1
      ) sub;

结果:

 year |   ts_min   |   ts_max   | weeks
------+------------+------------+-------
 2010 | 2010-01-04 | 2011-01-03 |    52
 2011 | 2011-01-03 | 2012-01-02 |    52
 2012 | 2012-01-02 | 2012-12-31 |    52
 2013 | 2012-12-31 | 2013-12-30 |    52
 2014 | 2013-12-30 | 2014-12-29 |    52
 2015 | 2014-12-29 | 2016-01-04 |    53
 2016 | 2016-01-04 | 2016-04-04 |    13  -- weeks so far

您没有定义event_date确切数据类型。它可以有所作为。请注意,“年”和“周”的确切定义取决于您所在的时区。它可能仍然是纽约12月31日星期日,而它已经是1月1日星期一在柏林。

Postgres在提取年份或周数或使用date_trunc()时假设您当前会话的时区设置。如果您的时区完全不同,请务必使用timestamp with time zone的数据类型 event_date 来排除错误的另外一个入口点。详细说明:

我的查询无论哪种方式都可以。我在查询的CTE中以类似的方式封装了上面的逻辑,所以你只提供感兴趣的年份一次

WITH year_data AS (
   SELECT date_trunc('week', jan4)                             AS ts_min  -- incl. bound
        , date_trunc('week', dec28_or_now) + interval '1 week' AS ts_max  -- excl. bound
        , extract(week FROM  dec28_or_now)                     AS weeks
   FROM  (SELECT jan1 + interval '3 days' AS jan4
               , least(jan1 + interval '1 year - 4 days', now()) AS dec28_or_now
          FROM  (SELECT date '2016-01-01' AS jan1) t  -- provide Jan 1 of the year here!
         ) sub
   )
SELECT week_start, phone_id, count_events
FROM  (
   SELECT t.phone_id, date_trunc('week', t.event_date) AS week_start
        , count(*) AS count_events
        , count(*) OVER (PARTITION BY t.phone_id) AS weeks
   FROM   tbl t
   JOIN   year_data y ON t.event_date >= y.ts_min
                     AND t.event_date <  y.ts_max
   GROUP  BY 1, 2
   ) sub
WHERE  sub.weeks = (SELECT weeks FROM year_data);

有关在同一查询级别使用短信获取短信计数和周数的技术:

同样重要的是:此查询使用sargable谓词,并且可以使用event_date上的索引(与目前为止提供的所有其他查询相对)。

理想情况下,您在(event_date, phone_id)上有一个索引,以便index-only scans获得最佳效果。