【问题标题】:Emulate MySQL LIMIT clause in Microsoft SQL Server 2000在 Microsoft SQL Server 2000 中模拟 MySQL LIMIT 子句
【发布时间】:2010-09-18 00:24:00
【问题描述】:

当我处理Zend Framework's database component 时,我们试图抽象出MySQL、PostgreSQL 和SQLite 支持的LIMIT 子句的功能。也就是说,可以这样创建查询:

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

但是,Microsoft SQL Server 2000 没有ROW_NUMBER() 函数。

所以我的问题是,你能想出一种方法来模拟 Microsoft SQL Server 2000 中的LIMIT 功能,只使用 SQL 吗?不使用游标或 T-SQL 或存储过程。它必须支持LIMIT 的两个参数,包括计数和偏移量。使用临时表的解决方案也是不可接受的。

编辑:

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“页面”。基本上,此解决方案有效,除非您需要结果集的最后一个“页面”,而该“页面”恰好不是页面大小的倍数。

编辑:

另一种解决方案效果更好,但前提是您可以假设结果集包含唯一的列:

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

结论:

似乎不存在用于在 MS SQL Server 2000 中模拟 LIMIT 的通用解决方案。如果您可以在 MS SQL Server 2005 中使用 ROW_NUMBER() 函数,则存在一个很好的解决方案。

【问题讨论】:

    标签: sql mysql sql-server zend-framework


    【解决方案1】:
    SELECT TOP n *
    FROM tablename
    WHERE key NOT IN (
        SELECT TOP x key
        FROM tablename
        ORDER BY key
        DESC
    );
    

    【讨论】:

    • 是的,这很接近,但只有在临时查询结果中有唯一键时才有效。您将如何在 GROUP BY 查询或连接多个表的查询中执行此操作?
    【解决方案2】:

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

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

    【讨论】:

    • msdn.microsoft.com/en-us/library/bb386988.aspx 说:“SQL Server 2000 和 SQL Server 2005 的翻译是不同的。如果您计划使用 Skip(Of TSource) 处理任何复杂的查询,请使用 SQL Server 2005。”
    • 我的意思是,如果先决条件是运行 MS SQL Server 2005,则有很多很好的解决方案可以解决这个问题。但在 MS SQL Server 2000 上,它们中的一般情况下工作的很少。诚然,它是现在是 2011 年,随着时间的推移,越来越难以证明运行 MS SQL Server 2000 的合理性。然而有些人仍然这样做! :(
    【解决方案3】:

    这是另一个仅适用于 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
    

    【讨论】:

    • 谢谢,看起来它在 Microsoft SQL Server 2005 中也可以工作。它仍然让我们没有很好的 Microsoft SQL Server 2000 解决方案。但我想既然是 2009 年,放弃支持终于是合理的了反正2000年。
    【解决方案4】:

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

    【讨论】:

    • 我同意您在设计应用程序时的建议。就我而言,我正在设计一个数据库访问框架,它应该具有一致的 API,但会根据不同品牌的数据库的需要生成不同的 SQL。解决方案必须在 SQL 准备中,而不是在获取中。
    • 好主意,非常感谢。总的来说,我认为您的解决方案更可行。
    • 当您在前几页时,此解决方案的问题并不那么明显。如果您需要转到 10,000 页的最后一页并且每行有 8k 或更多,该怎么办?我不确定 mssql_data_seek 如何可能阻止 SQL Server 选择所有这些行(即使它们最终不是由客户端呈现)。如果这是您计划使用的解决方案,我强烈建议您测试所有边缘和最坏情况。
    猜你喜欢
    • 2012-02-19
    • 1970-01-01
    • 2011-09-23
    • 2011-06-24
    相关资源
    最近更新 更多