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

Posted

技术标签:

【中文标题】何时使用公用表表达式 (CTE)【英文标题】:When to use Common Table Expression (CTE) 【发布时间】:2011-06-12 01:30:23 【问题描述】:

我已经开始阅读通用表表达式,但我想不出我需要使用它们的用例。它们似乎是多余的,因为派生表也可以这样做。有什么我遗漏或不理解的东西吗?有人可以给我一个简单的例子来说明常规选择、派生或临时表查询的限制,以说明 CTE 的情况吗?任何简单的例子都将受到高度赞赏。

【问题讨论】:

【参考方案1】:

还有一点没有指出,就是速度。我知道这是一个老问题,但我认为这值得直接评论/回答:

它们似乎是多余的,因为派生表也可以这样做

当我第一次使用 CTE 时,我被它的速度惊呆了。这是一个像教科书一样的案例,非常适合 CTE,但是在我曾经使用过的所有情况下,CTE 都有显着的速度提升。我的第一个查询对于派生表很复杂,需要很长时间才能执行。使用 CTE 只需几分之一秒,让我震惊,这甚至是可能的。

【讨论】:

【参考方案2】:

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

MS SQL 不允许您将 ORDER BY 与 UPDATE 一起使用,但在 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)

在这里查看更多信息:How to update and order by using ms sql

【讨论】:

【参考方案3】:

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

自引用的一个例子是递归:Recursive Queries Using CTE

令人兴奋的 Microsoft 定义 Taken from Books Online:

CTE 可用于:

创建递归查询。欲了解更多信息,请参阅Recursive Queries Using Common Table Expressions.

在不需要一般使用视图时替换视图;也就是说,您不必将定义存储在元数据中。

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

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

【讨论】:

是的。您不能自行加入派生表。值得指出的是,CTE 上的自联接仍然会给您留下 2 次单独的调用。 @Martin - 我很惊讶。你能支持那句话吗? @John 谢谢,我发现4guysfromrolla.com/webtech/071906-1.shtml 也很有用 @cyberkiwi - 哪个位?自加入会导致 2 次不同的调用?请参阅此答案中的示例***.com/questions/3362043/… 关于 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.numberwith 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【参考方案4】:

也许将 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;

享受

【讨论】:

【参考方案5】:
 ;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;

试试这个

【讨论】:

【参考方案6】:

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

但是通过使用 CTE(由 Tim Schmelter 在Select the first instance of a record 上回答)

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

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

【讨论】:

【参考方案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 table 表达式,所以我们可以说它就像一个视图,我们在其中定义和使用视图在单个批次中,CTE 不会作为永久对象存储在数据库中。但它的行为就像一个视图。我们可以对 CTE 执行删除和更新语句,这将对 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 可能更容易,在其他情况下也可能相反。性能可能会提高或恶化。等等【参考方案8】:

我认为使用 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

【讨论】:

您的第一个示例不能简化为仅使用嵌套查询而不是 CTE 吗? 这两个例子都可以。 你应该在没有 CTE 的情况下添加第一个,那么很明显为什么后者有用。 HAVING 是另一种进行后期过滤的方法,类似于使用 sub-SELECT【参考方案9】:

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

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

问题是,“简单示例”可能并不真正需要 CTE!

仍然非常方便。

【讨论】:

好的。你能举出一些相对复杂的例子来帮助我理解这个概念吗? “我对它们的唯一抱怨是它们不能被重复使用”——您想要重复使用的 CTE 应该被视为 VIEW 的候选者:) @onedaywhen:明白,但这意味着我并不总是对全局范围感到满意。有时在 proc 的范围内,我想定义一个 CTE 并将其用于选择和更新,或从不同表中选择类似数据。 当我不止一次需要同一个 CTE 时,我将它输入到一个临时表中,然后尽可能多地使用该临时表。

以上是关于何时使用公用表表达式 (CTE)的主要内容,如果未能解决你的问题,请参考以下文章

公用表表达式(CTE)

Hive 公用表表达式 CTE 使用指南

mysql8 公用表表达式CTE的使用

SQL 公用表表达式(CTE)

SQL-CTE公用表达式

SQL Server 公用表表达式(CTE)实现递归