此问题源自有关是否在particular case中使用SQL排名功能的讨论。
任何常见的RDBMS都包含一些排名功能,即其查询语言具有TOP n ... ORDER BY key
,ROW_NUMBER() OVER (ORDER BY key)
或ORDER BY key LIMIT n
(overview)之类的元素。
如果您只想显示大量记录中的一小部分,它们在提高性能方面做得很好。但是,它们也带来了一个重大陷阱:如果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
您将找到许多建议使用排名功能的解决方案,而错过指出可能的问题的方法。
问题:如果提出了包含排名功能的解决方案,应该提供什么建议?
rank
和row_number
是很棒的功能,应该更自由地使用IMO。人们只是不了解他们。
话虽这么说,您需要确保所排名的是唯一的。为重复项(尤其是日期)制定备份计划。您取回的数据仅与您输入的数据一样好。
我认为这里的陷阱与查询中的陷阱完全相同:
select top 2 * from tblA order by date desc
您需要了解所订购的商品,并确保有某种方式可以始终拥有赢家。如果没有,您将获得(可能是)带有最大日期的两行。
此外,为了便于记录,SQL Server不会按插入的物理顺序存储行。它在8k页上存储记录,并根据表上的聚集索引以最有效的方式对这些页进行排序。因此,绝对不能保证SQL Server中的顺序。
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有所不同)
每个数据库引擎都使用某种行标识符,以便可以区分两行。
这些标识符是:
MyISAM
中的行指针InnoDB
的PRIMARY KEY
表中的主键Uniquifier
的InnoDB
表中的[PRIMARY KEY
RID
的堆表中的SQL Server
SQL Server
的表中的主键聚集在PRIMARY/UNIQUE KEY
上uniquifier
表中的SQL Server
聚集在非唯一键上ROWID
中的UROWID
/ Oracle
CTID
中的[PostgreSQL
。您无权立即访问以下内容:
MyISAM
中的行指针Uniquifier
的InnoDB
表中的[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
上进行更改,因此您的结果顺序将不再稳定。
ROW_NUMBER确实是一个了不起的工具。如果使用不当,它可以提供不确定的结果,但是其他SQL函数也可以。您也可以让ORDER BY返回不确定的结果。
只知道您在做什么。
这是摘要:
n
行,或者期望满足约束条件的行数可能有所不同?重新考虑您的设计。如果您确切地期望n
行,那么在无法明确识别行的情况下,您的模型可能设计不佳。如果您期望行数可能有所变化,则可能需要调整UI才能显示查询结果。key
中添加使其唯一的列(例如PK)。您至少要获得对返回结果的控制权。 Quassnoi pointed out几乎总有一种方法可以做到这一点。RANK
,DENSE_RANK
和TOP n WITH TIES
。它们在Microsoft SQL Server 2005版本和8.4起的PosgreSQL中可用。如果这些功能不可用,请考虑对聚合使用嵌套查询,而不是对功能进行排名。