Эмуляция предложения MySQL LIMIT в Microsoft SQL Server 2000

Когда я работал над компонентом базы данных Zend Framework, мы попытались абстрагировать функциональность предложения LIMIT, поддерживаемого MySQL, PostgreSQL, и SQLite. То есть создание запроса может быть выполнено следующим образом:

$select = $db->select();
$select->from('mytable');
$select->order('somecolumn');
$select->limit(10, 20);

Когда база данных поддерживает LIMIT, это создает SQL-запрос, например:

SELECT * FROM mytable ORDER BY somecolumn LIMIT 10, 20

Это было более сложным для брендов базы данных, которые не поддерживают LIMIT (это предложение, кстати, не является частью стандартного языка SQL). Если вы можете сгенерировать номера строк, сделайте весь запрос производной таблицей, а во внешнем запросе используйте BETWEEN. Это было решение для Oracle и IBM DB2. Microsoft SQL Server 2005 имеет аналогичную функцию номера строки, поэтому можно написать запрос таким образом:

SELECT z2.*
FROM (
    SELECT ROW_NUMBER OVER(ORDER BY id) AS zend_db_rownum, z1.*
    FROM ( ...original SQL query... ) z1
) z2
WHERE z2.zend_db_rownum BETWEEN @offset+1 AND @[email protected];

Однако Microsoft SQL Server 2000 не имеет функции ROW_NUMBER().

Итак, мой вопрос: можете ли вы придумать способ эмулировать функциональность LIMIT в Microsoft SQL Server 2000, используя только SQL? Без использования курсоров или T-SQL или хранимой процедуры. Он должен поддерживать оба аргумента для LIMIT, как count, так и offset. Решения, использующие временную таблицу, также неприемлемы.

Edit:

Наиболее распространенное решение для MS SQL Server 2000 похоже на приведенное ниже, например, для получения строк с 50 по 75:

SELECT TOP 25 *
FROM ( 
  SELECT TOP 75 *
  FROM   table 
  ORDER BY BY field ASC
) a 
ORDER BY field DESC;

Однако это не работает, если общий набор результатов, скажем, 60 строк. Внутренний запрос возвращает 60 строк, потому что в верхнем 75. Затем внешний запрос возвращает строки 35-60, которые не соответствуют желаемой "странице" 50-75. В принципе, это решение работает, если вам не нужна последняя "страница" набора результатов, которая не будет кратной размеру страницы.

Edit:

Другое решение работает лучше, но только если вы можете предположить, что набор результатов включает уникальный столбец:

SELECT TOP n *
FROM tablename
WHERE key NOT IN (
    SELECT TOP x key
    FROM tablename
    ORDER BY key
);

Вывод:

В MS SQL Server 2000 не существует универсального решения для эмуляции LIMIT. Хорошее решение существует, если вы можете использовать функцию ROW_NUMBER() в MS SQL Server 2005.

Ответ 1

Вот еще одно решение, которое работает только в Sql Server 2005 и новее, потому что оно использует оператор except. Но я все равно разделяю. Если вы хотите получить записи 50 - 75, напишите:

select * from (
    SELECT top 75 COL1, COL2
    FROM MYTABLE order by COL3
) as foo
except
select * from (
    SELECT top 50 COL1, COL2
    FROM MYTABLE order by COL3
) as bar

Ответ 2

SELECT TOP n *
FROM tablename
WHERE key NOT IN (
    SELECT TOP x key
    FROM tablename
    ORDER BY key
    DESC
);

Ответ 3

Когда вам нужен только LIMIT, ms sql имеет эквивалентное ключевое слово TOP, так что это понятно. Когда вам нужен LIMIT с OFFSET, вы можете попробовать некоторые хаки, как описано выше, но все они добавляют некоторые накладные расходы, то есть для упорядочивания в одну сторону, а затем другую, или в расчётную операцию NOT IN. Я думаю, что все эти каскады не нужны. Самое чистое решение в моем оппионе будет просто использовать TOP без смещения на стороне SQL, а затем искать необходимую начальную запись с помощью соответствующего клиентского метода, например mssql_data_seek в php. Хотя это не чистое решение для SQL, я думаю, что это лучший вариант, потому что он не добавляет никаких накладных расходов (пропущенные записи не будут передаваться в сети, когда вы будете искать их, если это вас беспокоит).

Ответ 4

Я попытался бы реализовать это в своем ORM, поскольку там довольно просто. Если это действительно нужно в SQL Server, я бы посмотрел код, сгенерированный linq на sql, для следующего выражения linq to sql и оттуда. Инженер MSFT, который реализовал этот код, был частью команды SQL в течение многих лет и знал, что он делает.

var result = myDataContext.mytable.Skip(pageIndex * pageSize).Take(pageSize)