何时使用通用表表达式(CTE)


230

我已经开始阅读有关公用表表达式的内容,无法想到需要使用它们的用例。它们似乎是多余的,因为可以对派生表执行相同的操作。我有什么想念的东西吗?有人可以通过常规选择查询,派生查询或临时表查询来举例说明CTE的局限性吗?任何简单的例子将不胜感激。

Answers:


197

一个示例,如果您需要多次引用/联接相同的数据集,则可以通过定义CTE来实现。因此,它可以是一种形式的代码重用。

自我引用的一个示例是递归:使用CTE的递归查询

对于来自联机丛书的令人兴奋的Microsoft定义

CTE可用于:

  • 创建一个递归查询。有关更多信息,请参见使用公用表表达式的递归查询。

  • 当不需要通用视图时,可以用它代替视图;也就是说,您不必将定义存储在元数据中。

  • 启用按标量子选择或不确定的或具有外部访问权限的函数派生的列进行分组的功能。

  • 在同一条语句中多次引用结果表。


7
是的 您不能自行加入派生表。值得一提的是,尽管在CTE上进行自我联接仍然会给您2次单独的调用。
马丁·史密斯,

@马丁-我很惊讶。您可以备份该声明吗?
RichardTheKiwi 2011年


4
@cyberkiwi-哪一位?自我联接将导致两种不同的调用?见这个答案的例子stackoverflow.com/questions/3362043/...
马丁·史密斯

4
有关CTE的有趣事实。我一直想知道为什么多次引用CTE时CTE中的NEWID()会发生变化。select top 100 * into #tmp from master..spt_values order by 1,2,3,4 select A.number, COUNT(*) from #tmp A inner join #tmp B ON A.number = B.number+1 group by A.numbervswith CTE AS (select top 100 * from master..spt_values order by 1,2,3,4) select A.number, COUNT(*) from CTE A inner join CTE B ON A.number = B.number+1 group by A.number
RichardTheKiwi 2011年

50

我用它们来分解复杂的查询,尤其是复杂的联接和子查询。我发现我越来越多地将它们作为“伪视图”使用,以帮助我了解查询的意图。

我对它们的唯一抱怨是它们无法重复使用。例如,我可能有一个存储的proc,其中包含两个可以使用相同CTE的更新语句。但是CTE的“范围”仅是第一个查询。

麻烦的是,“简单示例”可能真的不需要CTE!

仍然非常方便。


好。您能举一些相对复杂的例子来说明这个概念吗?
imak 2011年

28
“我对它们的唯一抱怨是它们不能被重复使用” –您想重复使用的CTE应该被视为是VIEW:) 的候选人
2011年

6
@onedaywhen:理解了,但这意味着我并不总是很满意全球视野。有时,在proc的范围内,我想定义一个CTE并将其用于选择和更新,或从不同表中选择相似数据。
n8wrl 2013年

5
当我多次需要相同的CTE时,我将其馈入临时表中,然后根据需要使用该临时表。
Fandango68

43

我看到使用cte的原因有两个。

在where子句中使用计算值。对我来说,这似乎比派生表还干净。

假设有两个表-Questions和Answers通过Questions.ID = Answers.Question_Id(和测验ID)结合在一起

WITH CTE AS
(
    Select Question_Text,
           (SELECT Count(*) FROM Answers A WHERE A.Question_ID = Q.ID) AS Number_Of_Answers
    FROM Questions Q
)
SELECT * FROM CTE
WHERE Number_Of_Answers > 0

这是我想要获取问题和答案列表的另一个示例。我希望答案与结果中的问题归为一组。

WITH cte AS
(
    SELECT [Quiz_ID] 
      ,[ID] AS Question_Id
      ,null AS Answer_Id
          ,[Question_Text]
          ,null AS Answer
          ,1 AS Is_Question
    FROM [Questions]

    UNION ALL

    SELECT Q.[Quiz_ID]
      ,[Question_ID]
      ,A.[ID] AS  Answer_Id
      ,Q.Question_Text
          ,[Answer]
          ,0 AS Is_Question
        FROM [Answers] A INNER JOIN [Questions] Q ON Q.Quiz_ID = A.Quiz_ID AND Q.Id = A.Question_Id
)
SELECT 
    Quiz_Id,
    Question_Id,
    Is_Question,
    (CASE WHEN Answer IS NULL THEN Question_Text ELSE Answer END) as Name
FROM cte    
GROUP BY Quiz_Id, Question_Id, Answer_id, Question_Text, Answer, Is_Question 
order by Quiz_Id, Question_Id, Is_Question Desc, Name

10
您不能将第一个示例简化为仅使用嵌套查询而不是CTE吗?
2014年

2
两个例子都可以。
Manachi

3
您应该添加第一个不带CTE的对象,然后很明显为什么后者有用。
Ufos

HAVING是做后期滤波器的另一种方法,它类似于使用subSELECT
William Entriken

21

我发现对使用CTE有用的一种情况是,您希望基于一个或多个列获取DISTINCT数据行,但返回表中的所有列。使用标准查询,您可能首先必须将不同的值转储到临时表中,然后尝试将它们连接回原始表以检索其余的列,或者您可能编写一个极其复杂的分区查询以将结果返回到一次运行,但极有可能无法读取,并导致性能问题。

但是通过使用CTE(如Tim Schmelter在“ 选择记录的第一个实例”上的回答)

WITH CTE AS(
    SELECT myTable.*
    , RN = ROW_NUMBER()OVER(PARTITION BY patientID ORDER BY ID)
    FROM myTable 
)
SELECT * FROM CTE
WHERE RN = 1

如您所见,这更容易阅读和维护。与其他查询相比,其性能要好得多。


16

认为CTE替代用于单个查询的视图可能更有意义。但是不需要正式视图的开销,元数据或持久性。当您需要:

  • 创建一个递归查询。
  • 在查询中多次使用CTE的结果集。
  • 通过减少大量相同的子查询来提高查询的清晰度。
  • 启用按CTE结果集中的列分组

这是一个剪切粘贴示例:

WITH [cte_example] AS (
SELECT 1 AS [myNum], 'a num' as [label]
UNION ALL
SELECT [myNum]+1,[label]
FROM [cte_example]
WHERE [myNum] <=  10
)
SELECT * FROM [cte_example]
UNION
SELECT SUM([myNum]), 'sum_all' FROM [cte_example]
UNION
SELECT SUM([myNum]), 'sum_odd' FROM [cte_example] WHERE [myNum] % 2 = 1
UNION
SELECT SUM([myNum]), 'sum_even' FROM [cte_example] WHERE [myNum] % 2 = 0;

请享用


7

今天,我们将学习通用表表达式,它是SQL Server 2005中引入的新功能,并且在以后的版本中也可用。

公用表表达式:-公用表表达式可以定义为临时结果集,换句话说,它可以替代SQL Server中的视图。公用表表达式仅在定义它的语句批中有效,不能在其他会话中使用。

声明CTE(公用表表达式)的语法:-

with [Name of CTE]
as
(
Body of common table expression
)

让我们举个例子:

CREATE TABLE Employee([EID] [int] IDENTITY(10,5) NOT NULL,[Name] [varchar](50) NULL)

insert into Employee(Name) values('Neeraj')
insert into Employee(Name) values('dheeraj')
insert into Employee(Name) values('shayam')
insert into Employee(Name) values('vikas')
insert into Employee(Name) values('raj')

CREATE TABLE DEPT(EID INT,DEPTNAME VARCHAR(100))
insert into dept values(10,'IT')
insert into dept values(15,'Finance')
insert into dept values(20,'Admin')
insert into dept values(25,'HR')
insert into dept values(10,'Payroll')

我创建了两个表employee和Dept,并在每个表中插入了5行。现在,我想加入这些表并创建一个临时结果集以进一步使用它。

With CTE_Example(EID,Name,DeptName)
as
(
select Employee.EID,Name,DeptName from Employee 
inner join DEPT on Employee.EID =DEPT.EID
)
select * from CTE_Example

让我们逐句理解语句的每一行。

要定义CTE,我们编写“ with”子句,然后给表表达式起一个名字,这里我给它起一个名字“ CTE_Example”

然后,我们编写“ As”并将代码括在两个方括号(---)中,我们可以在多个方括号中联接多个表。

在最后一行中,我使用了“ Select * from CTE_Example”,我们在代码的最后一行中引用了Common表表达式,因此我们可以说它就像一个视图,我们在一个视图中定义和使用该视图批处理和CTE不会作为永久对象存储在数据库中。但是它的行为就像一个视图。我们可以在CTE上执行delete和update语句,这将直接影响CTE中使用的引用表。让我们举个例子来了解这个事实。

With CTE_Example(EID,DeptName)
as
(
select EID,DeptName from DEPT 
)
delete from CTE_Example where EID=10 and DeptName ='Payroll'

在上面的语句中,我们从CTE_Example中删除一行,它将从CTE中使用的引用表“ DEPT”中删除数据。


我还是不明白这一点。这与仅以相同条件从DEPT中删除之间有什么区别?它似乎没有使任何事情变得容易。
霍尔格·雅各布斯

如果我错了,请指正我,但是执行计划可能会有所不同,我认为这是Neeraj的观点,有很多方法可以实现相同的目标,但是根据情况,某些方法会比其他方法更具优势。例如,在某些情况下,通过DELETE FROM语句读取CTE可能更容易,而在其他情况下,情况可能相反。性能可能会提高或降低。等
WonderWorker

7

当您要执行“有序更新”时,它非常有用。

MS SQL不允许您将UPDATE与ORDER BY一起使用,但是在CTE的帮助下,您可以这样做:

WITH cte AS
(
    SELECT TOP(5000) message_compressed, message, exception_compressed, exception
    FROM logs
    WHERE Id >= 5519694 
    ORDER BY Id
)
UPDATE  cte
SET     message_compressed = COMPRESS(message), exception_compressed = COMPRESS(exception)

在这里查看更多信息:如何使用ms sql更新和订购


0

尚未指出的一点是速度。我知道这是一个古老的回答问题,但是我认为这应该直接发表评论/回答:

它们似乎是多余的,因为可以对派生表执行相同的操作

当我第一次使用CTE时,它的速度让我非常震惊。就像教科书中的情况一样,非常适合CTE,但是在我使用CTE的所有情况下,速度都有了显着提高。我的第一个查询是复杂的派生表,需要很长时间才能执行。使用CTE花费了几秒钟的时间,这让我震惊,甚至有可能。


-4
 ;with cte as
  (
  Select Department, Max(salary) as MaxSalary
  from test
  group by department
  )  
  select t.* from test t join cte c on c.department=t.department 
  where t.salary=c.MaxSalary;

试试这个

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.