差距和岛屿?或不?

Posted

技术标签:

【中文标题】差距和岛屿?或不?【英文标题】:Gaps and Islands ? Or Not? 【发布时间】:2015-10-29 15:48:51 【问题描述】:

我有下表:

;with data as (
select '508325'  as [customer], 'G61' as [demo_given],
        cast('2015-1-1' as date) as [date_of_demo]
union all select '508325', 'G61', cast('2015-3-1' as date) 
union all select '508325', 'G61',cast('2015-3-15' as date)
union all select '508325', 'G61',cast('2015-3-16' as date)
union all select '508325', 'G61',cast('2015-3-17' as date)
union all select '508325', 'G61',cast('2015-6-1' as date)
union all select '508325', 'G61',cast('2015-8-1' as date)
union all select '508325', 'G61',cast('2015-9-1' as date)
union all select '508325', 'G61',cast('2015-9-1' as date)
union all select '508325', 'G61',cast('2015-12-1' as date)
)

每个客户在 4 个月内只能提供 3 个演示。 第一个时期从给出的第一个演示开始计算,并在 4 个月后结束。

如果该期间的演示数量超过 3,我需要演示的日期 4 以及该 4 个月期间的后期。 (在本例中为2015-3-162015-3-17

下一个时期从前四个月后的第一个演示日期开始。所以我需要计算2015-6-12015-9-30 期间的演示数量,并返回该期间给出的最终“剩余”演示的日期。

我该怎么做呢?

【问题讨论】:

【参考方案1】:

为了便于阅读,我使用了多步 CTE,但您可以根据需要将其组合起来:

tally - 简单的数字表,您可以使用任何您想要的方法(递归 cte、虚拟表、表函数......) min_date_per_customer - 获取每位客户的首次演示日期 date_ranges - 生成将 4 个月添加到 min_date 的范围 final - 将data 加入date_ranges,生成行号 main query - 过滤掉特定时间段内第 4、5、6、...的演示

代码:

WITH tally(N) AS (
  SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
  FROM sys.all_columns a CROSS JOIN sys.all_columns b
), min_date_per_customer AS (
  SELECT customer, MIN(date_of_demo) AS min_date
  FROM #data
  GROUP BY customer
), date_ranges AS (
  SELECT t.N, mdpc.customer
      ,[date_start] = DATEADD(m, 4 * (t.N - 1), min_date)
      ,[date_end]   = DATEADD(m, 4 *t.N, min_date)
  FROM min_date_per_customer mdpc
  CROSS JOIN tally t
  WHERE t.N < 100 -- you can generate as many period as you wish
), final AS (
  SELECT d.customer
       ,d.demo_given
       ,d.date_of_demo
       ,dr.N
       ,rn = ROW_NUMBER() OVER (PARTITION BY dr.customer, dr.N ORDER BY date_of_demo)
  FROM #data d
  JOIN date_ranges dr
    ON d.[date_of_demo] >= dr.date_start
   AND d.[date_of_demo] <= dr.date_end
   AND d.customer = dr.customer
)
SELECT *
FROM final
WHERE rn > 3
ORDER BY customer, date_of_demo;

LiveDemo

输出:

╔══════════╦════════════╦═════════════════════╦═══╦═════╗
║ customer ║ demo_given ║    date_of_demo     ║ N ║ rn  ║
╠══════════╬════════════╬═════════════════════╬═══╬═════╣
║   508325 ║ G61        ║ 2015-03-16 00:00:00 ║ 1 ║   4 ║
║   508325 ║ G61        ║ 2015-03-17 00:00:00 ║ 1 ║   5 ║
║   508325 ║ G61        ║ 2015-09-01 00:00:00 ║ 2 ║   4 ║
╚══════════╩════════════╩═════════════════════╩═══╩═════╝

【讨论】:

以上是关于差距和岛屿?或不?的主要内容,如果未能解决你的问题,请参考以下文章

差距和岛屿 - 如何按 ID 对每组连续行求和

差距和岛屿 - 使用 Postgresql 获取某个日期范围内的失业日期列表

差距和孤岛 - Microsoft Access

使用差距和孤岛知识找到最长时间不改变就业(SQL)

SQL Server:寻找就业差距——孤岛和差距问题

1摄氏度和-1摄氏度体感差距大吗