Select 顶部使用 SQL 服务器 returns 与 select 不同的输出 *

Select top using SQL Server returns different output than select *

我尝试根据字母和编号格式从数据库中获取 select top n 数据。输出必须先按字母顺序排列,再按数字顺序排列。

当我尝试获取所有数据 (select *) 时,我得到了正确的输出:

select nocust, share 
from TB_STOCK
where share = ’BBCA’ 
  and concat(share, nocust) < ‘ZZZZZZZZ’
order by 
    case when nocust like ‘[a-z]%’ then 0 else 1 end


nocust | share
-------+--------
a522   | BBCA
b454   | BBCA
k007   | BBCA
p430   | BBCA
q797   | BBCA
s441   | BBCA
s892   | BBCA
u648   | BBCA
v107   | BBCA
4211   | BBCA
6469   | BBCA
6751   | BBCA

但是当我尝试 select top n(例如:前 5 名)时,我得到的输出与预期不同(不像 select * from table):

select top 5 nocust, share 
from TB_STOCK
where share = ’BBCA’ 
  and concat(share, nocust) < ‘ZZZZZZZZ’
order by 
    case when nocust like ‘[a-z]%’ then 0 else 1 end

nocust | share
-------+--------
k007   | BBCA
b454   | BBCA
a522   | BBCA
p430   | BBCA
q797   | BBCA

我预计错误是在 concat 和 order by 之间的某个地方,有人能告诉我如何获得正确的前 5 个输出,例如:

nocust | share
-------+--------
a522   | BBCA
b454   | BBCA
k007   | BBCA
p430   | BBCA
q797   | BBCA

你有一个非常奇怪的 ORDER BY - 它只确保以字母开头的条目在以数字开头的条目之前排序 - 但你 NOT 实际上是按值本身排序的。没有具体的 ORDER BY 表示:无法保证行的排序方式 - 正如您在此处看到的那样。

您需要将 ORDER BY 调整为:

 ORDER BY
     CASE WHEN nocust LIKE '[a-z]%' THEN 1 ELSE 0 END,
     nocust

现在 您实际上是按 nocust 订购的 - 现在,我很确定,输出将是相同的

您的 ORDER BY 排序不稳定;它将数据大致分为两个类别之一,但没有足够详细地指定如何在类别中对项目进行排序。这意味着在 TOP 5 表单中 sql 服务器可以自由选择数据访问策略,这意味着它可以在找到 5 行数据后轻松停止 case when returns 0

假设您有来自 SELECT * ... ORDER BY Category

的输出
Category, Thing
Animal, Cat
Animal, Dog
Animal, Goat
Vegetable, Potato
Vegetable, Turnip
Vegetable, Swede

绝对不能保证如果您执行SELECT TOP 2 * ... ORDER BY category,您将按此顺序获得"Cat, Dog"。您今天可以合理地得到 "Goat, Dog",明天可以得到 "Cat, Goat",当 SQL 服务器在添加新数据后重新调整其索引时。对于按类别排名前 2 的顺序,您唯一可以保证的是,只要数据库中至少有两种动物,并且没有按字母顺序排列早于 "animal" 的新类别,您就会得到两个动物

是不是因为TOP N的优化意味着sql服务器一旦有N行符合条件就可以提前停止;如果它已经找到 5 行的类别在排序中排在第一位,则它不需要访问和排序一百万行。假设它可以知道列中不同的值和这些值的计数作为其统计信息的一部分,它可以对这些不同的值进行排序以了解哪些值先出现,然后找到任意 5 个随机行,这些行的值将先排序,然后 return 它们。本质上 sql 服务器可能认为 "I know I have 3 'animal', and animals come before everything else, and the user wants 2. I'll just start reading rows and stop after I get 2 animals" 而不是 "I'll read every Thing, sort all million of them on category, then take the first 2 rows"

这可能比对一百万行进行排序然后提取第一个 X

快得多

为了在每次必须通过指定保证类别中的事物的排序条件使排序稳定时获得可重复的结果,将正确排序到没有歧义的地方

在您的订单中添加更多列,这样每一行在整体排序中都有一个保证的位置,然后您的排序就会稳定,TOP N 每次都会 return 相同的行。要使排序稳定,您排序所依据的列集合必须具有唯一的值组合。您可以按 20 列排序,但如果有任何行的所有 30 列都具有相同的值(并且差异仅发生在第 21 个值上,您不按该值排序),则不能保证排序顺序

我想从不同的角度来回答这个问题。

首先要明确的是Optimizer make the best possible plan quickly.

Optimizer select index or do not select index in most cost effective manner.

我正在使用 Adventure 2016 database 并且 Production.Product504 行。

select [Name],ProductNumber from Production.Product
order by [Name]

按预期对行进行排序。

select top 5 [Name],ProductNumber from Production.Product
order by [Name]

按预期对行进行排序。

如果我在订单中使用 case 语句

select [Name],ProductNumber from Production.Product
order by case when [name] like '[a]%' then 1 else -1 end

按预期对记录进行排序。所有 504 行都在处理中。

如果我在 Top 中使用小于等于 20% of total rows,例如

select Top 5 [Name],ProductNumber from Production.Product
order by case when [name] like '[a]%' then 1 else -1 end



Then it pick first n records and display n record quickly.
Sorting was not as expected.

如果我在 Top 中使用更多 20% of total rows,例如

select Top (101) [Name],ProductNumber from Production.Product
order by case when [name] like '[a]%' then 1 else -1 end

它将处理所有 504 rows 并进行相应排序。

排序结果符合预期。

以上所有情况 Clustered Index Scan (Product id) 都完成了。 在这个例子中 [Name]and ProductNumber 是两个不同的 non clustered index.

但是没有被选中

你可以做到,

;With CTE as(

select  nocust, share ,
case when nocust like ‘[a-z]%’ then 0 else 1 end SortCol
from TB_STOCK
where share = ’BBCA’ 
  and concat(share, nocust) < ‘ZZZZZZZZ’


)

select top 5 * from CTE
order by SortCol