SQL Server CTE循环;将所有记录一起插入

时间:2018-05-03 14:07:35

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

我遇到这种情况:

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在第1个循环后停止...

2 个答案:

答案 0 :(得分:6)

您可以使用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

答案 1 :(得分: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;