Как выбрать самые нижние строки?


100

Я могу выполнить SELECT TOP (200) ... но почему не BOTTOM (200)?

Ну, чтобы не вдаваться в философию, я имею в виду, как я могу сделать эквивалент TOP (200), но наоборот (снизу, как вы ожидаете, что BOTTOM сделает ...)?

Ответы:


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

2
Почему вы используете производную таблицу?
RichardOD

14
Если вы хотите вернуть строки в порядке A-> Z, но выбрать первые 200 строк в порядке Z-> A, это один из способов сделать это. Другие ответы, предлагающие просто изменить ORDER BY, не будут возвращать те же результаты, что и в вопросе, поскольку они будут не в порядке (если порядок не имеет значения, о чем OP не сказал).
Tom H

3
@Tom H. Пришлось подумать на несколько секунд, что ты имел в виду (я не спал 14 часов). Сначала я не видел разницы между вашим и порядком по ответам, но теперь вижу. Итак, +1.
RichardOD

1
Этот и другие ответы отлично работают, когда вы работаете с небольшими таблицами. Я не думаю, что стоит упорядочивать всю таблицу по столбцу, если вас интересуют только несколько нижних строк.
stablefish

1
хороший ответ, лучший imho ... настоящая проблема в том, что я не могу сделать это из сценария ASP, поэтому я думаю, что мне нужно изменить порядок objRecordset вручную или с помощью функции, предоставляемой ASP ....
Andrea_86,

98

В этом нет необходимости. Вы можете использовать ORDER BYи просто изменить сортировку, DESCчтобы получить тот же эффект.


3
Google сказал то же самое, и теперь 9 из вас согласны, достаточно хорошо для меня, спасибо: D
CloudMeta

9
Использование DESC вернет последние N строк, но возвращенные строки также будут в обратном порядке от первых N строк.
RickNZ

11
Что делать, если в вашей таблице нет индекса для ORDER BY?
Protector one

8
@Justin: представьте, что у вас есть только один столбец, содержащий значения varchar. ORDER BY будет сортировать по алфавиту, что (вероятно) не то, что нам нужно.
Protector one

3
Tom H. - правильный ответ, иначе ваши строки будут в обратном порядке.
Пьер-Оливье Гуле

39

Извините, но я не думаю, что вижу правильные ответы, на мой взгляд.

Функция TOPx показывает записи в неопределенном порядке. Из этого определения следует, что BOTTOMфункция не может быть определена.

Независимо от какого-либо индекса или порядка сортировки. Когда вы делаете это, ORDER BY y DESCвы сначала получаете строки с наибольшим значением y. Если это автоматически сгенерированный идентификатор, он должен отображать записи, добавленные в таблицу последними, как это предлагается в других ответах. Тем не мение:

  • Это работает только при наличии автоматически созданного столбца идентификаторов.
  • Это оказывает значительное влияние на производительность, если сравнить это с TOPфункцией

Правильный ответ должен заключаться в том, что нет и не может быть эквивалента TOPдля получения нижних строк.


3
Правда, аналога вроде нет, просто обходные пути.
poke

4
TOP не имеет ничего общего с порядком, в котором элементы были добавлены в таблицу, это просто означает: «Дайте первые X записей, которые соответствуют моему запросу»
Люк

4
Да, это дает вам первые записи, добавленные в таблицу, которые соответствуют вашему запросу.
Martijn Burger

4
Я согласен с Люком. Таблицы базы данных по определению не имеют порядка. Никогда не следует полагаться на порядок, предоставляемый СУБД, если в инструкции select нет предложения ORDER BY. читайте здесь, в вики. Однако система базы данных не гарантирует никакого упорядочивания строк, если только в операторе SELECT, запрашивающем таблицу, не указано предложение ORDER BY.
Зохар Пелед

2
Я согласен с этим ответом, но на практике ответ Тома решает проблему для всех практических применений.
Антонио

18

Логически,

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

Например, выберите «Нижние 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
)

1
Привет, shadi2014, без использования «ORDER BY» ваш результат будет каким-то случайным.
bummi

5
бумми, ты прав, но именно поэтому этот ответ правильный. Select TOP сам по себе является "случайным" в теории, и это правильная реализация для Select BOTTOM. В таблице из 5000 записей нижняя 1000 - это все, кроме верхних 4000.
tzachs 05

9

Казалось бы, в любом из ответов, реализующих предложение ORDER BY в решении, отсутствует суть или на самом деле не понимается, что возвращает вам TOP.

TOP возвращает неупорядоченный набор результатов запроса, который ограничивает набор записей первыми N возвращенными записями. (С точки зрения Oracle, это похоже на добавление where ROWNUM <(N + 1).

Любое решение, использующее порядок, может возвращать строки, которые также возвращаются предложением TOP (поскольку этот набор данных был изначально неупорядочен), в зависимости от того, какие критерии использовались в порядке

Полезность TOP заключается в том, что как только набор данных достигает определенного размера N, он прекращает выборку строк. Вы можете почувствовать, как выглядят данные, без необходимости извлекать их все.

Для точной реализации BOTTOM потребуется извлечь весь набор данных в неупорядоченном виде, а затем ограничить набор данных последними N записями. Это не будет особенно эффективно, если вы имеете дело с огромными таблицами. Это также не обязательно даст вам то, о чем вы думаете . Конец набора данных не обязательно может быть «последними вставленными строками» (и, вероятно, не будет для большинства приложений с интенсивным использованием DML).

Точно так же решения, реализующие ORDER BY, к сожалению, потенциально губительны при работе с большими наборами данных. Если у меня есть, скажем, 10 миллиардов записей и мне нужны последние 10, было бы глупо заказывать 10 миллиардов записей и выбирать последние 10.

Проблема здесь в том, что BOTTOM не имеет того значения, о котором мы думаем, сравнивая его с TOP.

Когда записи вставляются, удаляются, вставляются, удаляются снова и снова, в хранилище появляются пробелы, а позже строки будут вставлены, если это возможно. Но то, что мы часто видим, когда выбираем TOP, оказывается отсортированными данными, потому что они могли быть вставлены на ранней стадии существования таблицы. Если в таблице не много удалений, может показаться, что она упорядочена. (например, даты создания могут быть такими же далекими, как и само создание таблицы). Но на самом деле, если это таблица с большим количеством удалений, строки TOP N могут выглядеть совсем не так.

Итак - суть здесь (каламбур) заключается в том, что кто-то, кто запрашивает BOTTOM N записей, на самом деле не знает, что они просят. Или, по крайней мере, то, что они просят, и то, что на самом деле означает BOTTOM, - не одно и то же.

Итак - решение может удовлетворить фактические бизнес-потребности отправителя запроса ... но не соответствует критериям НИЖНЕГО.


1
Отличное объяснение. Проголосуйте за то, чтобы пролить больше света на эту тему.
rohrl77 09

Мой вариант использования таков. Я insertвыполнил большой оператор, чтобы поместить строки в большую неиндексированную таблицу. (Я сначала заполняю таблицу, прежде чем начну ее индексировать.) Я потерял сеанс клиента из-за перезагрузки или чего-то еще, и теперь я хочу увидеть, есть ли там мои недавно добавленные строки. Если «нижняя» строка таблицы - одна из моих недавних, я знаю, что операция завершена. Если «нижняя» строка - это что-то еще, что ж, нет никаких гарантий, и я должен просканировать всю таблицу, чтобы убедиться ... но, скорее всего, я мог бы сэкономить время, быстро проверив «нижнюю», как вы можете верхняя'.
Эд Авис,

Хорошее объяснение, но оно по-прежнему подразумевает существование дна, для которого просто требуется, чтобы данные считывались / извлекались в обратном порядке. В случае (допустимого края) прерванной вставки в новую таблицу будет полезна проверка последней вставленной записи (внизу) без извлечения всего. Есть ли техническая причина, по которой данные таблицы не могут быть получены в обратном порядке?
Джеймс

3

В настоящее время принятый ответ Джастина Этье не является правильным ответом, как указывает «Защитник один».

Насколько я понимаю, на данный момент ни один другой ответ или комментарий не дает эквивалента 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

Я ни в коем случае не утверждаю, что это хорошая идея для использования при любых обстоятельствах, но она дает желаемые результаты.



1

Проблема с другим способом упорядочивания состоит в том, что при этом часто не используются индексы. Он также не очень расширяемый, если вам когда-либо понадобится выбрать количество строк, которые не находятся в начале или в конце. Альтернативный способ заключается в следующем.

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 «не позволяет эффективно использовать индексы», тогда получите достойную СУБД! РСУБД никогда не должно заботиться о том, перемещается ли она по индексу вперед или назад. 2) Вас беспокоит использование индексов, но ваше решение прикрепляет последовательность к каждой строке в таблице ... Это один из способов гарантировать, что соответствующий индекс НЕ будет использоваться.
Разочарованный

1

Ответ "Tom H" выше правильный, и он работает для меня при получении нижних 5 строк.

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

Спасибо.


0

попробуй это.

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

что делает ваш код для OP? Пожалуйста, добавьте еще немного объяснений того, как вы пытаетесь решить проблему
techspider

Я сделал правку. Я надеюсь, что это немного лучше объясняет, добавляя больше комментариев. Основная идея состоит в том, чтобы выбрать верхний x из таблицы, затем выбрать верхний x - требуемое число, а затем использовать оператор except, чтобы исключить ненужные результаты.
HumbleWebDev

0

Я придумал решение, которое не требует, чтобы вы знали количество возвращаемых строк.

Например, если вы хотите, чтобы в таблице были зарегистрированы все местоположения, кроме последнего 1 (или 2, или 5, или 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

0

Выполнение простого подзапроса, отсортированного по убыванию, с последующей сортировкой по тому же столбцу по возрастанию делает свое дело.

SELECT * FROM 
    (SELECT TOP 200 * FROM [table] t2 ORDER BY t2.[column] DESC) t1
    ORDER BY t1.[column]


0

Сначала создайте индекс в подзапросе в соответствии с исходным порядком таблицы, используя:

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.