如何引用一个 CTE 两次?

Posted

技术标签:

【中文标题】如何引用一个 CTE 两次?【英文标题】:How to reference one CTE twice? 【发布时间】:2011-01-09 08:22:49 【问题描述】:

我有一个非常胖的公用表表达式,其中包括行号,以便我可以返回分页结果集。我还想在分页结果集之前返回与查询匹配的记录总数。

with recs as (select *, row_number() over (order by id) as rownum from ......)
select * from recs where rownum between @a and @b .... select count(*) from recs

显然我上面的查询是不完整的,但这只是为了说明我的观点。我想要一页结果和匹配的总数。我如何做到这一点,而不必从字面上复制和粘贴整个 20 多行 CTE?

【问题讨论】:

我可能会考虑重命名这个问题,因为接受的答案实际上并没有使用 CTE 两次。 【参考方案1】:

这就是我们在生产环境中处理分页(暂时没有会话管理)的方式。按预期执行。

DECLARE
   @p_PageNumberRequested  int = 1,
      -- Provide -1 to retreive all pages with all the rows.
   @p_RowsPerPage          int = 25

;WITH Numbered AS (
SELECT
   ROW_NUMBER() OVER (ORDER BY YourOrdering) AbsoluteRowNumber
,  COUNT(1) OVER () TotalRows
,  YourColumns
FROM
   YourTable
),
Paged AS (
SELECT
   (AbsoluteRowNumber - 1) / @p_RowsPerPage + 1 PageNumber,
   *
FROM
   Numbered)
SELECT
   ROW_NUMBER() OVER(PARTITION BY PageNumber ORDER BY AbsoluteRowNumber) RowNumberOnPage,
   *
FROM
   Paged
WHERE
      PageNumber = @p_PageNumberRequested
   OR
      @p_PageNumberRequested = -1
ORDER BY 
   AbsoluteRowNumber

【讨论】:

【参考方案2】:

这是最好的:

;WITH recs AS
(SELECT a,b,c,
      row_number() over (
                         ORDER BY id) AS RowNum,
                   row_number() over () AS RecordCount
FROM ......)
SELECT a,b,c,rownum,RecordCount FROM recs
WHERE rownum BETWEEN @a AND @b

【讨论】:

【参考方案3】:

您可以附加一个包含总行数的字段,当然它会在每一行上

select recs.*,totalrows = (select count(0) from recs) 
from recs

【讨论】:

【参考方案4】:

您可以使用逗号创建多个引用上述 CTE 的 CTE。

只是为了说明我的意思:

with recs as (
select 
    *, 
    row_number() over (order by id) as rownum from ......
    ),
counts as (
    select count(*) as totalrows from recs
)
select recs.*,count.totalrows
from recs
cross apply counts 
where rownum between @a and @b .... 

这不是一个好的解决方案。

this article 中描述了在 CTE 中计算总计数而不计算记录的最佳解决方案。

DECLARE @startRow INT; SET @startrow = 50;
WITH cols
AS
(
    SELECT table_name, column_name, 
        ROW_NUMBER() OVER(ORDER BY table_name, column_name) AS seq, 
        ROW_NUMBER() OVER(ORDER BY table_name DESC, column_name desc) AS totrows
    FROM [INFORMATION_SCHEMA].columns
)
SELECT table_name, column_name, totrows + seq -1 as TotRows
FROM cols
WHERE seq BETWEEN @startRow AND @startRow + 49
ORDERBY seq

【讨论】:

是的,我想到了这个,但是当查询没有返回记录时会出现问题。我想我可以用一个 UNION ALL 和一个虚拟行来捏造它...... 查看我从文章中获取的最后一段代码。它有一个升序和降序的行数是什么,它只是将它们添加到结果中以获得总行数。这在我们的生产环境中表现得非常好。 啊,太棒了!该链接有一个非常好的方法来实现这一点。 这个解决方案在大型数据集上可能会很慢...... jw56578 下面列出的 COUNT 选项应该可以正常工作,而且更干净。 这适用于简单的 CTE 查询,但父/子递归 CTE 怎么样?在这里尝试过,但没有用(或者我错过了什么)【参考方案5】:

不要以为你可以。来自MSDN

公用表表达式 (CTE) 可以是 被认为是一个临时的结果集 在执行中定义的 单个 SELECT、INSERT 的范围, 更新、删除或创建视图 声明。

强调“单个 SELECT、INSERT、UPDATE、DELETE 或 CREATE VIEW 语句”。

这可能是您想要使用Temporary Table 的情况。

CREATE TABLE #Recs

  .....

INSERT INTO #Recs
select *, row_number() over (order by id) as rownum from ......

如果你事先不知道表的结构,你可以使用这个表格来创建一个临时表:

select *, row_number() over (order by id) as rownum INTO #Recs from ......

您将能够按照上述方式使用临时表。

【讨论】:

另外,我建议您仅在真正需要时才使用这些“SELECT *”。它们可能会导致性能问题,而且大多数时候并没有必要。 这种用于创建临时表的语法也可能很有用:Select * Into #Recs From... 实际上,我需要对分层数据执行复杂的 SELECT 语句,调用它的方式会因情况而异。 嗯,你是说CTE/Temp表的结构会有所不同?如果是这种情况,那么我会推荐 David Hall 的建议。这将允许您根据您选择的内容定义临时表的结构(类似于您的 CTE)。 我在使用临时表时遇到的问题是我不想将半百万或更多的行塞进一个表中。这样做似乎效率低下。

以上是关于如何引用一个 CTE 两次?的主要内容,如果未能解决你的问题,请参考以下文章

如何将自引用的复杂游标转换为更高效的 SQL 代码? CTE、交叉应用还是其他?

SQL-CTE公用表达式

SQL 中with的用法

mysql8 公用表表达式CTE的使用

递归 CTE 查找父记录

如何两次引用外键表?