我正在尝试解析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,希望它有用。
答案 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获得最佳效果。