TSQL-Begin End内部的子查询

时间:2018-12-10 04:27:38

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

考虑以下查询:

onActivityResult

我想知道begin ;with t1 as ( select top(10) x from tableX ), t2 as ( select * from t1 ), t3 as ( select * from t1 ) -- -------------------------- select * from t2 join t3 on t3.x=t2.x end go 是否被两次调用,因此tableX被两次调用(这意味着t1就像表一样)?

还是仅一次将其行保存在整个查询中的t1中(就像编程lang中的变量一样)?

只是试图弄清楚tsql引擎如何对此进行优化。要知道这一点很重要,因为如果t1有数百万行,并且在整个查询中被多次调用以生成相同的结果,那么应该有一种更好的方法。

3 个答案:

答案 0 :(得分:3)

只需创建表:

CREATE TABLE tableX
(
    x int PRIMARY KEY
);

INSERT INTO tableX
VALUES (1)
      ,(2)

打开执行计划生成并执行查询。您将得到如下内容:

enter image description here

是的,该表被查询两次。如果您使用复杂的公用表表达式并且要处理大量数据,我建议将结果存储在临时表中。

有时候,对于过去运行良好的复杂CTE,我的执行计划非常糟糕。此外,您还可以在临时表上定义索引并进一步提高性能。

答案 1 :(得分:2)

说实话,没有答案……唯一的答案是Race your horses (Eric Lippert)

编写查询的方式不会告诉您引擎如何将其置于执行状态。这取决于很多很多的影响...

您告诉引擎您想获得什么,引擎决定如何来获取。

相同的调用之间甚至可能有所不同,具体取决于统计信息,当前正在运行的查询,现有的缓存结果等。

仅作为提示,请尝试以下操作:

USE master;
GO
CREATE DATABASE testDB;
GO
USE testDB;
GO

-我创建了一个具有1.000.000行的物理测试表

CREATE TABLE testTbl(ID INT IDENTITY PRIMARY KEY, SomeValue VARCHAR(100));

WITH MioRows(Nr) AS (SELECT TOP 1000000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values v1 CROSS JOIN master..spt_values v2 CROSS JOIN master..spt_values v3)
INSERT INTO testTbl(SomeValue)
SELECT CONCAT('Test',Nr)
FROM MioRows;

-现在我们可以开始对此进行测试

GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @dt DATETIME2 = SYSUTCDATETIME();

-您与CTE接触的方法

;with t1 as (select * from testTbl)
     ,t2 as (select * from t1)
     ,t3 as (select * from t1)
select t2.ID AS t2_ID,t2.SomeValue AS t2_SomeValue,t3.ID AS t3_ID,t3.SomeValue AS t3_SomeValue  INTO target1   
from t2
join t3 on t3.ID=t2.ID;

SELECT 'Final CTE',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
GO

CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @dt DATETIME2 = SYSUTCDATETIME();

-将中间结果写入物理表

SELECT * INTO test1 FROM testTbl;
SELECT 'Write into test1',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());

select t2.ID AS t2_ID,t2.SomeValue AS t2_SomeValue,t3.ID AS t3_ID,t3.SomeValue AS t3_SomeValue  INTO target2   
from test1 t2
join test1 t3 on t3.ID=t2.ID

SELECT 'Final physical table',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());

GO
CHECKPOINT;
GO
DBCC DROPCLEANBUFFERS;
GO
DECLARE @dt DATETIME2 = SYSUTCDATETIME();

-与以前相同,但在中间表上具有主键

SELECT * INTO test2 FROM testTbl;
SELECT 'Write into test2',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());
ALTER TABLE test2 ADD PRIMARY KEY (ID);
SELECT 'Add PK',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());

select t2.ID AS t2_ID,t2.SomeValue AS t2_SomeValue,t3.ID AS t3_ID,t3.SomeValue AS t3_SomeValue  INTO target3  
from test2 t2
join test2 t3 on t3.ID=t2.ID

SELECT 'Final physical tabel with PK',DATEDIFF(MILLISECOND,@dt,SYSUTCDATETIME());

-清理(小心真实数据!!!)

GO
USE master;
GO
--DROP DATABASE testDB;
GO

在我的系统上

  • 第一时间674ms,
  • 秒1.205ms(写入test1时为297)和
  • 三分之一1.727毫秒(用于写入test2的时间为285毫秒,用于创建索引的时间为650毫秒。

尽管查询执行了两次,但引擎可以利用缓存的结果。

Conclusio

引擎真的很聪明...不要试图变得更聪明...

如果表将包含很多列,每行包含更多数据,则整个测试可能会返回其他内容...

如果您的CTE(子查询)涉及具有连接,视图,函数等复杂得多的数据,则引擎可能会遇到麻烦,无法找到最佳方法。

如果性能很重要,您可以赛马进行测试。 One hint: I sometimes used a TABLE HINT quite successfully: FORCE ORDER。这将按照查询中指定的顺序执行联接。

答案 2 :(得分:0)

这是测试理论的简单示例: 首先,通过仅调用此事件的临时表。

declare @r1 table (id int, v uniqueidentifier);

insert into @r1 
SELECT * FROM 
(
    select id=1, NewId() as 'v' union   
    select id=2, NewId()
) t
-- -----------

begin 
;with 

    t1 as (
        select * from @r1
    ),

    t2 as (
        select * from t1    
    ),

    t3 as (
        select * from t1    
    )

    -- ----------------

    select * from t2
    union all select * from t3

end
go

enter image description here


另一方面,如果我们将问题放到t1而不是临时表中,它将被调用两次。

t1 as (
        select id=1, NewId() as 'v' union   
        select id=2, NewId()
    )

enter image description here

因此,我的结论是使用临时表,而不对cached结果进行回复。 另外,我在一次仅两次调用“ matter”的大规模查询中实现了这一点,并将其移至临时表后,执行时间几乎缩短了一半!