从有序 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 5
在 same 查询中没有 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 行的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode 34 Search for a Range (有序数组中查找给定数字的起止下标)
从 Firebase 实时数据库 Kotlin 中检索有序数据