如何在postgreql中每两周选择一次并进行分组

时间:2018-01-26 12:46:47

标签: sql postgresql

我正在尝试将表中的行分组两周,但似乎无法解决如何操作 - 尤其是因为{ "_id" : { "companyID" : "someOthervalue", "Name" : "some name" }, "facebookEvents" : [ { "type" : "like", "message" : "sdfdfdf", "link" : "http://www.facebook.com/140", "timeStamp" : 1431014457000.0 }, { "type" : "like", "message" : "Try our Android application", "link" : "http://www.facebook.com/140", "timeStamp" : 1431014457000.0 }, { "type" : "like", "message" : "sdfdfdf", "link" : "http://www.facebook.com/140", "timeStamp" : 1431014457000.0 } ], "TotalLikes" : 3.0 } 函数没有'两周&# 39;关键字参数。

这是我到目前为止所做的:

date_part

我如何重写(或修改)上面的查询以便每两周对数据进行分组?

3 个答案:

答案 0 :(得分:1)

<强>更新 两周是一个两周的时期 - 一个甚至是另一个奇怪的。例如第1周和第2周,第3周和第4周,第5周和第6周。

更近:2是偶数,mod(2,2)=0,1是奇数,mod(1,2)=1

4是偶数,mod(4,2)=0和3是奇数,mod(3,2)=1

6是偶数,mod(6,2)=0和5是奇数,mod(5,2)=1

因此,您可以假设每个连续数字中的每一周除以2提醒为1,并且每个下一周的数字/ 2提醒0

一般的想法是 - 使用一年中的连续周数。为了避免Jan 1成为第一名和Dec31(可能是第53名 - 因此连续两次),我使用IW

  

ISO 8601周编号年份的周数(01-53;第一个星期四)   年度是在第1周)

然后我假设如果一周的数字是奇数,则下一个是偶数,所以我们将所有时间分成两周的部分 - 甚至+奇数。

SQL示例:

o=# with c(d) as (values('2017.12.21'::date),('2017.12.31'),('2018.01.26'),('2018.02.01'))
select d,to_char(d,'IW'),right(to_char(d,'IW'),1)::int,mod(right(to_char(d,'IW'),1)::int, 2) from c;
     d      | to_char | right | mod
------------+---------+-------+-----
 2017-12-21 | 51      |     1 |   1
 2017-12-31 | 52      |     2 |   0
 2018-01-26 | 04      |     4 |   0
 2018-02-01 | 05      |     5 |   1
(4 rows)

mod为0或1 - 此列

https://www.postgresql.org/docs/current/static/functions-math.html https://www.postgresql.org/docs/current/static/functions-formatting.html

当然,如果您想要无间隙的数据,则需要在outer join上添加generate_series ...

答案 1 :(得分:1)

基本理念

我们需要做的是,连续14天间隔并将它们映射到唯一的存储桶,然后按这些存储桶进行分组。这些存储桶可以是任何类型,intchartimstamp,只要我们具有唯一值。

实现这一目标的一个简单方法是划分。除以14天,并将结果截断为日期精度。

例如,我们可以提取自1970-31-01以来的秒数,UNIX纪元,并除以两周内的秒数14 * 24 * 60 * 60 = 14 * 86400 = 1209600。 (我将使用Vao Tsun的示例数据)

WITH c(d) AS (values('2017.12.21'::date),('2017.12.31'),('2018.01.26'),('2018.02.01'))
SELECT (EXTRACT(EPOCH FROM d)::int/86400)/14 fortnight FROM c

自1970-01-01(周四)以来产生了四分之一:

 fortnight
-----------
      1251
      1252
      1254
      1254
(4 rows)

我们得到的整数值代表自1970-01-01以来的最新数量,但我们不必关心这一点。重要的是,它唯一标识了两周。

由于1970-01-01是星期四,所有的炮台将在星期四开始。我们可能希望通过添加以下内容来改变我们两周的起点到一周的另一天(例如星期一):

WITH c(d) AS (values('2017.12.21'::date),('2017.12.31'),('2018.01.26'),('2018.02.01'))
SELECT (EXTRACT(EPOCH FROM d)::int/86400 + 4)/14 fortnight FROM c

到星期四增加四天,我们将在星期一结束。

如果你想要一年四季开始,而不是一些任意的绝对日期,比如1970-01-01,我们可以使用一年中的某一天:

WITH c(d) AS (values('2017.12.21'::date),('2017.12.31'),('2018.01.26'),('2018.02.01'))
SELECT EXTRACT(year FROM d) * 26 + EXTRACT(doy FROM d)::int/14 AS fortnight FROM c;

产生

 fortnight
-----------
     52467
     52468
     52469
     52470
(4 rows)

我们需要将提取的年份乘以26,因为一年中有26.1 ...四个星期。

截断

而不是划分另一种方法是截断。我们将特定两周的每一天映射到该两周的第一个时间戳。

WITH c(d) AS (values('2017.12.21'::date),('2017.12.31'),('2018.01.26'),('2018.02.01'))
SELECT d - make_interval(secs => EXTRACT(EPOCH FROM d)::int % (86400 * 14)) AS fortnight FROM c;

产生

      fortnight
---------------------
 2017-12-14 00:00:00
 2017-12-28 00:00:00
 2018-01-25 00:00:00
 2018-01-25 00:00:00
(4 rows)

这似乎有点复杂,但有一些好处。结果仍然是日期/时间类型,其他代码不需要担心我们使用过Fortnights的事实。

同样,我们可以相对于年初来计算这一点,而不是绝对的四分之一:

WITH c(d) AS (values('2017.12.21'::date),('2017.12.31'),('2018.01.26'),('2018.02.01'))
SELECT d - make_interval(days => EXTRACT(dow FROM d)::int % 14) AS fortnight FROM c;

产生

      fortnight
---------------------
 2017-12-17 00:00:00
 2017-12-31 00:00:00
 2018-01-21 00:00:00
 2018-01-28 00:00:00
(4 rows)

结果属于timestamp类型,您可能希望改为使用date。这可以通过演员来解决:

(d - make_interval(days => EXTRACT(dow FROM d)::int % 14))::date

或从int减去interval而不是date

d - (EXTRACT(dow FROM d)::int % 14)

还有更多的可能性。通过这个方案,我们可以计算相对于月初,某个任意日期等的两周或任何其他间隔。

答案 2 :(得分:0)

我发布另一个答案来解释我的错误以及为什么我的&#34; smart-n-neat&#34; 方式失败......

架构构建和查询位于: https://www.db-fiddle.com/f/j5i2Td8CvxCVXQQYePKzCe/0

第一个(也是正确的)查询:

select distinct w2, avg(c) over (partition by w2)
from d
join generate_series('2016.11.28'::date,'2017.02.23'::date,'2 weeks'::interval) w2
  on gs >= w2 and gs < w2 + '2 weeks'::interval
order by w2;

是一种漫长,简单而正确的方法。有想法是加入两周间隔。它工作,可靠,一切都很好。

现在是第二个查询:

select distinct div(to_char(gs,'IW')::int,2), min(gs) over w, avg(c) over w 
from d
window w as (partition by div(to_char(gs,'IW')::int,2)) 
order by min;

更短,更整洁,更聪明,但有一个巨大的限制,无法使用。这就是为什么: 我的方法将最近两周的间隔分成两部分:2016年的上周和2017年的第一周,从而将结果除以一半。如果将这两周的平均值乘以一半,则两个查询的结果将匹配。唉为边缘年周引入CASE WHEN逻辑使整洁的解决方案变得沉重和开销。因此失去了重点。

TL; DR 整洁轻巧的解决方案仅在一年的间隔内工作,距离年底或一年的开始还有两周,最后如果我们的每两周一次从周一开始。

现在轻量级解决方案背后的想法:round(2/2, 0)=1round(3/2, 0)=1,因此您可以按两周的时间间隔划分年份,并将其用于分组。

此外,我故意不接受这个新年开关,因为2018年1月1日是星期一,所以IWWW相同 - 通常情况并非如此。

最后我的奇数和偶数周的第一个答案根本不可行。它划分的年份不是两周的时间间隔,而是分为两部分 - 偶数和奇数周......我欺骗了自己&#34;接近的事情&#34;想法和提醒的工作,而我应该做相反的整体价值...