【发布时间】:2021-10-14 03:18:26
【问题描述】:
我在将一些递归 CTE 代码从 PostgreSQL 调整到 SQL Server 时遇到问题,来自《用数据对抗流失》一书
这是工作的 PostgreSQL 代码:
with recursive
active_period_params as (
select interval '30 days' as allowed_gap,
'2021-09-30'::date as calc_date
),
active as (
-- anchor
select distinct account_id, min(start_date) as start_date
from subscription inner join active_period_params
on start_date <= calc_date
and (end_date > calc_date or end_date is null)
group by account_id
UNION
-- recursive
select s.account_id, s.start_date
from subscription s
cross join active_period_params
inner join active e on s.account_id=e.account_id
and s.start_date < e.start_date
and s.end_date >= (e.start_date-allowed_gap)::date
)
select account_id, min(start_date) as start_date
from active
group by account_id
这是我转换为 SQL Server 的尝试。它陷入了一个循环。我认为这个问题与 SQL Server 所需的 UNION ALL 有关。
with
active_period_params as (
select 30 as allowed_gap,
cast('2021-09-30' as date) as calc_date
),
active as (
-- anchor
select distinct account_id, min(start_date) as start_date
from subscription inner join active_period_params
on start_date <= calc_date
and (end_date > calc_date or end_date is null)
group by account_id
UNION ALL
-- recursive
select s.account_id, s.start_date
from subscription s
cross join active_period_params
inner join active e on s.account_id=e.account_id
and s.start_date < e.start_date
and s.end_date >= dateadd(day, -allowed_gap, e.start_date)
)
select account_id, min(start_date) as start_date
from active
group by account_id
订阅表是属于客户的订阅列表。客户可以拥有多个日期重叠或日期之间有间隔的订阅。 null end_date 表示订阅当前处于活动状态并且没有定义的 end_date。下面是单个客户 (account_id = 15) 的示例数据:
subscription
---------------------------------------------------
| id | account_id | start_date | end_date |
---------------------------------------------------
| 6 | 15 | 01/06/2021 | null |
| 5 | 15 | 01/01/2021 | null |
| 4 | 15 | 01/06/2020 | 01/02/2021 |
| 3 | 15 | 01/04/2020 | 15/05/2020 |
| 2 | 15 | 01/03/2020 | 15/05/2020 |
| 1 | 15 | 01/06/2019 | 01/01/2020 |
预期的查询结果(由 PostgreSQL 代码产生):
------------------------------
| account_id | start_date |
------------------------------
| 15 | 01/03/2020 |
问题: 上面的 SQL Server 代码卡在一个循环中,没有产生结果。
PostgreSQL 代码说明:
- 锚块查找在 calc_date (30/09/2021) (id 5 & 6) 处于活动状态的 subs,并返回 min start_date (01/01/2021)
- 然后,递归块查找在 allowed_gap 中存在的任何较早的 subs,这是在 1) 中找到的 min_start 日期前 30 天。 id 4 符合此条件,因此新的最小 start_date 为 01/06/2020
- 递归重复并在 allowed_gap (01/06/2020 - 30 天) 内找到两个子项。在这些潜艇(id 2 和 3)中,新的最小 start_date 是 01/03/2020
- 递归未能在 allowed_gap 内找到更早的子(01/03/2020 - 30 天)
- 查询返回 account_id 15 的开始日期 01/03/2020
任何帮助表示赞赏!
【问题讨论】:
-
请提供minimal reproducible example,即样本数据、您想要的结果和您的尝试。
-
我可以看到至少有一个区别
dateadd(day, allowed_gap, e.start_date)应该是dateadd(day, -allowed_gap, e.start_date)。两个系统之间的一大区别是 Postgres 能够区分 rCTE,而 SQL Server 不能,因为它逐行处理它。让我感到震惊的是,这种递归可能使用计数表之类的东西做得更好,但如果没有minimal reproducible example,就很难判断。 -
@Charlieface 感谢您注意到错误 - 已修复
-
注意 - 将 DISTINCT 与 GROUP BY 子句一起使用几乎总是毫无意义的。 GROUP BY 将保证每组分组列都有一行。添加 distinct 没有任何用处。
标签: sql sql-server tsql