从有序 CTE、TOP 与 Range 中检索 X 行

Posted

技术标签:

【中文标题】从有序 CTE、TOP 与 Range 中检索 X 行【英文标题】:Retrieving X rows from an ordered CTE, TOP vs Range 【发布时间】:2016-11-08 14:21:22 【问题描述】:

目标:

想知道在尝试从已排序的 CTE 检索有限数量的行时哪个更快/更好。

示例:

假设我有一个看起来像这样的 CTE(故意简化),我只想要前 5 行:

WITH    cte
          AS (
               SELECT  Id =  RANK() OVER (ORDER BY t.ActionID asc)
                      , t.Name
               FROM     tblSample AS t -- tblSample is indexed on Id              
             )

哪个更快:

SELECT TOP 5 * FROM cte

SELECT * FROM cte WHERE Id BETWEEN 1 AND 5 ?

注意事项:

    我不是数据库程序员,所以对我来说 TOP 解决方案似乎更好 一旦 SS 找到第 5 行,它就会停止执行并“返回”(100% 假设)而在另一种方法中,我觉得它会不必要 处理整个 cte。 我的问题是针对 CTE,如果是表格,这个问题的答案是否相同?

【问题讨论】:

你真的试过这个吗?无法订购 CTE... @HoneyBadger 我简化了问题的 CTE,在我的真实世界案例中,CTE 是使用 RANK() OVER ( ORDER BY...) 查询创建的,该查询插入按 Id 排序的行 CTE 不插入行。在以下查询中调用 CTE 时会执行它。因此,当您执行 select * from cte ... 时,就是执行 cte 时(整个 cte)。过滤直到稍后才会发生。我怀疑你会发现这两种方法之间的性能差异很大。 我认为如果将 cte 定义为 TOP 5,order by 与您在 RANK() 中使用的相同,您可能会获得更好的性能。 top 5same 查询中没有 order by 实际上是“选择 5 个任意行”。就任何外部/消费查询而言,应用于子查询、CTE 或窗口函数内的任何 ORDER BY 对行的顺序没有影响 - 它仅用于定义 查询的其他一些元素(这里,Id 的值要assign)。它保证 CTE 以任何有意义的方式“排序”。 【参考方案1】:

需要注意的最重要的一点是,两个查询不会总是产生相同的结果集。考虑以下数据:

CREATE TABLE #tblSample (ActionId int not null, name varchar(10) not null);
INSERT #tblSample VALUES (1,'aaa'),(2,'bbb'),(3,'ccc');

这两者都会产生相同的结果:

WITH CTE AS 
(
  SELECT id = RANK() OVER (ORDER BY t.ActionID asc), t.name
  FROM #tblSample t
)
SELECT TOP(2) * FROM CTE;

WITH CTE AS 
(
  SELECT id = RANK() OVER (ORDER BY t.ActionID asc), t.name
  FROM #tblSample t
)
SELECT * FROM CTE WHERE id BETWEEN 1 AND 2;

现在让我们做这个更新:

UPDATE #tblSample SET ActionId = 1;

在此更新后,第一个查询仍然返回两行,第二个查询返回 3。还要记住,如果 TOP 查询中没有 ORDER BY,则无法保证结果,因为 SQL 中没有默认顺序。

除此之外 - 哪个性能更好?这取决于。这取决于您的索引、统计信息、行数以及 SQL 引擎附带的执行计划。

【讨论】:

【参考方案2】:

Top 5 根据表上定义的索引选择任意 5 行,而介于 1 和 5 之间的 Id 尝试根据 Id 列获取数据,无论是通过索引查找还是扫描取决于所选属性。两者都是两个不同的查询。如果您在 Id 上没有任何索引,“Id between”查询可能会很慢,

让我试着用一个例子来解释......

认为这是您的数据..

create index nci_name on yourcte(id) include(name)
--drop index nci_name on yourcte

;with cte as (
select * from yourcte )
select top 5 * from cte

;with cte as (
select * from yourcte )
select * from cte where id between 1 and 5

首先,我在包含名称的 id 上创建索引,现在如果您看到第二个查询执行索引搜索,第一个执行索引扫描并选择前 5 个,所以在这种情况下,第二种方法更好

查看执行计划:

现在我正在删除索引 执行 --drop index nci_name on yourtable

现在它对这两种方法都进行表扫描

如果您在两次表扫描中都注意到,在第一次扫描中它只读取 5 行,而在第二种方法中它读取 10 行并应用谓词

查看第一个计划的执行计划属性

对于第二种方法,它读取 10 行

现在第一种方法更好..

在您的情况下,此索引需要位于确定 id 的 ActionId 上。

因此,性能取决于您对基表的索引方式。

【讨论】:

@KannanKandaswamy 一个 CTE 不能有索引。 CTE 不能有索引但底层表有索引仪式? @KannanKandaswamy,根据 HoneyBadger 的评论,我明白你的意思,“在以下查询中调用 CTE 时会执行它。所以当你选择 * from cte ... 时被执行(整个 cte)”.. 我不知道是这样的,我认为它是首先执行的,就像插入到 TEMP 表中一样。 用示例更新答案.. 但我无法理解您的评论 @KannanKandasamy 很好的例子来解释这个概念。但是,在这种情况下,过滤属性是函数 (rank) 的结果,因此不能被索引(至少不能在 cte 中)。【参考方案3】:

为了获得您在 cte 中计算的 RANK(),它必须按 t.ActionID 对所有数据进行排序。排序是一种阻塞操作:必须先处理整个输入,然后才能输出单行。

所以在这种情况下,无论您选择任意五行,还是选择排在最前面的五行,都可能无关紧要。

【讨论】:

如果t.ActionID被索引,则不需要进行排序。

以上是关于从有序 CTE、TOP 与 Range 中检索 X 行的主要内容,如果未能解决你的问题,请参考以下文章

python列表索引out of range是啥意思?

LeetCode 34 Search for a Range (有序数组中查找给定数字的起止下标)

从 Firebase 实时数据库 Kotlin 中检索有序数据

PostgREST 在子查询或 CTE 中使用限制和偏移量

设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈

CTE递归获取树层次结构