Lex*_*ton 5 oracle gaps-and-islands
我对在 SQL 中工作很陌生,并且有一个差距和孤岛问题,我正试图获得一系列连续几个月的会员资格。任何成员都可能有多个成员资格,并且他们的成员资格可能存在差距或重叠。我的想法是按成员订购会员资格,并按月在该组内计划和反向工作,直到找到差距,然后指定会员资格的开始和结束日期。
这是我的数据样本:
Member YYYYMM Plan
1 201601 123
1 201602 123
2 201505 123
2 201506 123
2 201510 123
2 201511 123
2 201512 123
1 201510 456
1 201511 456
1 201512 456
1 201601 456
Run Code Online (Sandbox Code Playgroud)
上述数据将代表两个独立会员在不同时间跨度的相同/不同计划的会员资格。现在,我需要让它看起来更像这样:
Member YYYYMM_Begin YYYYMM_End Plan
1 201601 201602 123
2 201505 201506 123
2 201510 201512 123
1 201510 201601 456
Run Code Online (Sandbox Code Playgroud)
到目前为止,我有一个递归连接,它基于成员和计划进行分区,并按 YYYYMM 降序排列它们,这似乎有效。但是我一直在思考如何确定连续几个月的会员资格的开始和结束日期。
SELECT DISTINCT A.Member, A.Plan, A.YYYYMM, B.Rank
FROM Member_Hist A
JOIN (SELECT
MH.Member,
MH.Plan,
MH.YYYYMM,
RANK() OVER (PARTITION BY Member, Plan ORDER BY YYYYMM DESC) AS Rank
FROM Member_Hist MH) B ON A.Member = B.Member AND A.Plan = B.Plan
ORDER BY Member ASC, Plan ASC, YYYYMM DESC
Run Code Online (Sandbox Code Playgroud)
编辑:我使用的是 Oracle,而不是 MySQL;另外,我只有阅读权限。编辑:抱歉,不使用 MySQL 或 SQL Server
这是一个间隙和岛屿问题。如果你谷歌它,很多网站都会谈论它。这只是我在许多其他链接中单击的第一个链接:Solving Gaps and Islands with Enhanced Window Functions
以下是查询的工作原理:
YYYYMM
是一个 varchar 并且不给出连续的数字。因此它首先将它们更改为正确的日期格式。ROW_NUMBER()
将按组Member
和[Plan]
连续的月份将有连续的rn
,一旦rn
从日期中删除,它们将具有相同的值DATEADD(month, rn, l1.dt)
Member Plan dt rn => DATEADD(month, rn, l1.dt)
2 123 2015-12-01 1 => 2016-01-01
2 123 2015-11-01 2 => 2016-01-01
2 123 2015-10-01 3 => 2016-01-01
2 123 2015-06-01 4 => 2015-10-01
2 123 2015-05-01 5 => 2015-10-01
Run Code Online (Sandbox Code Playgroud)DATEADD(month, rn, l1.dt)
最终用于将它们与Member
和Plan
查询甲骨文:
WITH dates AS(
SELECT DISTINCT Member, "Plan"
, dt = CAST(YYYYMM || '01' as date)
FROM data
), list AS (
SELECT *
, rn = ROW_NUMBER() OVER(PARTITION BY Member, "Plan" ORDER BY dt DESC)
FROM dates d
)
SELECT l1.Member
, MIN(l1.dt) as YYYYMM_Begin
, MAX(l1.dt) as YYYYMM_End
, l1."Plan"
FROM list l1
GROUP BY Member, "Plan", ADD_MONTHS(l1.dt, rn);
Run Code Online (Sandbox Code Playgroud)
Oracle [SQL 小提琴][]
查询 SQL 服务器:
WITH dates AS(
SELECT DISTINCT Member, [Plan]
, dt = CAST(YYYYMM+'01' as date)
FROM @data
), list AS (
SELECT *
, rn = ROW_NUMBER() OVER(PARTITION BY Member, [Plan] ORDER BY dt DESC)
FROM dates d
)
SELECT l1.Member
, MIN(l1.dt) as YYYYMM_Begin
, MAX(l1.dt) as YYYYMM_End
, l1.[Plan]
FROM list l1
GROUP BY Member, [Plan], DATEADD(month, rn, l1.dt);
Run Code Online (Sandbox Code Playgroud)
SQL Server SQL 小提琴
输出:
Member YYYYMM_Begin YYYYMM_End Plan
1 2016-01-01 2016-02-01 123
1 2015-10-01 2016-01-01 456
2 2015-05-01 2015-06-01 123
2 2015-10-01 2015-12-01 123
Run Code Online (Sandbox Code Playgroud)