如何选择最底部的行?

122

我可以使用SELECT TOP (200) ...,但为什么没有BOTTOM (200)呢?

好吧,不谈哲学,我的意思是,如何执行相当于TOP (200)的相反操作(从底部开始,就像你想要的BOTTOM一样...)?

16个回答

117
SELECT
    columns
FROM
(
     SELECT TOP 200
          columns
     FROM
          My_Table
     ORDER BY
          a_column DESC
) SQ
ORDER BY
     a_column ASC

2
你为什么要使用派生表? - RichardOD
16
如果你想按A到Z的顺序返回行,但是按Z到A的顺序选择前200行,有一种方法可以实现。其他回答建议只更改ORDER BY将无法返回与问题描述相同的结果,因为它们会失去顺序(除非顺序不重要,但OP没有说)。 - Tom H
3
@Tom H. 我需要花几秒钟思考你的意思(我已经醒了14个小时)。起初我看不出你和按答案排序之间的区别,但现在我能够理解了。赞一个。 - RichardOD
1
这个和其他答案在处理较小的表时效果很好。我认为当你只对底部几行感兴趣时,没有必要按列对整个表进行排序。 - steadyfish
1
好的答案,在我看来是最好的...真正的问题是我无法从ASP脚本中执行此操作,因此我认为我需要手动重新排序objRecordset或使用ASP提供的函数。 - Andrea_86

104

这是不必要的。你可以使用ORDER BY,并将排序方式改为DESC来获得相同的效果。


11
使用 DESC 将返回最后的 N 行,但是返回的行也将与前面的 N 行顺序相反。 - RickNZ
19
如果您的表没有索引来进行ORDER BY操作,该怎么办? - Protector one
10
想象一下只有一个包含varchar值的列。ORDER BY会按字母顺序排序,这可能不是我们想要的结果。 - Protector one
3
汤姆·H. 是正确答案,否则您的行将倒序。 - Pierre-Olivier Goulet
1
起初我不同意你的答案,但经过进一步分析后,我同意了。+1 - J.S. Orris
显示剩余9条评论

54

很抱歉,但在我看来,我认为没有任何正确的答案。

TOP x函数显示未定义顺序的记录。根据这个定义,无法定义BOTTOM函数。

独立于任何索引或排序顺序。当您执行ORDER BY y DESC时,将首先获取具有最高y值的行。如果这是自动生成的ID,则应该显示最后添加到表中的记录,就像其他答案中建议的那样。但是:

  • 仅在存在自动生成的id列时才适用
  • TOP函数相比,它具有显着的性能影响

正确的答案应该是不存在并且不能有与TOP等价的方法来获取底部行。


3
确实似乎没有相应的词汇,只有变通的方法。 - poke
4
TOP与项目添加到表中的顺序无关,它只是表示“给出与我的查询匹配的前X条记录”。 - Luke
4
是的,它会给你匹配查询条件的表中最先添加的记录。 - Martijn Burger
4
我同意Luke的观点。根据定义,数据库表是无序的。当SELECT语句没有ORDER BY子句时,不应该依赖于RDBMS提供的顺序。然而,除非在查询表的SELECT语句中指定了ORDER BY子句,否则数据库系统不保证行的任何排序。在维基百科上阅读 - Zohar Peled
2
我同意这个答案,但在实践中,Tom的答案解决了所有实际使用的问题。 - Antonio
显示剩余8条评论

31

从逻辑上讲,

BOTTOM (x) is all the records except TOP (n - x), where n is the count; x <= n

例如:选择Employee表的最后1000行:

在T-SQL中,

DECLARE 
@bottom int,
@count int

SET @bottom = 1000 
SET @count = (select COUNT(*) from Employee)

select * from Employee emp where emp.EmployeeID not in 
(
SELECT TOP (@count-@bottom) Employee.EmployeeID FROM Employee
)

2
嗨shadi2014,如果不使用“ORDER BY”,你的结果会有些随机。 - bummi
10
Bummi,你说得对,但这正是使这个答案正确的原因。理论上,选择TOP本身是“随机”的,而这是Select BOTTOM的正确实现。在一个5000条记录的表中,最后1000条记录就是除前4000条记录以外的所有记录。 - tzachs

16
似乎任何实现ORDER BY子句的答案都错了,或者并没有真正理解TOP返回的内容。
TOP返回一个无序查询结果集,将记录集限制为第一个返回的N条记录。(从Oracle的角度来看,这类似于添加where ROWNUM < (N+1)。
任何使用排序的解决方案都可能返回被TOP子句返回的行(因为该数据集在首次排序前是无序的),具体取决于排序时使用的标准。
TOP的有用之处在于一旦数据集达到某个大小N,则停止获取行。您可以感受数据的外观,而无需获取所有数据。
要准确实现BOTTOM,它需要无序地获取整个数据集,然后将数据集限制为最后的N条记录。如果您处理的是巨大的表,则这将不是特别有效。它也不一定会给出您所要求的结果。数据集的结尾可能不一定是“最后插入的行”(对于大多数DML密集型应用程序来说通常不是)。
同样,实现ORDER BY的解决方案在处理大型数据集时也可能会产生灾难性后果。如果我有,比如,100亿条记录,想要最后的10条记录,那么对100亿条记录进行排序并选择最后的10条记录就相当愚蠢。
问题在于,BOTTOM在与TOP进行比较时并没有我们所想象的意义。当记录被反复插入、删除、插入、删除时,存储空间中会出现一些空隙。如果可能的话,稍后会将行插入这些空隙。但我们经常看到的情况是,在选择 TOP 时,数据看起来好像是排序过的,因为它可能在表存在的早期就被插入了。如果表没有遭受太多删除,它可能看起来是有序的(例如,创建日期可能与表的创建本身一样久远)。但事实是,如果这是一个频繁删除的表,TOP N 行可能根本不像那样排列。
所以 -- 这里的底线(pun intended)是,要求 BOTTOM N 记录的人实际上并不知道他们在要求什么。或者说,他们要求的和 BOTTOM 实际上意味着的东西并不相同。
因此,解决方案可能满足请求者的实际业务需求...但并不符合 BOTTOM 的标准。

3
解释得非常好,为阐明这个话题点赞。 - rohrl77
我的使用情况是这样的。我运行了一个大的insert语句,将行插入到一个大型未索引的表中。(在开始对其进行索引之前,我首先填充表格。)由于重新启动或其他原因,我失去了客户端会话,现在我想查看我的新添加的行是否存在其中。如果表的“底部”行是我最近添加的行之一,那么我知道操作已完成。如果“底部”行是其他内容,则没有保证,我必须扫描整个表以确保...但很可能通过快速检查“底部”来节省一些时间,就像您可以检查“顶部”一样。 - Ed Avis
很好的解释,但它仍然暗示着存在一个底部,只需要以相反的顺序读取/检索数据。在新表上中止插入的(尽管是边缘)情况下,验证最后插入的记录(底部)而不必检索所有内容将会很有用。是否有技术原因导致无法以相反的顺序检索表数据? - James
+1 对于关于BOTTOM概念的哲学辩论,但-1因为OP明确要求“不涉及哲学”! - scign

4

首先,在子查询中按照表的原始顺序创建索引,使用以下代码:

ROW_NUMBER () OVER (ORDER BY (SELECT NULL) ) AS RowIndex

然后按照主查询中创建的RowIndex列,降序排序表格:

ORDER BY RowIndex DESC

最后,使用TOP关键字并指定所需行数即可:
    SELECT TOP 1 * --(or 2, or 5, or 34)
    FROM   (SELECT ROW_NUMBER() OVER (ORDER BY  (SELECT NULL) ) AS RowIndex, * 
            FROM MyTable) AS SubQuery
    ORDER BY RowIndex DESC

3

您只需要将ORDER BY反转即可。添加或删除DESC


2

按另一种方式排序的问题在于它通常没有充分利用索引。如果您需要选择不在开头或结尾的多行,它也不太可扩展。另一种替代方法如下。

DECLARE @NumberOfRows int;
SET @NumberOfRows = (SELECT COUNT(*) FROM TheTable);

SELECT col1, col2,...
FROM (
    SELECT col1, col2,..., ROW_NUMBER() OVER (ORDER BY col1) AS intRow
    FROM TheTable
) AS T
WHERE intRow > @NumberOfRows - 20;

2
  1. 如果改变ORDER BY子句的方向“不能很好地利用索引”,那么就换一个好的关系型数据库管理系统吧!RDBMS不应该关心它是向前还是向后导航索引。
  2. 你担心使用索引,但你的解决方案将序列附加到表中的每一行……这是确保不会使用适当索引的一种方式。
- Disillusioned

2

"Justin Ethier"的回答目前不正确,正如"Protector one"所指出的。

就我目前所见,没有其他回答或评论提供问题作者所需的BOTTOM(x)等效功能。

首先,让我们考虑需要使用此功能的情况:

SELECT * FROM Split('apple,orange,banana,apple,lime',',')

这将返回一个包含一列和五条记录的表格:

  • 苹果
  • 橙子
  • 香蕉
  • 苹果
  • 酸橙

正如您所看到的:我们没有ID列;我们无法按返回的列排序;我们无法像对前两条记录那样使用标准SQL选择后两条记录。

以下是我提供解决方案的尝试:

SELECT * INTO #mytemptable FROM Split('apple,orange,banana,apple,lime',',')
ALTER TABLE #mytemptable ADD tempID INT IDENTITY
SELECT TOP 2 * FROM #mytemptable ORDER BY tempID DESC
DROP TABLE #mytemptable

这里是一个更完整的解决方案:

SELECT * INTO #mytemptable FROM Split('apple,orange,banana,apple,lime',',')
ALTER TABLE #mytemptable ADD tempID INT IDENTITY
DELETE FROM #mytemptable WHERE tempID <= ((SELECT COUNT(*) FROM #mytemptable) - 2)
ALTER TABLE #mytemptable DROP COLUMN tempID
SELECT * FROM #mytemptable
DROP TABLE #mytemptable

我绝不是在声称这是在所有情况下都可以使用的好方法,但它能够提供所需的结果。

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接