39

When I worked on the Zend Framework's database component, we tried to abstract the functionality of the LIMIT clause supported by MySQL, PostgreSQL, and SQLite. That is, creating a query could be done this way:

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

When the database supports LIMIT, this produces an SQL query like the following:

SELECT * FROM mytable ORDER BY somecolumn LIMIT 10, 20

This was more complex for brands of database that don't support LIMIT (that clause is not part of the standard SQL language, by the way). If you can generate row numbers, make the whole query a derived table, and in the outer query use BETWEEN. This was the solution for Oracle and IBM DB2. Microsoft SQL Server 2005 has a similar row-number function, so one can write the query this way:

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 @offset+@count;

However, Microsoft SQL Server 2000 doesn't have the ROW_NUMBER() function.

So my question is, can you come up with a way to emulate the LIMIT functionality in Microsoft SQL Server 2000, solely using SQL? Without using cursors or T-SQL or a stored procedure. It has to support both arguments for LIMIT, both count and offset. Solutions using a temporary table are also not acceptable.

Edit:

The most common solution for MS SQL Server 2000 seems to be like the one below, for example to get rows 50 through 75:

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

However, this doesn't work if the total result set is, say 60 rows. The inner query returns 60 rows because that's in the top 75. Then the outer query returns rows 35-60, which doesn't fit in the desired "page" of 50-75. Basically, this solution works unless you need the last "page" of a result set that doesn't happen to be a multiple of the page size.

Edit:

Another solution works better, but only if you can assume the result set includes a column that is unique:

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

Conclusion:

No general-purpose solution seems to exist for emulating LIMIT in MS SQL Server 2000. A good solution exists if you can use the ROW_NUMBER() function in MS SQL Server 2005.

4

4 回答 4

5
SELECT TOP n *
FROM tablename
WHERE key NOT IN (
    SELECT TOP x key
    FROM tablename
    ORDER BY key
    DESC
);
于 2009-04-06T04:28:26.350 回答
5

这是另一个仅适用于 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
于 2009-06-23T13:07:47.720 回答
4

当您只需要 LIMIT 时,ms sql 具有等效的 TOP 关键字,因此很清楚。当您需要 LIMIT 和 OFFSET 时,您可以尝试之前描述的一些技巧,但它们都会增加一些开销,即订购一种方式,然后订购另一种方式,或者昂贵的 NOT IN 操作。我认为不需要所有这些级联。在我看来,最干净的解决方案是在 SQL 端使用没有偏移量的 TOP,然后使用适当的客户端方法(如 php 中的 mssql_data_seek)查找所需的起始记录。虽然这不是一个纯粹的 SQL 解决方案,但我认为它是最好的解决方案,因为它不会增加任何开销(跳过的记录在您搜索它们时不会在网络上传输,如果这让您担心的话)。

于 2009-05-13T10:06:24.557 回答
0

我会尝试在我的 ORM 中实现它,因为它在那里非常简单。如果它确实需要在 SQL Server 中,那么我会查看 linq to sql 为以下 linq to sql 语句生成的代码,然后从那里开始。实现该代码的 MSFT 工程师多年来一直是 SQL 团队的一员,并且知道自己在做什么。

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

于 2011-06-22T17:37:56.973 回答