【问题标题】:SQL Server CTE loop; insert all record togetherSQL Server CTE 循环;将所有记录一起插入
【发布时间】:2018-05-03 14:07:34
【问题描述】:

我有这种情况:

drop table #t1;
drop table #t2

select * 
into #t1
from
    (select 'va1'c1,'vb1'c2,'vc1'c3 union all
     select 'va2'c1,'vb2'c2,'vc2'c3 union all
     select 'va3'c1,'vb3'c2,'vc3'c3 union all
     select 'va1'c1,'vb1'c2,'vc1'c3 union all
     select 'va2'c1,'vb2'c2,'vc2'c3 union all
     select 'va3'c1,'vb3'c2,'vc3'c3 union all
     select 'va1'c1,'vb1'c2,'vc1'c3 union all
     select 'va2'c1,'vb2'c2,'vc2'c3 union all
     select 'va3'c1,'vb3'c2,'vc3'c3 union all
     select 'va1'c1,'vb1'c2,'vc1'c3 union all
     select 'va2'c1,'vb2'c2,'vc2'c3 union all
     select 'va3'c1,'vb3'c2,'vc3'c3 union all
     select 'va4'c1,'vb4'c2,'vc4'c3) t

select *
into #t2
from #t1
where 0 = 1

;with tmp1 as
(
    select 
        t1.*,
        ROW_NUMBER() over (partition by t1.c1 order by (select null)) r
    from 
        #t1 t1
    left join 
        #t2 t2 on t1.c1 = t2.c1
    where 
        t2.c1 is null   
), tmp2 as
(
    select 
        0 n,*
    from 
        tmp1
    union all
    select 
        n+1 n, t1.c1, t1.c2, t1.c3, t1.r
    from 
        tmp2 t1
    join 
        tmp1 t2 on t1.c1 = t2.c1
                and t2.r = t1.r + 1
    where 
        n < 10
)
--insert #t2
select c1, c2, c3  --,r
from tmp2

当我运行它时,它会选择一切正常(103 条记录)。

问题是当我将这段代码插入#t2(13条记录!!!)

我认为 SQL 是一步一步运行的,并且在运行过程中插入记录,而我在 tmp1 中的条件已经结束...

如何解决?

我的目标是检查数据是否存在,然后循环并插入结果...但是 SQL 在第一个循环后停止...

【问题讨论】:

  • 我相信这对你来说很清楚,但对于我们这些不熟悉你所做的事情的人来说,这没有多大意义。
  • 如果您运行提供的查询,您将看到最终选择返回 103 条记录,如果插入未注释,则插入 13 条记录。我认为这就是问题所在。
  • 如果将其与 insert 一起使用,SQL 会忽略递归 CTE。
  • 我建议将此作为错误报告给 Microsoft。与此同时,@lad2025 的解决方法就像做一个 SELECT INTO 第三个临时表然后使用它插入到你的 #t2
  • 你确定这是一个错误吗??以防万一,我该如何举报?

标签: sql sql-server tsql common-table-expression recursive-cte


【解决方案1】:

你可以使用MERGE:

select * into #t1
from(
select 'va1'c1,'vb1'c2,'vc1'c3 union all
select 'va2'c1,'vb2'c2,'vc2'c3 union all
select 'va3'c1,'vb3'c2,'vc3'c3 union all
select 'va1'c1,'vb1'c2,'vc1'c3 union all
select 'va2'c1,'vb2'c2,'vc2'c3 union all
select 'va3'c1,'vb3'c2,'vc3'c3 union all
select 'va1'c1,'vb1'c2,'vc1'c3 union all
select 'va2'c1,'vb2'c2,'vc2'c3 union all
select 'va3'c1,'vb3'c2,'vc3'c3 union all
select 'va1'c1,'vb1'c2,'vc1'c3 union all
select 'va2'c1,'vb2'c2,'vc2'c3 union all
select 'va3'c1,'vb3'c2,'vc3'c3 union all
select 'va4'c1,'vb4'c2,'vc4'c3 
)t;

select * into #t2 from #t1 where 0=1;

;with tmp1 as(
    select t1.*, ROW_NUMBER()over(partition by t1.c1 order by(select null))r
    from #t1 t1
    left join #t2 t2 
      on t1.c1=t2.c1
    where t2.c1 is null 
),tmp2 as (
    select 0 n,*
    from tmp1
    union all
    select n+1 n,t1.c1,t1.c2,t1.c3,t1.r
    from tmp2 t1
    join tmp1 t2
      on t1.c1=t2.c1
     and t2.r=t1.r+1
    where n<10
)
MERGE #t2
USING tmp2
  ON #t2.c1 = tmp2.c1
WHEN NOT MATCHED THEN
  INSERT VALUES (tmp2.c1, tmp2.c2, tmp2.c3);

SELECT @@ROWCOUNT;
-- 103

DBFiddle Demo


编辑

感谢 Bartosz Ratajczyk 审查此案:

事实证明它与惰性/渴望表/索引假脱机有关。至少还有两种方法可以强制 SQL Server 生成不同的执行计划:

a) 通过使用TOP (100) PERCENT

DECLARE @n INT = 100;

;with tmp1 as (
    select t1.*,
           ROW_NUMBER()over(partition by t1.c1 order by(select null))r
    from #t1 t1
    left join #t2 t2 
      on t1.c1=t2.c1
    where t2.c1 is null 
),tmp2 as
(
    select 0 n,*
    from tmp1
    union all
    select n+1 n,t1.c1,t1.c2,t1.c3,t1.r
    from tmp2 t1
    join tmp1 t2
      on t1.c1=t2.c1
     and t2.r=t1.r+1
    where n<10
)
insert #t2
select TOP (@n) PERCENT c1, c2, c3  --,r
from tmp2

SELECT @@ROWCOUNT;

b) 通过使用ORDER BY .. OFFSET 0 ROWS:

;with tmp1 as(
    select t1.*,
           ROW_NUMBER()over(partition by t1.c1 order by(select null))r
    from #t1 t1
    left join #t2 t2 
      on t1.c1=t2.c1
    where t2.c1 is null 
),tmp2 as
(
    select 0 n,*
    from tmp1
    union all
    select n+1 n,t1.c1,t1.c2,t1.c3,t1.r
    from tmp2 t1
    join tmp1 t2
      on t1.c1=t2.c1
     and t2.r=t1.r+1
    where n<10
)
insert #t2
select c1, c2, c3  --,r
from tmp2
ORDER BY 1 OFFSET 0 ROWS;

SELECT @@ROWCOUNT;

db<>fiddle demo2


附录:How does the recursive CTE work? by Bartosz Ratajczyk

【讨论】:

    【解决方案2】:

    您在 MS SQL 服务器的 CTE 实现中遇到了一个特殊问题。并非在所有后端都以这种方式处理。您必须先选择一个临时游标,然后从中插入。即:

    SELECT *
    INTO #t1
    FROM(
        SELECT 'va1' c1, 'vb1' c2, 'vc1' c3
        UNION ALL
        SELECT 'va2' c1, 'vb2' c2, 'vc2' c3
        UNION ALL
        SELECT 'va3' c1, 'vb3' c2, 'vc3' c3
        UNION ALL
        SELECT 'va1' c1, 'vb1' c2, 'vc1' c3
        UNION ALL
        SELECT 'va2' c1, 'vb2' c2, 'vc2' c3
        UNION ALL
        SELECT 'va3' c1, 'vb3' c2, 'vc3' c3
        UNION ALL
        SELECT 'va1' c1, 'vb1' c2, 'vc1' c3
        UNION ALL
        SELECT 'va2' c1, 'vb2' c2, 'vc2' c3
        UNION ALL
        SELECT 'va3' c1, 'vb3' c2, 'vc3' c3
        UNION ALL
        SELECT 'va1' c1, 'vb1' c2, 'vc1' c3
        UNION ALL
        SELECT 'va2' c1, 'vb2' c2, 'vc2' c3
        UNION ALL
        SELECT 'va3' c1, 'vb3' c2, 'vc3' c3
        UNION ALL
        SELECT 'va4' c1, 'vb4' c2, 'vc4' c3
        )t;
    
    SELECT * INTO #t2 FROM #t1 WHERE 0=1;
    
    DECLARE @tmp TABLE(c1 VARCHAR(10), c2 VARCHAR(10), c3 VARCHAR(10));
    
    WITH
        tmp1 AS (
                    SELECT t1.*, ROW_NUMBER() OVER (PARTITION BY t1.c1 ORDER BY(SELECT NULL)) r
                    FROM #t1 t1
                         LEFT JOIN #t2 t2 ON t1.c1=t2.c1
                    WHERE t2.c1 IS NULL
                ),
        tmp2 AS (
                    SELECT 0 n, * FROM tmp1
                    UNION ALL
                    SELECT n+1 n, t1.c1, t1.c2, t1.c3, t1.r
                    FROM tmp2 t1
                         JOIN tmp1 t2 ON t1.c1=t2.c1
                                     AND t2.r=t1.r+1
                    WHERE n<10
                )
    INSERT @tmp(c1, c2, c3)
    SELECT c1, c2, c3 --,r
    FROM tmp2;
    
    INSERT #t2 SELECT * FROM @tmp;
    
    SELECT * FROM #t2;
    
    DROP TABLE #t1;
    DROP TABLE #t2;
    

    【讨论】:

    • 嗨,是的,我知道这个解决方案,但我喜欢直接这样做,因为我有很多真实案例......
    • 这完全没有意义......但它确实有效。你有这方面的任何文件吗?我以前从未见过这种行为。更奇怪的是,您可以正确插入表变量,但不能插入临时表。这里发生了一些可疑的事情。
    • 如果你做SELECT tmp2.c1, tmp2.c2,tmp2.c3 INTO #t3 FROM tmp2,你也会得到预期的结果(103行)。我也很想看到一些关于这方面的文档。
    • @SeanLange 可能它被认为是一个错误并且在文档中找不到。我只是碰巧从经验中知道。
    • 这很奇怪。如果它们都是表变量,它也会失败。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-17
    • 2014-01-06
    • 2016-05-25
    • 1970-01-01
    • 1970-01-01
    • 2019-04-27
    相关资源
    最近更新 更多