应该将SQL排名功能视为“谨慎使用”

问题描述 投票:4回答:5

此问题源自有关是否在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您将找到许多建议使用排名功能的解决方案,而错过指出可能的问题的方法。

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

sql limit row-number
5个回答
3
投票

rankrow_number是很棒的功能,应该更自由地使用IMO。人们只是不了解他们。

话虽这么说,您需要确保所排名的是唯一的。为重复项(尤其是日期)制定备份计划。您取回的数据仅与您输入的数据一样好。

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

select top 2 * from tblA order by date desc

您需要了解所订购的商品,并确保有某种方式可以始终拥有赢家。如果没有,您将获得(可能是)带有最大日期的两行。

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


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
投票

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

这些标识符是:

  • MyISAM中的行指针
  • 定义了InnoDBPRIMARY KEY表中的主键
  • 未定义UniquifierInnoDB表中的[PRIMARY KEY
  • RID的堆表中的SQL Server
  • SQL Server的表中的主键聚集在PRIMARY/UNIQUE KEY
  • 索引键+ uniquifier表中的SQL Server聚集在非唯一键上
  • ROWID中的UROWID / Oracle
  • CTID中的[PostgreSQL

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

  • MyISAM中的行指针
  • 未定义UniquifierInnoDB表中的[PRIMARY KEY
  • RID的堆表中的SQL Server
  • 索引键+ uniquifier表中的SQL Server聚集在非唯一键上

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

  • ROWID中的UROWID / Oracle
  • CTID中的[PostgreSQL

(它们可以更改更新或从备份还原)

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

它们返回完全相同的结果,可以视为终极唯一化器。

这只是意味着您应该始终在顺序子句中包含完全控制的某种唯一化符,以保持顺序一致。

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

SELECT  *
FROM    mytable
ORDER BY
        ordering_column, pk

否则,将all列包括在排序条件中:

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

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

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

但是不要依赖ROWID / CTID对行进行排序。

它可以很容易地在UPDATE上进行更改,因此您的结果顺序将不再稳定。


1
投票

ROW_NUMBER确实是一个了不起的工具。如果使用不当,它可以提供不确定的结果,但是其他SQL函数也可以。您也可以让ORDER BY返回不确定的结果。

只知道您在做什么。


0
投票

这是摘要:

  • 首先使用头。应该很明显,但这始终是一个很好的起点。您是否确切期望n行,或者期望满足约束条件的行数可能有所不同?重新考虑您的设计。如果您确切地期望n行,那么在无法明确识别行的情况下,您的模型可能设计不佳。如果您期望行数可能有所变化,则可能需要调整UI才能显示查询结果。
  • key中添加使其唯一的列(例如PK)。您至少要获得对返回结果的控制权。 Quassnoi pointed out几乎总有一种方法可以做到这一点。
  • 考虑使用可能更合适的功能,例如RANKDENSE_RANKTOP n WITH TIES。它们在Microsoft SQL Server 2005版本和8.4起的PosgreSQL中可用。如果这些功能不可用,请考虑对聚合使用嵌套查询,而不是对功能进行排名。
© www.soinside.com 2019 - 2024. All rights reserved.