一列的历史聚合,直到另一列中每一行的指定时间

Posted

技术标签:

【中文标题】一列的历史聚合,直到另一列中每一行的指定时间【英文标题】:historical aggregation of a column up until a specified time in each row in another column 【发布时间】:2021-10-06 18:43:42 【问题描述】:

我在 Amazon RedShift 中有两个表 login_attemptscheckouts。一个用户可以有多次(不)成功的登录尝试和多次(不)成功的结帐,如下例所示:

login_attempts

login_id | user_id  |       login           |   success
-------------------------------------------------------
1        |  1       |   2021-07-01 14:00:00 |   0
2        |  1       |   2021-07-01 16:00:00 |   1
3        |  2       |   2021-07-02 05:01:01 |   1
4        |  1       |   2021-07-04 03:25:34 |   0
5        |  2       |   2021-07-05 11:20:50 |   0
6        |  2       |   2021-07-07 12:34:56 |   1

checkouts

checkout_id |   checkout_time       | user_id   |   success
------------------------------------------------------------
1           |   2021-07-01 18:00:00 |   1       |   0
2           |   2021-07-02 06:54:32 |   2       |   1
3           |   2021-07-04 13:00:01 |   1       |   1
4           |   2021-07-08 09:05:00 |   2       |   1

鉴于此信息,我如何获得下表,其中包含每次结帐的历史表现截至当时

checkout_id | checkout              | user_id | lastGoodLogin       | lastFailedLogin     |  lastGoodCheckout   |  lastFailedCheckout |
---------------------------------------------------------------------------------------------------------------------------------------
1           | 2021-07-01 18:00:00   | 1       | 2021-07-01 16:00:00 | 2021-07-01 14:00:00 |       NULL          |     NULL
2           | 2021-07-02 06:54:32   | 2       | 2021-07-02 05:01:01 |       NULL          |       NULL          |     NULL
3           | 2021-07-04 13:00:01   | 1       | 2021-07-01 16:00:00 | 2021-07-04 03:25:34 |       NULL          | 2021-07-01 18:00:00
4           | 2021-07-08 09:05:00   | 2       | 2021-07-07 12:34:56 | 2021-07-05 11:20:50 | 2021-07-02 06:54:32 |     NULL

更新:我能够得到lastFailedCheckoutlastGoodCheckout,因为那是在同一张表上进行窗口操作(结帐),但我不明白如何最好地将它与login_attempts 表连接起来以获得last[Good|Failed]Login字段。 (sqlfiddle)

P.S.:我也愿意接受PostgreSQL 的建议。

【问题讨论】:

你熟悉窗口函数吗?特别是框架条款?这应该允许你做你正在寻找的东西。如果您坚持实施解决方案,我可以建议您在 sqlfiddle.com 中设置您的测试用例并展示您已经取得了多远?更容易分享特定领域的专业知识,并显示您遇到的问题将显示有助于您理解的领域。 @BillWeiner 感谢您的提示 :) 我更新了帖子。 【参考方案1】:

好的开始!您的 SQL 中有几件事 - 1) 您应该尽量避免不等式连接,因为这些连接会导致数据爆炸,并且在这种情况下不需要。只需在您的窗口函数中放置一个 CASE 语句,以仅使用您想要的结帐(或登录)类型。 2)您可以使用框架子句在查找以前的结帐时不选择同一行。

一旦你有了这个模式,你就可以用它来找到你正在寻找的其他 2 列数据。第一步是将表 UNION 在一起,而不是 JOIN。这意味着制作更多的列,以便数据可以一起存在,但这很容易。现在,您在同一数据中拥有了用户 ID 和“事情”发生的时间。你只需要再WINDOW 2 次来提取你想要的信息。最后,您需要使用带有 where 子句的外部 select 去除非结帐行。

像这样:

create table login_attempts(
  loginid smallint,
  userid smallint,
  login timestamp,
  success smallint
);

create table checkouts(
  checkoutid smallint,
  userid smallint,
  checkout_time timestamp,
  success smallint
);


insert into login_attempts values
(1, 1, '2021-07-01 14:00:00', 0),
(2, 1, '2021-07-01 16:00:00', 1),
(3, 2, '2021-07-02 05:01:01', 1),
(4, 1, '2021-07-04 03:25:34', 0),
(5, 2, '2021-07-05 11:20:50', 0),
(6, 2, '2021-07-07 12:34:56', 1)
;

insert into checkouts values
(1, 1, '2021-07-01 18:00:00', 0),
(2, 2, '2021-07-02 06:54:32', 1),
(3, 1, '2021-07-04 13:00:01', 1),
(4, 2, '2021-07-08 09:05:00', 1)
;

SQL:

select * 
from (
 select 
  c.checkoutid,
  c.userid,
  c.checkout_time,
  
  max(case success when 0 then checkout_time end) over (
    partition by userid
    order by event_time
    rows between unbounded preceding and 1 preceding
  ) as lastFailedCheckout,  
  
  max(case success when 1 then checkout_time end) over (
    partition by userid
    order by event_time
    rows between unbounded preceding and 1 preceding
  ) as lastGoodCheckout,

  max(case lsuccess when 0 then login end) over (
    partition by userid
    order by event_time
    rows between unbounded preceding and 1 preceding
  ) as lastFailedLogin,  
  
  max(case lsuccess when 1 then login end) over (
    partition by userid
    order by event_time
    rows between unbounded preceding and 1 preceding
  ) as lastGoodLogin
  
 from (
  select checkout_time as event_time, checkoutid, userid, 
    checkout_time, success,
    NULL as login, NULL as lsuccess
  from checkouts
  UNION ALL
  select login as event_time,NULL as checkoutid, userid, 
    NULL as checkout_time, NULL as success,  
    login, success as lsuccess
  from login_attempts
 ) c
) o
where o.checkoutid is not null
order by o.checkoutid

【讨论】:

完整的 sqlfiddle 在这里:sqlfiddle.com/#!15/7ccb1/53 非常感谢简洁的解释和回答:) 没问题。万事如意

以上是关于一列的历史聚合,直到另一列中每一行的指定时间的主要内容,如果未能解决你的问题,请参考以下文章

如果同一行中另一列中的值匹配,如何比较列的两个值

根据另一列中的值从一列中减去值(SQL)

在行值更改后,在另一列中返回一行

根据另一列的位置从一组列中返回值

如何根据 PySpark 数据框的另一列中的值修改列? F.当边缘情况

EXCEL中在某列中查找指定文本,返回行对应另一列的数据用啥函数