【问题标题】:CTE - LEFT OUTER JOIN Performance ProblemCTE - 左外连接性能问题
【发布时间】:2021-01-04 21:10:00
【问题描述】:

使用 SQL Server 2017。

SQL FIDDLE:LINK

CREATE TABLE [TABLE_1] 
(
    PLAN_NR decimal(28,6) NULL,
    START_DATE datetime  NULL,
);

CREATE TABLE [TABLE_2] 
(
    PLAN_NR decimal(28,6) NULL,
    PERIOD_NR decimal(28,6) NULL,
);

INSERT INTO TABLE_1 (PLAN_NR, START_DATE)
VALUES (1, '2020-05-01'), (2, '2020-08-05');

INSERT INTO TABLE_2 (PLAN_NR, PERIOD_NR)
VALUES (1, 1), (1, 2), (1, 5), (1, 6), (1, 5), (1, 6), (1, 17),  
       (2, 2), (2, 3), (2, 5), (2, 2), (2, 17), (2, 28);

CREATE VIEW ALL_PERIODS
AS
    WITH rec_cte AS 
    (
        SELECT 
            PLAN_NR, START_DATE,
            1 period_nr, DATEADD(day, 7, START_DATE) next_date
        FROM 
            TABLE_1
        UNION ALL
        SELECT 
            PLAN_NR, next_date,
            period_nr + 1, DATEADD(day, 7, next_date)
        FROM 
            rec_cte       
        WHERE 
            period_nr < 100       
  ),
  cte1 AS 
  (
      SELECT 
          PLAN_NR, period_nr, START_DATE
      FROM 
          rec_cte
      UNION ALL
      SELECT 
          PLAN_NR, period_nr, DATEADD(DAY, 1, EOMONTH(next_date, -1)) 
      FROM 
          rec_cte
      WHERE 
          MONTH(START_DATE) <> MONTH(next_date)
  ),
  cte2 AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY PLAN_NR ORDER BY START_DATE) rn
FROM cte1
)
SELECT PLAN_NR, rn PERIOD_NR, START_DATE 
FROM cte2
WHERE rn <= 100

表_1 列出了计划 (PLAN_NR) 及其开始日期 (START_DATE)。 表 2 列出了计划编号 (PLAN_NR) 和期间 (1 - X)。每个计划编号期间可能会出现多次,但也可能会丢失。 一个时期持续 7 天,除非该时期包括月份的变化。然后将周期分为月末前和月末后。

视图 ALL_PERIODS 根据此系统列出每个计划的 100 个期间。

我的问题是我想在视图中使用的以下选择的性能:

SELECT 
t2.PLAN_NR
, t2.PERIOD_NR
, a_p.START_DATE 
from TABLE_2 as t2 
left outer join ALL_PERIODS a_p on t2.PERIOD_NR = a_p.PERIOD_NR and t2.PLAN_NR = a_p.PLAN_NR

从 TABLE_2 中的大约 4000 个条目开始,选择变得异常缓慢。

连接本身还没有减慢查询速度。只有额外的选择 a_p.START_DATE 一切都会变得异常缓慢。

我将视图读入一个临时表并对其进行了联接,没有遇到性能问题。 (4000 个条目需要 2 秒)。

所以我假设视图中使用的 CTE 是性能缓慢的原因。 不幸的是,我不能在视图中使用临时表,而且我不想将数据写入普通表。

SQL Server 中有没有办法改善 CTE 延迟?

【问题讨论】:

  • 您在表上定义了哪些索引?查询计划是什么样的?
  • 只插入整数时为什么要使用decimal(28,6)
  • decimal(28,6) 只是出于习惯使用。在表中定义了多个索引......但我想它们不适合 sql fiddle 中的最小示例。我是否有意义扩展 fiddel
  • 用计数表替换 rCTE,例如 @ItzikBenGan 的 this one

标签: sql-server common-table-expression


【解决方案1】:

使用计划表和“数字表”之间的交叉连接生成 ALL_PERIODS,而不是递归 CTE,或者作为非递归 CTE。

EG

WITH N As
(
   select top 100 row_number() over (order by (select null)) i
   from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10) ) v1(i),
        (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10) ) v2(i)
),
plan_period AS 
(
    SELECT 
        PLAN_NR, START_DATE,
        N.i period_nr, DATEADD(day, 7*N.i, START_DATE) next_date
    FROM TABLE_1 CROSS JOIN N
),

【讨论】:

  • 谢谢,但据我所知,这样我就不会像原来的 cte 那样在每个月末得到额外的时间间隔。
  • 那我肯定会预先计算并存储每个计划的周期。
【解决方案2】:

如果您能够修改视图,我建议您这样做:

  1. 在数据库中添加一个包含从 0 开始的数字到您认为需要的任何数字的表格,您可以使用以下命令:
create table numbers ( id int)
go
;with cte (
  select 0 num
  union all
  select num + 1
  where num < 2000 -- change this 
)

insert into number
from num from cte
  1. 将视图中的第一个 cte 更改为:
    WITH rec_cte AS 
    (
          
  SELECT
        PLAN_NR
        , DATEADD(DAY, 7* id, START_DATE) START_DATE
        , id  +1                        period_nr
        , DATEADD(DAY, 7*( id+1), START_DATE) next_date

    FROM
        TABLE_1 t
        CROSS apply intenum  i 
         WHERE i.id <100       
  ),...
  1. 还可以考虑使用临时表而不是 cte,这可能会有所帮助

【讨论】:

  • 临时表创造了奇迹,但正如我所提到的,我希望解决方案在我无法使用临时表的视图中工作
  • 然后保留 ctes 并将递归 cte 替换为步骤 1 和 2 中的给定解决方案
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多