Как выбрать n-ю строку в таблице базы данных SQL?

Мне интересно изучить некоторые (в идеале) независимые от базы данных способы выбора n- й строки из таблицы базы данных. Было бы также интересно посмотреть, как этого можно достичь, используя встроенные функции следующих баз данных:

  • SQL Server
  • MySQL
  • PostgreSQL
  • SQLite
  • Oracle

В настоящее время я делаю что-то вроде следующего в SQL Server 2005, но мне было бы интересно увидеть другие более агностические подходы:

WITH Ordered AS (
SELECT ROW_NUMBER() OVER (ORDER BY OrderID) AS RowNumber, OrderID, OrderDate
FROM Orders)
SELECT *
FROM Ordered
WHERE RowNumber = 1000000

Кредит за приведенный выше SQL: блог Фироза Ансари

Обновление: см . Ответ Troels Arvin относительно стандарта SQL. Троэлс, у вас есть ссылки, которые мы можем процитировать?

Ответов (25)

Решение

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

Действительно хороший сайт, рассказывающий об этом и других вещах, - http://troels.arvin.dk/db/rdbms/#select-limit .

По сути, PostgreSQL и MySQL поддерживают нестандартные:

SELECT...
LIMIT y OFFSET x 

Oracle, DB2 и MSSQL поддерживают стандартные оконные функции:

SELECT * FROM (
  SELECT
    ROW_NUMBER() OVER (ORDER BY key ASC) AS rownumber,
    columns
  FROM tablename
) AS foo
WHERE rownumber <= n

(который я только что скопировал с сайта, указанного выше, поскольку я никогда не использую эти БД)

Обновление: в PostgreSQL 8.4 поддерживаются стандартные оконные функции, поэтому ожидайте, что второй пример будет работать и для PostgreSQL.

Обновление: SQLite добавил поддержку оконных функций в версии 3.25.0 от 15.09.2018, поэтому обе формы также работают в SQLite.

ДОБАВИТЬ:

LIMIT n,1

Это ограничит результаты одним результатом, начиная с результата n.

Оракул:

select * from (select foo from bar order by foo) where ROWNUM = x

1 небольшое изменение: n-1 вместо n.

select *
from thetable
limit n-1, 1

Например, если вы хотите выбрать каждую 10-ю строку в MSSQL, вы можете использовать;

SELECT * FROM (
  SELECT
    ROW_NUMBER() OVER (ORDER BY ColumnName1 ASC) AS rownumber, ColumnName1, ColumnName2
  FROM TableName
) AS foo
WHERE rownumber % 10 = 0

Просто возьмите МОД и измените число 10 здесь на любое число, которое хотите.

невероятно, что вы можете найти SQL-движок, выполняющий это ...

WITH sentence AS
(SELECT 
    stuff,
    row = ROW_NUMBER() OVER (ORDER BY Id)
FROM 
    SentenceType
    )
SELECT
    sen.stuff
FROM sentence sen
WHERE sen.row = (ABS(CHECKSUM(NEWID())) % 100) + 1

Вот быстрое решение вашей проблемы.

SELECT * FROM table ORDER BY `id` DESC LIMIT N, 1

Здесь вы можете получить последнюю строку, заполнив N = 0, вторую последнюю - N = 1, четвертую последнюю, заполнив N = 3 и так далее.

Это очень частый вопрос во время интервью, и это очень простой ответ.

Далее, если вам нужна сумма, идентификатор или некоторый числовой порядок сортировки, вы можете использовать функцию CAST в MySQL.

SELECT DISTINCT (`amount`) FROM cart ORDER BY CAST( `amount` AS SIGNED ) DESC LIMIT 4 , 1

Здесь, заполнив N = 4, вы сможете получить пятую последнюю запись наибольшей суммы из таблицы CART. Вы можете подобрать свое поле и имя таблицы и придумать решение.

SQL СЕРВЕР


Выбрать n-ю запись сверху

SELECT * FROM (
SELECT 
ID, NAME, ROW_NUMBER() OVER(ORDER BY ID) AS ROW
FROM TABLE 
) AS TMP 
WHERE ROW = n

выберите n-ю запись снизу

SELECT * FROM (
SELECT 
ID, NAME, ROW_NUMBER() OVER(ORDER BY ID DESC) AS ROW
FROM TABLE 
) AS TMP 
WHERE ROW = n

Проверьте это на SQL Server:

Select top 10 * From emp 
EXCEPT
Select top 9 * From emp

Это даст вам 10-й РЯД таблицы emp!

Вот как я бы сделал это в DB2 SQL, я считаю, что RRN (относительный номер записи) хранится в таблице оператором / оператором;

SELECT * FROM (                        
   SELECT RRN(FOO) AS RRN, FOO.*
   FROM FOO                         
   ORDER BY RRN(FOO)) BAR             
 WHERE BAR.RRN = recordnumber

Мне кажется, что для эффективности вам нужно 1) сгенерировать случайное число от 0 до одного меньше, чем количество записей в базе данных, и 2) иметь возможность выбрать строку в этой позиции. К сожалению, разные базы данных имеют разные генераторы случайных чисел и разные способы выбора строки в позиции в наборе результатов - обычно вы указываете, сколько строк пропустить и сколько строк вы хотите, но для разных баз данных это делается по-разному. Вот что у меня работает в SQLite:

select * 
from Table 
limit abs(random()) % (select count(*) from Words), 1;

Это зависит от возможности использовать подзапрос в предложении limit (который в SQLite - LIMIT <recs to skip>, <recs to take>). Выбор количества записей в таблице должен быть особенно эффективным, поскольку он является частью базы данных. метаданные, но это зависит от реализации базы данных. Кроме того, я не знаю, действительно ли запрос построит набор результатов перед извлечением N-й записи, но я надеюсь, что в этом нет необходимости. Обратите внимание, что я не указываю предложение «порядок по». Возможно, лучше "упорядочить" что-то вроде первичного ключа, который будет иметь индекс - получение N-й записи из индекса может быть быстрее, если база данных не может получить N-ю запись из самой базы данных без построения набора результатов. .

В Oracle 12c вы можете использовать OFFSET..FETCH..ROWS опцию с ORDER BY

Например, чтобы получить третью запись сверху:

SELECT * 
FROM   sometable
ORDER BY column_name
OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY;

Если вы хотите посмотреть на собственные функции: MySQL, PostgreSQL, SQLite и Oracle (в основном SQL Server, похоже, не имеет этой функции ), вы можете ДЕЙСТВИТЕЛЬНО использовать оконную функцию NTH_VALUE. Источник Oracle: Функции Oracle: NTH_VALUE

Я действительно экспериментировал с этим в нашей базе данных Oracle, чтобы провести некоторое сравнение первой строки (после заказа) со второй строкой (опять же, после заказа). Код будет выглядеть примерно так (если вы не хотите переходить по ссылке):

SELECT DISTINCT dept_id
     , NTH_VALUE(salary,2) OVER (PARTITION BY dept_id ORDER BY salary DESC
           RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) 
        AS "SECOND HIGHEST"
     , NTH_VALUE(salary,3) OVER (PARTITION BY dept_id ORDER BY salary DESC
           RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
        AS "THIRD HIGHEST"
  FROM employees
 WHERE dept_id in (10,20)
 ORDER 
    BY dept_id;

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

WITH r AS (
  SELECT TOP 1000 * FROM emp
)
SELECT * FROM r
EXCEPT
SELECT TOP 999 FROM r

Это даст 1000-ю строку в SQL Server.

select * from 
(select * from ordered order by order_id limit 100) x order by 
x.order_id desc limit 1;

Сначала выберите 100 первых строк в порядке возрастания, а затем выберите последнюю строку в порядке убывания и ограничьте до 1. Однако это очень дорогостоящий оператор, поскольку он обращается к данным дважды.

Наиболее подходящий ответ, который я видел в этой статье для sql server

WITH myTableWithRows AS (
    SELECT (ROW_NUMBER() OVER (ORDER BY myTable.SomeField)) as row,*
    FROM myTable)
SELECT * FROM myTableWithRows WHERE row = 3

Но на самом деле, разве все это не просто салонные уловки для хорошего дизайна базы данных? Несколько раз мне требовались такие функции, как этот простой одноразовый запрос для быстрого создания отчета. В любой реальной работе использование подобных приемов создает проблемы. Если требуется выбрать конкретную строку, просто создайте столбец с последовательным значением и покончите с этим.

PostgreSQL поддерживает оконные функции, определенные стандартом SQL, но они неудобны, поэтому большинство людей используют (нестандартный) LIMIT/OFFSET :

SELECT
    *
FROM
    mytable
ORDER BY
    somefield
LIMIT 1 OFFSET 20;

В этом примере выбирается 21-я строка. OFFSET 20 говорит Постгресу пропустить первые 20 записей. Если вы не укажете ORDER BY предложение, нет гарантии, какую запись вы получите обратно, что редко бывает полезным.

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

select top 1 field
from table
where field in (select top 5 field from table order by field asc)
order by field desc

Это даст 5-й элемент, измените второе верхнее число, чтобы получить другой n-й элемент

Только SQL-сервер (я думаю), но должен работать на более старых версиях, которые не поддерживают ROW_NUMBER ().

Я не уверен в остальном, но я знаю, что SQLite и MySQL не имеют «стандартного» порядка строк. По крайней мере, для этих двух диалектов следующий фрагмент берет 15-ю запись из the_table, сортируя ее по дате / времени добавления:

SELECT * FROM the_table ORDER BY added DESC LIMIT 1,15

(конечно, вам нужно будет добавить поле DATETIME и установить его на дату / время, когда была добавлена ​​запись ...)

Когда мы работали с MSSQL 2000, мы делали то, что мы называли «тройным переворотом»:

РЕДАКТИРОВАТЬ

DECLARE @InnerPageSize int
DECLARE @OuterPageSize int
DECLARE @Count int

SELECT @Count = COUNT(<column>) FROM <TABLE>
SET @InnerPageSize = @PageNum * @PageSize
SET @OuterPageSize = @Count - ((@PageNum - 1) * @PageSize)

IF (@OuterPageSize < 0)
    SET @OuterPageSize = 0
ELSE IF (@OuterPageSize > @PageSize)
    SET @OuterPageSize = @PageSize

DECLARE @sql NVARCHAR(8000)

SET @sql = 'SELECT * FROM
(
    SELECT TOP ' + CAST(@OuterPageSize AS nvarchar(5)) + ' * FROM
    (
        SELECT TOP ' + CAST(@InnerPageSize AS nvarchar(5)) + ' * FROM <TABLE> ORDER BY <column> ASC
    ) AS t1 ORDER BY <column> DESC
) AS t2 ORDER BY <column> ASC'

PRINT @sql
EXECUTE sp_executesql @sql

Это было не изящно и не быстро, но работало.

Вот общая версия sproc, которую я недавно написал для Oracle, которая позволяет динамическое разбиение на страницы / сортировку - HTH

-- p_LowerBound = first row # in the returned set; if second page of 10 rows,
--                this would be 11 (-1 for unbounded/not set)
-- p_UpperBound = last row # in the returned set; if second page of 10 rows,
--                this would be 20 (-1 for unbounded/not set)

OPEN o_Cursor FOR
SELECT * FROM (
SELECT
    Column1,
    Column2
    rownum AS rn
FROM
(
    SELECT
        tbl.Column1,
        tbl.column2
    FROM MyTable tbl
    WHERE
        tbl.Column1 = p_PKParam OR
        tbl.Column1 = -1
    ORDER BY
        DECODE(p_sortOrder, 'A', DECODE(p_sortColumn, 1, Column1, 'X'),'X'),
        DECODE(p_sortOrder, 'D', DECODE(p_sortColumn, 1, Column1, 'X'),'X') DESC,
        DECODE(p_sortOrder, 'A', DECODE(p_sortColumn, 2, Column2, sysdate),sysdate),
        DECODE(p_sortOrder, 'D', DECODE(p_sortColumn, 2, Column2, sysdate),sysdate) DESC
))
WHERE
    (rn >= p_lowerBound OR p_lowerBound = -1) AND
    (rn <= p_upperBound OR p_upperBound = -1);

Вопреки утверждениям некоторых ответов, стандарт SQL не молчит по этому поводу.

Начиная с SQL: 2003, вы можете использовать «оконные функции», чтобы пропускать строки и ограничивать наборы результатов.

А в SQL: 2008 был добавлен немного более простой подход с использованием
OFFSET skip ROWS FETCH FIRST n ROWS ONLY

Лично я не думаю, что добавление SQL: 2008 было действительно необходимо, поэтому, если бы я был ISO, я бы не включил его в уже довольно крупный стандарт.

Для SQL Server общий способ перехода по номеру строки таков:

SET ROWCOUNT @row [email protected] = the row number you wish to work on.

Например:

set rowcount 20   --sets row to 20th row

select meat, cheese from dbo.sandwich --select columns from table at 20th row

set rowcount 0   --sets rowcount back to all rows

Это вернет информацию о 20-й строке. Не забудьте после этого ввести rowcount 0.

В SQL 2005 и более поздних версиях эта функция встроена. Используйте функцию ROW_NUMBER (). Он отлично подходит для просмотра веб-страниц в стиле << Назад и Вперед >>:

Синтаксис:

SELECT
    *
FROM
    (
        SELECT
            ROW_NUMBER () OVER (ORDER BY MyColumnToOrderBy) AS RowNum,
            *
        FROM
            Table_1
    ) sub
WHERE
    RowNum = 23