120

I can do SELECT TOP (200) ... but why not BOTTOM (200)?

Well not to get into philosophy what I mean is, how can I do the equivalent of TOP (200) but in reverse (from the bottom, like you'd expect BOTTOM to do...)?

Venkat
  • 2,549
  • 2
  • 28
  • 61
MetaGuru
  • 42,847
  • 67
  • 188
  • 294

16 Answers16

114
SELECT
    columns
FROM
(
     SELECT TOP 200
          columns
     FROM
          My_Table
     ORDER BY
          a_column DESC
) SQ
ORDER BY
     a_column ASC
Tom H
  • 46,766
  • 14
  • 87
  • 128
  • 2
    Why are you using a derived table? – RichardOD Dec 09 '09 at 20:25
  • 16
    If you want to return rows in A->Z order, but select the top 200 in Z->A order this is one way to do it. The other answers, suggesting just changing the ORDER BY will not return the same results described in the question, as they will be out of order (unless order doesn't matter, which the OP did not say). – Tom H Dec 09 '09 at 20:32
  • 3
    @Tom H. Had to think for a few seconds as to what you meant (I've been up 14 hours). At first I couldn't see the difference between yours and the order by answers, but now I can. So +1. – RichardOD Dec 09 '09 at 20:52
  • 1
    This and other answers work fine when you're working on smaller tables. I don't think it's worth ordering the entire table by a column when you are just interested in the bottom few rows. – steadyfish Oct 13 '14 at 16:09
  • 1
    good answer, the best imho...the real problem is that i can't do this from an ASP script, so i think i need to reorder the objRecordset manually or with the function that ASP provide.... – Andrea_86 Jul 17 '15 at 13:27
105

It is unnecessary. You can use an ORDER BY and just change the sort to DESC to get the same effect.

Justin Ethier
  • 131,333
  • 52
  • 229
  • 284
  • 11
    Using DESC will return the last N rows, but the returned rows will also be in reverse order from the first N rows. – RickNZ Dec 10 '09 at 02:59
  • 18
    What if there is no index on your table to ORDER BY? – Protector one Feb 29 '12 at 11:00
  • 1
    @Protector one - I'm confused; you do not need an index to do an `ORDER BY`. Just pick one or more of the columns. – Justin Ethier Feb 29 '12 at 14:35
  • 9
    @Justin: Imagine having only one column, containing varchar values. ORDER BY would sort alphabetically, which is (probably) not what we want. – Protector one Mar 01 '12 at 08:25
  • 1
    @Protector one - Agreed, but that is another problem beyond this question and answer. If you need a numeric sort the best solution is likely to add a numeric column. However, if there is no numeric column (or index), the answer still applies - your solution just needs to be extended to meet additional requirements such as this. – Justin Ethier Mar 01 '12 at 14:30
  • 3
    Tom H. is the correct anwser, otherwise, your rows will be in reverse order. – Pierre-Olivier Goulet May 15 '14 at 12:25
  • 1
    So hypothetical case where it's not the same: I have a table of log entries, and I want to see the last N entries arranged from oldest to newest. – Adam Parkin Jul 10 '14 at 22:35
  • 1
    Right, you have to use a sub query in this case. – Justin Ethier Jul 10 '14 at 23:43
  • 1
    At first I didn't agree with your answer, but after further analysis I do. +1 – J.S. Orris Jul 11 '15 at 01:20
  • I just found the case for BOTTOM in my project (since I am here). I need to return result-set in ascending order (data for graphs) but want to have only latest X of records. To do this I need to change order to descending, use TOP, temp table and then use another select to select from temp table and change order again. Bottom would be very nice in this case. – Mariusz Feb 09 '16 at 11:26
  • There's a lot of things that are not necessary, but make life more convenient. Top can be used in commands that do not allow for a sort. Example, "Delete top(1000) from table" does not allow for an "Order by". To accomplish this I need to run a sub query or create a temp table or table variable. All of those would accomplish my goals, but simply being able to use "delete bottom (1000)" would be rather convenient. – Rick Paul Mar 28 '17 at 21:25
  • 1
    I suggest removing "it is unnecessary". Yeah technically there is a workaround, but at this moment this post has 97525 views. I'd be willing to bet most of them were hoping there was a "SELECT BOTTOM(n)..." function. – Ethan The Brave Mar 30 '17 at 17:14
  • Bear in mind that "plain" TOP queries work by partially scanning a table. It stops querying when it hits the top count value. Adding and ORDER BY clause forces a FULL table scan (it reads the whole table, irrespective of TOP count). – alejandrob Aug 20 '20 at 17:50
  • 1
    How to solve this question based on your answer https://stackoverflow.com/a/67245487/811293? – joseluisbz Apr 24 '21 at 17:19
55

Sorry, but I don't think I see any correct answers in my opinion.

The TOP x function shows the records in undefined order. From that definition follows that a BOTTOM function can not be defined.

Independent of any index or sort order. When you do an ORDER BY y DESC you get the rows with the highest y value first. If this is an autogenerated ID, it should show the records last added to the table, as suggested in the other answers. However:

  • This only works if there is an autogenerated id column
  • It has a significant performance impact if you compare that with the TOP function

The correct answer should be that there is not, and cannot be, an equivalent to TOP for getting the bottom rows.

Alexander Rossa
  • 1,900
  • 1
  • 22
  • 37
Martijn Burger
  • 7,315
  • 8
  • 54
  • 94
  • 3
    True, there seems to be no equivalent, just workarounds. – poke Nov 21 '14 at 17:30
  • 4
    TOP has nothing to do with order in which items were added to a table it just means "Give the the first X records that match my query" – Luke Feb 04 '15 at 23:09
  • 4
    Yes it does, it gives you the records first records added to the table that match your query. – Martijn Burger Feb 05 '15 at 08:08
  • 4
    I agree with Luke. Database tables are order-less by definition. One should never rely on the order provided by the RDBMS when the select statement has no ORDER BY clause. [read here on wiki](http://en.wikipedia.org/wiki/Table_%28database%29#Tables_versus_relations) _However, the database system does not guarantee any ordering of the rows unless an ORDER BY clause is specified in the SELECT statement that queries the table._ – Zohar Peled May 03 '15 at 08:00
  • 1
    The definition of [Microsoft Transact-SQL](https://msdn.microsoft.com/en-us/library/ms189463.aspx) is that it _returns the first N number of rows in an undefined order._ So it is undefined and it are the 'first' rows, an ambiguous definition. In my experience it always returns the first rows added to the server, although looking at the formal definition you cannot be 100% certian. – Martijn Burger May 05 '15 at 09:43
  • 2
    I agree with this answer, but in practice [Tom's answer](http://stackoverflow.com/a/1876620/2436175) solves the problem for all practical uses. – Antonio Oct 09 '15 at 09:26
  • like @Luke said it depends on the query. As a default sql-server sorts using the PK the same as for every other select query, not by the insert order. – MosheG Mar 10 '16 at 21:25
  • @MosheG Where did you get the information that it is sorted by the PK? – Martijn Burger Mar 11 '16 at 11:53
  • 1
    Wow. So much wrong here. SQL Server does not return rows in order of the PK. It does not return rows in the order that they were inserted. It doesn't even return rows in the order of the clustered index. It returns rows in the order of an `ORDER BY` and if there is none, **it returns rows in whatever order is convenient for it**. Relying on this to be the order that **you** want or think will happen is a huge mistake. This is all easily demonstrated. http://www.sqlfiddle.com/#!6/fa5af. Build the schema and do `SELECT my_string FROM dbo.Test_Order`. – Tom H May 25 '16 at 14:09
  • Hi @TomH. I agree that your answer gives a practical solution. You are right about the order, I updated the answer for that. However, I still think that there is and cannot be an alternative to `TOP`. – Martijn Burger May 25 '16 at 14:39
  • 1
    If TOP 10 returns 10 records without any order, then it's wrong to say that they are the 'first' 10 records. They are just 10 records, selected at random. Thats why the keyword 'TOP' becomes misleading, and 'LIMIT' is a much more appropiate keyword. You are limiting the results to just 10 records. – Juan Perez Feb 27 '22 at 19:33
  • 1
    Also, if you were to switch to a different db (e.g. PostgreSql with LIMIT), it may or may not act differently. Agree with @JuanPerez that TOP is misleading. IMHO LIMIT is a more appropriate term. – user1062589 Mar 01 '22 at 13:01
31

Logically,

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

E.g. Select Bottom 1000 from Employee:

In 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
)
Shadi Alnamrouti
  • 11,796
  • 4
  • 56
  • 54
  • 2
    Hi shadi2014, without the use of "ORDER BY" your result will be somehow random. – bummi May 03 '15 at 08:03
  • 10
    bummi, you're right, but that what makes this answer correct. Select TOP itself is "random" in theory, and this is the correct implementation for Select BOTTOM. In a table of 5000 records, bottom 1000 is everything except top 4000. – tzachs Aug 05 '15 at 14:48
16

It would seem that any of the answers which implement an ORDER BY clause in the solution is missing the point, or does not actually understand what TOP returns to you.

TOP returns an unordered query result set which limits the record set to the first N records returned. (From an Oracle perspective, it is akin to adding a where ROWNUM < (N+1).

Any solution which uses an order, may return rows which also are returned by the TOP clause (since that data set was unordered in the first place), depending on what criteria was used in the order by

The usefulness of TOP is that once the dataset reaches a certain size N, it stops fetching rows. You can get a feel for what the data looks like without having to fetch all of it.

To implement BOTTOM accurately, it would need to fetch the entire dataset unordered and then restrict the dataset to the final N records. That will not be particularly effective if you are dealing with huge tables. Nor will it necessarily give you what you think you are asking for. The end of the data set may not necessarily be "the last rows inserted" (and probably won't be for most DML intensive applications).

Similarly, the solutions which implement an ORDER BY are, unfortunately, potentially disastrous when dealing with large data sets. If I have, say, 10 Billion records and want the last 10, it is quite foolish to order 10 Billion records and select the last 10.

The problem here, is that BOTTOM does not have the meaning that we think of when comparing it to TOP.

When records are inserted, deleted, inserted, deleted over and over and over again, some gaps will appear in the storage and later, rows will be slotted in, if possible. But what we often see, when we select TOP, appears to be sorted data, because it may have been inserted early on in the table's existence. If the table does not experience many deletions, it may appear to be ordered. (e.g. creation dates may be as far back in time as the table creation itself). But the reality is, if this is a delete-heavy table, the TOP N rows may not look like that at all.

So -- the bottom line here(pun intended) is that someone who is asking for the BOTTOM N records doesn't actually know what they're asking for. Or, at least, what they're asking for and what BOTTOM actually means are not the same thing.

So -- the solution may meet the actual business need of the requestor...but does not meet the criteria for being the BOTTOM.

user9323238
  • 161
  • 1
  • 2
  • 3
    Excellent explanation. Upvote for shedding more light on the topic. – rohrl77 Jul 09 '19 at 11:29
  • My use case is this. I ran a big `insert` statement to put rows into a big, unindexed table. (I am populating the table first before I start to index it.) I lost my client session because of a reboot or whatever, and now I want to see whether my newly added rows are in there. If the 'bottom' row of the table is one of my recent ones, I know the operation completed. If the 'bottom' row is something else, well there are no guarantees and I have to scan the whole table to make sure... but most likely I could save some time by quickly checking the 'bottom' just as you can the 'top'. – Ed Avis Feb 21 '20 at 17:28
  • Good explanation, but it still implies the existence of a bottom, that just requires the data to be read/retrieved in reverse. In the (admitteedly edge) case of an aborted insert on a new table, verifying the last record inserted (the bottom) without retrieving everything would be useful. Is there a technical reason why the table data can't be retrieved in reverse order? – James Jun 17 '20 at 14:23
  • +1 for the philosophical debate about the BOTTOM concept but also -1 since OP specifically asked "not to get into philosophy"! – scign Aug 20 '21 at 01:36
4

First, create an index in a subquery according to the table's original order using:

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

Then order the table descending by the RowIndex column you've created in the main query:

ORDER BY RowIndex DESC

And finally use TOP with your wanted quantity of rows:

    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
biruk1230
  • 3,042
  • 4
  • 16
  • 29
3

All you need to do is reverse your ORDER BY. Add or remove DESC to it.

Justin Swartsel
  • 3,451
  • 1
  • 20
  • 24
2

The problem with ordering the other way is that it often does not make good use of indices. It is also not very extendable if you ever need to select a number of rows that are not at the start or the end. An alternative way is as follows.

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;
Paul
  • 16,285
  • 13
  • 41
  • 52
  • 2
    1) If changing the direction of your ORDER BY clause "does not make good use of indices", then get a decent RDBMS! The RDBMS should never care whether it navigates the index forwards or backwards. 2) You're concerned about use of indexes, yet your solution attaches a sequence to every row in the table... That's one way to guarantee an appropriate index will NOT be used. – Disillusioned Dec 10 '09 at 00:20
2

The currently accepted answer by "Justin Ethier" is not a correct answer as pointed out by "Protector one".

As far as I can see, as of now, no other answer or comment provides the equivalent of BOTTOM(x) the question author asked for.

First, let's consider a scenario where this functionality would be needed:

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

This returns a table of one column and five records:

  • apple
  • orange
  • banana
  • apple
  • lime

As you can see: we don't have an ID column; we can't order by the returned column; and we can't select the bottom two records using standard SQL like we can do for the top two records.

Here is my attempt to provide a solution:

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

And here is a more complete solution:

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

I am by no means claiming that this is a good idea to use in all circumstances, but it provides the desired results.

tomosius
  • 1,369
  • 12
  • 18
2

You can use the OFFSET FETCH clause.

SELECT COUNT(1) FROM COHORT; --Number of results to expect

SELECT * FROM COHORT 
ORDER BY ID
OFFSET 900 ROWS --Assuming you expect 1000 rows
FETCH NEXT 100 ROWS ONLY;

(This is for Microsoft SQL Server)

Official documentation: https://www.sqlservertutorial.net/sql-server-basics/sql-server-offset-fetch/

Selah
  • 7,728
  • 9
  • 48
  • 60
1

"Tom H" answer above is correct and it works for me in getting Bottom 5 rows.

SELECT [KeyCol1], [KeyCol2], [Col3]
FROM
(SELECT TOP 5 [KeyCol1],
       [KeyCol2],
       [Col3]
  FROM [dbo].[table_name]
  ORDER BY [KeyCol1],[KeyCol2] DESC) SOME_ALAIS
  ORDER BY [KeyCol1],[KeyCol2] ASC

Thanks.

0

try this.

declare @floor int --this is the offset from the bottom, the number of results to exclude
declare @resultLimit int --the number of results actually retrieved for use
declare @total int --just adds them up, the total number of results fetched initially

--following is for gathering top 60 results total, then getting rid of top 50. We only keep the last 10
set @floor = 50 
set @resultLimit = 10
set @total = @floor + @resultLimit

declare @tmp0 table(
    --table body
)

declare @tmp1 table(
    --table body
)

--this line will drop the wanted results from whatever table we're selecting from
insert into @tmp0
select Top @total --what to select (the where, from, etc)

--using floor, insert the part we don't want into the second tmp table
insert into @tmp1
select top @floor * from @tmp0

--using select except, exclude top x results from the query
select * from @tmp0
except 
select * from @tmp1
HumbleWebDev
  • 555
  • 4
  • 20
  • what makes your code for the OP? Please add a little bit more explanation on how you try to resolve the problem – techspider May 31 '16 at 17:19
  • I made an edit. I hope that it explains it a little better, adding more comments. The main idea is select top x from a table, then select top x - num wanted, then using an except statement to exlude the uneeded results. – HumbleWebDev May 31 '16 at 18:52
0

I've come up with a solution to this that doesn't require you to know the number of row returned.

For example, if you want to get all the locations logged in a table, except the latest 1 (or 2, or 5, or 34)

SELECT * 
FROM
    (SELECT ROW_NUMBER() OVER (ORDER BY CreatedDate) AS Row, * 
    FROM Locations
    WHERE UserId = 12345) AS SubQuery
WHERE Row > 1 -- or 2, or 5, or 34
Red
  • 3,030
  • 3
  • 22
  • 39
0

Querying a simple subquery sorted descending, followed by sorting on the same column ascending does the trick.

SELECT * FROM 
    (SELECT TOP 200 * FROM [table] t2 ORDER BY t2.[column] DESC) t1
    ORDER BY t1.[column]
sheppe
  • 708
  • 5
  • 12
0
SELECT TOP 10*from TABLE1 ORDER BY ID DESC

Where ID is the primary key of the TABLE1.

-2
SELECT columns FROM My_Table LIMIT 200 OFFSET (SELECT Count(*)-200 My_Table)