基于单个日期从多行创建日期范围

Posted

技术标签:

【中文标题】基于单个日期从多行创建日期范围【英文标题】:Creating a Date Range from Multiple Rows Based on a Single Date 【发布时间】:2020-09-27 05:40:43 【问题描述】:

我有一个包含以下字段的用户表:User_ID、Email、Used_date。

正如我们所见,用户可以在一段时间内切换到多封电子邮件。从 used_date 字段我想创建日期范围字段(Email_Start_Date 和 Email_End_Date)。它们将存储用户使用该电子邮件的时间段。

用户可以切换回旧电子邮件。在这种情况下,同一封电子邮件将有两个日期范围。

我还想填补上一封电子邮件的最后一天和当前电子邮件的开始日期之间的空白。

例如,如果用户在 2020 年 8 月 28 日至 2020 年 8 月 31 日期间使用某人@gmail.com。

他又于 2020 年 9 月 3 日切换到someone1@gmail.com。

然后在输出中有人@gmail.com 的日期范围为 2020 年 8 月 28 日 - 2020 年 9 月 2 日。

这是一个缝隙和岛屿的例子。但我不知道如何实现。

谢谢大家!

【问题讨论】:

【参考方案1】:

我只建议行号和聚合的区别:

select user_id, email, min(used_date) as email_start_date,
       lead(min(used_date)) over (partition by user_id order by min(used_date)) - interval '1 day' as email_end_date
from (select t.*,
             row_number() over (partition by user_id order by used_date) as seqnum,
             row_number() over (partition by user_id, email order by used_date) as seqnum_2
      from t
     ) t
group by user_id, email, (seqnum - seqnum_2);

其实你也可以用lag()做到这一点,不用聚合:

select user_id, email, min(used_date) as email_start_date,
       lead(used_date) over (partition by user_id order by used_date) - interval '1 day' as email_end_date
from (select t.*,
             lag(email) over (partition by user_id order by used_date) as prev_email
      from t
     ) t
where prev_email is null or prev_email <> email;

第二个很简单。它只保留电子邮件更改的行(或用户数据开始的行)。然后它使用lead() 来获取结束日期。

Here 是一个 dbfiddle。

【讨论】:

非常感谢戈登。您的第一个建议效果很好。对于某些电子邮件序列,使用延迟的第二个失败。我试图弄清楚,但我没有看到任何模式。反正我现在很好。再次感谢您的帮助! @Thinkpad 。 . .嗯。如果有重复的日期,它可能会表现得很奇怪。【参考方案2】:

下次,将您的数据粘贴为文本,这样我们就不必再输入了...

你是这个意思吗?我更喜欢“无限日期”而不是最后一个日期的 NULL 值 - 我更喜欢“会话 id”而不是“岛标识符”,它们通常在点击流和物联网分析中被称为...

WITH
indata(userid,email,used_dt) AS (
          SELECT 1,'someone@gmail.com' , DATE '2020-08-28'
UNION ALL SELECT 1,'someone@gmail.com' , DATE '2020-08-29'
UNION ALL SELECT 1,'someone@gmail.com' , DATE '2020-08-30'
UNION ALL SELECT 1,'someone@gmail.com' , DATE '2020-08-31'
UNION ALL SELECT 1,'someone1@gmail.com', DATE '2020-09-03'
UNION ALL SELECT 1,'someone1@gmail.com', DATE '2020-09-05'
UNION ALL SELECT 1,'someone1@gmail.com', DATE '2020-09-07'
UNION ALL SELECT 1,'someone@gmail.com',  DATE '2020-09-09'
UNION ALL SELECT 2,'bob@gmail.com'     , DATE '2019-07-12'
UNION ALL SELECT 3,'alice@newmail.com' , DATE '2020-08-08'
)
,
with_change_counter AS (
SELECT 
  userid
, email
, used_dt AS used_from_dt
, CASE 
    WHEN LAG(email,1,'') OVER(
      PARTITION BY userid ORDER BY used_dt
    ) <> email 
    THEN 1
    ELSE 0 
  END AS counter
, LEAD(used_dt,1,'9999-12-31') OVER(
    PARTITION BY userid ORDER BY used_dt
  ) AS used_until_dt
  FROM indata
)
,with_sess_id AS (
  SELECT
    userid
  , email
  , used_from_dt
  , used_until_dt
  , SUM(counter) OVER(PARTITION BY userid ORDER BY used_from_dt) AS sessid
  , counter
  FROM with_change_counter
) 
SELECT
  userid
, MAX(email) AS email
, MIN(used_from_dt) AS email_start_date
, MAX(used_until_dt) AS email_end_date
FROM with_sess_id
GROUP BY
  sessid
, userid
ORDER BY
  userid
, sessid
, email
;
-- out  userid |       email        | email_start_date | email_end_date 
-- out --------+--------------------+------------------+----------------
-- out       1 | someone@gmail.com  | 2020-08-28       | 2020-09-03
-- out       1 | someone1@gmail.com | 2020-09-03       | 2020-09-09
-- out       1 | someone@gmail.com  | 2020-09-09       | 9999-12-31
-- out       2 | bob@gmail.com      | 2019-07-12       | 9999-12-31
-- out       3 | alice@newmail.com  | 2020-08-08       | 9999-12-31

【讨论】:

感谢 Marcothesane 的建议。我试图从excel中过去我的数据。但它被转换为图像。下次我使用插入表语句。

以上是关于基于单个日期从多行创建日期范围的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 日期范围生成到多行

建立一个只有 1 个日期的日期范围

日期时间日历:在单个输入字段中选择日期范围

从日期范围创建日期和小时列

按组将数据框日期拆分为单个最小最大日期范围

从日期时间范围创建熊猫数据框[重复]