递归加入以获得跨度

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

Jul*_*eur 5

这是一个间隙和岛屿问题。如果你谷歌它,很多网站都会谈论它。这只是我在许多其他链接中单击的第一个链接: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)最终用于将它们与MemberPlan

查询甲骨文:

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)