此问题源于是否在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
你会发现许多解决方案,建议使用排名功能,并错过指出可能存在的问题。
问题:如果提出包含排名功能的解决方案,应该给出什么建议?
答案 0 :(得分:3)
rank
和row_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
已定义Uniquifier
在InnoDB
表中没有定义PRIMARY KEY
RID
在SQL Server
的堆表SQL Server
PRIMARY/UNIQUE KEY
表中的主键
uniquifier
表中的索引键+ SQL Server
聚集在非唯一键上ROWID
中UROWID
/ Oracle
{li> CTID
PostgreSQL
。您无法立即访问以下内容:
MyISAM
Uniquifier
在InnoDB
表中没有定义PRIMARY KEY
RID
在SQL 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。RANK
,DENSE_RANK
和TOP n WITH TIES
。它们在2005年版本的Microsoft SQL Server和8.4之后的PosgreSQL中可用。如果这些函数不可用,请考虑使用嵌套查询和聚合而不是排名函数。