SQL排名功能是否应被视为“谨慎使用”

时间:2009-08-20 10:44:54

标签: sql limit row-number

此问题源于是否在particular case中使用SQL排名功能的讨论。

任何常见的RDBMS都包含一些排名功能,即它的查询语言包含TOP n ... ORDER BY keyROW_NUMBER() OVER (ORDER BY key)ORDER BY key LIMIT noverview)等元素。

如果您只想从大量记录中提供一小块,那么它们在提高性能方面做得很好。但它们也带来了一个重大缺陷:如果key不是唯一的,那么结果是不确定的。请考虑以下示例:


users

user_id name
1       John
2       Paul
3       George
4       Ringo

logins

login_id user_id login_date
1        4       2009-08-17
2        1       2009-08-18
3        2       2009-08-19
4        3       2009-08-20

查询应该返回最后登录的人:

SELECT TOP 1 users.*
FROM
  logins JOIN
  users ON logins.user_id = users.user_id
ORDER BY logins.login_date DESC

正如预期的那样George被返回,一切看起来都很好。但随后将新记录插入logins表:

1        4       2009-08-17
2        1       2009-08-18
3        2       2009-08-19
4        3       2009-08-20
5        4       2009-08-20

上面的查询现在返回什么? RingoGeorge?你不能说。据我所记,例如MySQL 4.1返回物理创建的第一条符合条件的记录,即结果为George。但这可能因版本而异,也可能因DBMS而异。应该归还什么?有人可能会说Ringo,因为他显然是最后登录的,但这是纯粹的解释。在我看来,两者都应该被退回,因为你无法明确地从可用数据中做出决定。

因此,此查询符合要求:

SELECT users.*
FROM
  logins JOIN
  users ON
    logins.user_id = users.user_id AND
    logins.login_date = (
      SELECT max(logins.login_date)
      FROM
        logins JOIN
        users ON logins.user_id = users.user_id)

作为替代方案,某些DBMS提供了特殊功能(例如,Microsoft SQL Server 2005为此目的引入了TOP n WITH TIES ... ORDER BY key(由gbn建议),RANKDENSE_RANK


如果您搜索SO,例如ROW_NUMBER你会发现许多解决方案,建议使用排名功能,并错过指出可能存在的问题。

问题:如果提出包含排名功能的解决方案,应该给出什么建议?

5 个答案:

答案 0 :(得分:3)

{p> rankrow_number是非常棒的功能,应该更自由地使用,IMO。人们只是不知道他们。

话虽如此,你需要确保你所排名的是独一无二的。有重复的备份计划(尤其是日期)。您获得的数据与您输入的数据一样好。

我认为这里的陷阱在查询中完全相同:

select top 2 * from tblA order by date desc

您需要了解您的订购情况,并确保有一些方法可以永远赢得胜利。如果没有,您将得到一个(可能)随机的两行,其中包含最大日期。

此外,对于记录,SQL Server不会按插入的物理顺序存储行。它将记录存储在8k页面上,并根据表格上的聚簇索引以最有效的方式对这些页面进行排序。因此,SQL Server中绝对不能保证顺序。

答案 1 :(得分:2)

Use the WITH TIES clause in your example above

SELECT TOP 1 WITH TIES users.*
FROM
  logins JOIN
  users ON logins.user_id = users.user_id
ORDER BY logins.login_date DESC

如您所述使用DENSE_RANK

不要把自己放在这个位置 示例:存储时间(日期时间)并在相同的3.33毫秒瞬间接受非常罕见重复的极低风险(SQL 2008不同)

答案 2 :(得分:2)

每个数据库引擎都使用某种行标识符,以便区分两行。

这些标识符是:

  • MyISAM
  • 中的行指针
  • InnoDB表中的主键,其中PRIMARY KEY已定义
  • {li} UniquifierInnoDB表中没有定义PRIMARY KEY
  • RIDSQL Server的堆表
  • SQL Server
  • 中聚集的PRIMARY/UNIQUE KEY表中的主键
  • uniquifier表中的索引键+ SQL Server聚集在非唯一键上
  • ROWID
  • UROWID / Oracle {li> CTID PostgreSQL

您无法立即访问以下内容:

  • MyISAM
  • 中的行指针 {li} UniquifierInnoDB表中没有定义PRIMARY KEY
  • RIDSQL Server的堆表
  • uniquifier表中的索引键+ SQL Server聚集在非唯一键上

此外,您无法控制以下内容:

    ROWID
  • UROWID / Oracle {li> CTID PostgreSQL

(他们可以更新或从备份恢复)

如果这两个表在这些表中相同,那意味着它们应该从应用程序的角度来看是相同的。

它们返回完全相同的结果,可以被视为最终的无统一者。

这只是意味着你应该总是包含一些你可以完全控制到排序子句的uniquifier,以保持你的顺序一致。

如果您的表具有主键或唯一键(甚至是复合键),请将其包含在订购条件中:

SELECT  *
FROM    mytable
ORDER BY
        ordering_column, pk

否则,请在排序条件中包含所有列:

SELECT  *
FROM    mytable
ORDER BY
        ordering_column, column1, ..., columnN

后面的条件将始终返回任何其他无法区分的行,但由于它们无论如何都无法区分,因此从应用程序的角度来看它看起来是一致的。

顺便说一下,这是在表格中始终有PRIMARY KEY的另一个好理由。

但不要依赖ROWID / CTID订购行。

它可以在UPDATE上轻松更改,因此您的结果订单将不再稳定。

答案 3 :(得分:1)

ROW_NUMBER确实是一个很棒的工具。如果误用,它可以提供非确定性结果,但其他SQL函数也是如此。您也可以让ORDER BY返回非确定性结果。

只要知道你在做什么。

答案 4 :(得分:0)

这是摘要:

  • 先用你的头。应该是显而易见的,但始终是一个好点。您是否准确地期望n行,或者您是否期望可能有不同数量的行满足约束?重新考虑你的设计。如果您准确地期望n行,那么如果无法明确地识别行,则您的模型可能设计得很差。如果您希望行数可能不同,则可能需要调整UI以显示查询结果。
  • key添加使其唯一的列(例如PK)。您至少可以获得对返回结果的控制权。几乎总有一种方法可以做Quassnoi pointed out
  • 考虑使用可能更合适的功能,例如RANKDENSE_RANKTOP n WITH TIES。它们在2005年版本的Microsoft SQL Server和8.4之后的PosgreSQL中可用。如果这些函数不可用,请考虑使用嵌套查询和聚合而不是排名函数。