4

这个问题源于关于在特定情况下是否使用 SQL 排名功能的讨论。

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

如果您只想显示大量记录中的一小部分,它们在提高性能方面做得很好。但是它们也引入了一个主要的陷阱:如果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

上面的查询现在返回什么?Ringo? George? 你说不出来。据我记得,例如 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建议),RANK以及DENSE_RANK为此目的)。


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

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

4

5 回答 5

3

rank并且row_number是应该更自由地使用的奇妙功能,IMO。人们只是不知道他们。

话虽如此,您需要确保您的排名依据是独一无二的。为重复项(尤其是日期)制定备份计划。您返回的数据仅与您输入的数据一样好。

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

select top 2 * from tblA order by date desc

您需要了解您所订购的商品,并确保有某种方法可以让您始终有赢家。如果没有,您会得到(可能)随机的两行,其中包含最大日期。

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

于 2009-08-20T11:28:56.143 回答
2

在上面的示例中使用 WITH TIES 子句

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 不同)

于 2009-08-20T12:02:04.000 回答
2

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

这些标识符是:

  • 行指针在MyISAM
  • InnoDB表中已PRIMARY KEY定义的主键
  • UniquifierInnoDB没有PRIMARY KEY定义的表中
  • RIDinSQL Server的堆表
  • 的表中的主键SQL Server聚集在PRIMARY/UNIQUE KEY
  • 索引键 + uniquifierinSQL Server的表聚集在非唯一键上
  • ROWID/UROWIDOracle
  • CTIDPostgreSQL.

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

  • 行指针在MyISAM
  • UniquifierInnoDB没有PRIMARY KEY定义的表中
  • RIDinSQL Server的堆表
  • 索引键 + uniquifierinSQL Server的表聚集在非唯一键上

此外,您无法控制以下各项:

  • ROWID/UROWIDOracle
  • CTIDPostgreSQL.

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

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

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

这只是意味着您应该始终包含某种您可以完全控制排序子句的唯一性,以保持您的排序一致。

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

SELECT  *
FROM    mytable
ORDER BY
        ordering_column, pk

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

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

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

PRIMARY KEY顺便说一句,这是在你的桌子上总是有 a 的另一个很好的理由。

但不要依赖ROWID/CTID来排序行。

它可以轻松更改,UPDATE因此您的结果顺序将不再稳定。

于 2009-08-20T15:06:21.403 回答
1

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

只知道你在做什么。

于 2009-08-20T11:36:18.173 回答
0

这是摘要:

  • 先用你的头。应该是显而易见的,但它始终是一个很好的起点。您是否期望n行数准确,或者您期望满足约束的行数可能不同?重新考虑你的设计。如果您n准确地期望行,那么如果无法明确地识别行,您的模型可能设计得不好。如果您预计行数可能会有所不同,则可能需要调整 UI 以显示查询结果。
  • 添加列key使其独一无二(例如PK)。您至少可以重新控制返回的结果。正如Quassnoi 指出的那样,几乎总有办法做到这一点。
  • 考虑使用可能更合适的函数RANK,如DENSE_RANKTOP n WITH TIES。它们在 Microsoft SQL Server 2005 版本和 PosgreSQL 8.4 以后的版本中可用。如果这些函数不可用,请考虑使用带有聚合的嵌套查询而不是排名函数。
于 2009-10-25T17:59:38.683 回答