SQL查找最近30天记录计数分组如下

时间:2020-01-26 16:50:42

标签: sql postgresql common-table-expression

我正在尝试在动态窗口-最近30天中每天检索每种状态的客户数量。 查询结果应显示最近30天(即today()-29天)中每个客户状态(A,B,C)每天有多少客户。每个客户一次都可以拥有一种状态,但是在整个客户生命周期内会从一种状态变为另一种状态。该查询的目的是显示客户整个生命周期的“运动”。从生成客户的第一个日期到今天,我已经生成了一系列日期。

我将以下查询汇总在一起,但看来我在做的事情是不正确的,因为结果表明大多数日子里所有状态的计数都是相同的,而每天创建新客户都是不可能的。我们通过另一个简单的查询进行了检查,并确认状态之间的分配不相等。

我试图在下面描述数据和用于获得最佳结果的SQL。

起点(示例表customer_statuses):

   customer_id | status | created_at 
---------------------------------------------------
    abcdefg1234   B      2019-08-22
    abcdefg1234   C      2019-01-17 
    ...   
    abcdefg1234   A      2018-01-18 
    bcdefgh2232   A      2017-09-02
    ghijklm4950   B      2018-06-06

状态-A,B,C 状态没有顺序的顺序,客户在业务关系开始时可以拥有任何状态,并可以在其整个生命周期之间切换状态。

表客户:

    id        |      f_name      |      country      |    created_at |
---------------------------------------------------------------------
abcdefg1234      Michael                 FR              2018-01-18
bcdefgh2232      Sandy                   DE              2017-09-02
....
ghijklm4950      Daniel                  NL              2018-06-06

SQL-当前版本:

WITH customer_list AS (
    SELECT
    DISTINCT a.id,
    a.created_at
    FROM
    customers a
),
dates AS (
     SELECT 
    generate_series(
        MIN(DATE_TRUNC('day', created_at)::DATE),
        MAX(DATE_TRUNC('day', now())::DATE),
        '1d'
        )::date AS day
     FROM customers a
), 
customer_statuses AS (
       SELECT
      customer_id,
      status,
      created_at,
      ROW_NUMBER() OVER 
      (
      PARTITION BY customer_id
      ORDER BY created_at DESC
      ) col
    FROM
        customer_status
)
SELECT
   day,
    (
    SELECT
    COUNT(DISTINCT id) AS accounts
    FROM customers 
    WHERE created_at::date BETWEEN day - 29 AND day
   ),
   status
FROM dates d
    LEFT JOIN customer_list cus
    ON d.day = cus.created_at
    LEFT JOIN customer_statuses cs 
    ON cus.id = cs.customer_id
WHERE
    cs.col = 1
GROUP BY 1,3
ORDER BY 1 DESC,3 ASC

当前查询结果如下:

  day    | count | status
-------------------------
2020-01-24   1230     C
2020-01-24   1230     B
2020-01-24   1230     A
2020-01-23   1200     C
2020-01-23   1200     B
2020-02-23   1200     A
2020-02-22   1150     C
2020-02-22   1150     B
...
2017-01-01    50      C
2017-01-01    50      B
2017-01-01    50      A

我从上面的结果中注意到了两件事-在大多数情况下,结果在给定的一天中对所有状态都显示相同的计数。第二个观察结果是,有时只有两种状态出现-事实并非如此。如果现在在给定日期以特定状态创建的新帐户,则应该结转前一天的计数-对吗?还是这是我创建的查询或我考虑的逻辑问题? 也许我期望结果在逻辑上不会发生?

必填结果:

    day    | count | status
-------------------------
2020-01-24   1230     C
2020-01-24   1000     B
2020-01-24   2500     A
2020-01-23   1200     C
2020-01-23   1050     B
2020-02-23   2450     A
2020-02-22   1160     C
2020-02-22   1020     B
2020-02-22   2400     A
...
2017-01-01    10      C
2017-01-01    4       B
2017-01-01   50       A

谢谢!

2 个答案:

答案 0 :(得分:1)

您的查询似乎过于复杂。这是另一种方法:

  • 使用lead()获取每个客户状态记录的状态结束时间。
  • 使用generate_series()来生成日期。

其余只是过滤和聚合:

select gs.dte, cs.status, count(*)
from (select cs.*,
             lead(cs.created_at, 1, now()::date) over (partition by cs.customer_id order by cs.created_at) as next_ca
      from customer_statuses cs
     ) cs cross join lateral
     generate_series(cs.created_at, cs.next_ca - interval '1 day', interval '1 day') gs(dte)
where gs.dte < now()::date - interval '30 day'

答案 1 :(得分:0)

我对查询进行了一些更改,因为我注意到在客户更改状态的那天我得到了重复的记录-一条具有旧状态的记录和一条新日期的记录。

例如@Gordon查询的输出:

b = Foo2()
b.var = 0xff
print(hex(b.var))
b.First.var = 0
b.Second.var = 1
print(b.First.var)
print(b.Second.var)

我对查询进行了调整,请参见下文,而结果正确地描述了状态之间的变化(更改当天没有重复的记录),但是,这些记录一直持续到 dte | status --------------------------- 2020-02-12 B ... ... 01.02.2020 A 01.02.2020 B 31.01.2020 A 30.01.2020 A 且不包含{{ 1}}(与今天一样)。我不确定为什么并且找不到正确的逻辑来确保所有这些都是我想要的。 日期正确地描述了每个客户的状态,返回的状态包括今天。

调整后的查询:

now()::date - interval '1day'

两项调整: 调整似乎也违反直觉,因为我似乎将间隔日从查询的一部分移开而只是将其添加到另一部分(在我看来,它会产生相同的结果)

a-增加了超前功能(第3行)减少的1天

now()::date

b-从next_ca变量(第6行)中删除了减少1天的时间

select gs.dte, cs.status, count(*)
from (select cs.*,
             lead(cs.created_at, 1, now()::date) over (partition by cs.customer_id order by cs.created_at) - INTERVAL '1day' as next_ca
      from customer_statuses cs
     ) cs cross join lateral
     generate_series(cs.created_at, cs.next_ca, interval '1 day') gs(dte)
where gs.dte < now()::date - interval '30 day'

带有调整后的查询的输出示例:

lead(cs.created_at, 1, now()::date) over (partition by cs.customer_id order by cs.created_at) - INTERVAL '1 day' as next_ca

感谢您的帮助!