表:
UserId, Value, Date.
我想得到UserId,每个UserId的max(Date)值.也就是说,具有最新日期的每个UserId的值.有没有办法在SQL中执行此操作?(最好是Oracle)
更新:对任何含糊不清的道歉:我需要获取所有UserIds.但对于每个UserId,只有该用户具有最新日期的那一行.
我看到许多人使用子查询或特定于供应商的功能来执行此操作,但我经常以下列方式执行此类查询而不使用子查询.它使用普通的标准SQL,因此它应该适用于任何品牌的RDBMS.
SELECT t1.* FROM mytable t1 LEFT OUTER JOIN mytable t2 ON (t1.UserId = t2.UserId AND t1."Date" < t2."Date") WHERE t2.UserId IS NULL;
换句话说:从t1
没有其他行的位置获取具有相同UserId
和更大日期的行.
(我将标识符"Date"放在分隔符中,因为它是一个SQL保留字.)
万一,如果t1."Date" = t2."Date"
出现加倍.通常表有auto_inc(seq)
关键,例如id
.为避免倍增可以使用如下:
SELECT t1.* FROM mytable t1 LEFT OUTER JOIN mytable t2 ON t1.UserId = t2.UserId AND ((t1."Date" < t2."Date") OR (t1."Date" = t2."Date" AND t1.id < t2.id)) WHERE t2.UserId IS NULL;
来自@Farhan的评论:
这是一个更详细的解释:
外部连接试图加入t1
带t2
.默认情况下,t1
返回所有结果,如果匹配t2
,则返回.如果t2
给定行没有匹配t1
,则查询仍返回行的行t1
,并NULL
用作所有t2
列的占位符.这就是外连接的工作原理.
此查询中的技巧是设计连接的匹配条件,使其t2
必须匹配相同的条件 userid
,并且更大 date
.这个想法是,如果一行中存在t2
一个更大date
的行,那么t1
它与之比较的行不能是最大date
的userid
.但是如果没有匹配 - 即如果没有行存在t2
大于date
行t1
- 我们知道行中t1
的行date
是给定的最大行userid
.
在那些情况下(当没有匹配时),列t2
将是NULL
- 甚至是连接条件中指定的列.所以这就是为什么我们使用WHERE t2.UserId IS NULL
,因为我们正在寻找在没有行被发现具有较大的情况下,date
对于给定的userid
.
这将检索my_date列值等于该userid的my_date的最大值的所有行.这可以为userid检索多行,其中最大日期在多行上.
select userid, my_date, ... from ( select userid, my_date, ... max(my_date) over (partition by userid) max_my_date from users ) where my_date = max_my_date
"分析功能摇滚"
编辑:关于第一条评论......
"使用分析查询和自连接会破坏分析查询的目的"
此代码中没有自联接.而是在内联视图的结果上放置了一个谓词,其中包含分析函数 - 一个非常不同的问题,以及完全标准的实践.
"Oracle中的默认窗口是从分区的第一行到当前的一行"
windowing子句仅适用于order by子句的存在.如果没有order by子句,则默认情况下不应用windowing子句,并且不能显式指定任何窗口子句.
代码有效.
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC) FROM table GROUP BY userid
我不知道你确切的列名,但它会是这样的:
select userid, value from users u1 where date = (select max(date) from users u2 where u1.userid = u2.userid)
没有工作,我没有掌握Oracle,但我似乎记得Oracle允许在IN子句中匹配多个列,这至少应该避免使用相关子查询的选项,这很少是一个好的理念.
也许这样的事情(不记得列列表是否应该加括号):
SELECT * FROM MyTable WHERE (User, Date) IN ( SELECT User, MAX(Date) FROM MyTable GROUP BY User)
编辑:刚尝试真实:
SQL> create table MyTable (usr char(1), dt date); SQL> insert into mytable values ('A','01-JAN-2009'); SQL> insert into mytable values ('B','01-JAN-2009'); SQL> insert into mytable values ('A', '31-DEC-2008'); SQL> insert into mytable values ('B', '31-DEC-2008'); SQL> select usr, dt from mytable 2 where (usr, dt) in 3 ( select usr, max(dt) from mytable group by usr) 4 / U DT - --------- A 01-JAN-09 B 01-JAN-09
所以它有效,尽管其他地方提到的一些新东西可能更有效.
我知道你要求Oracle,但在SQL 2005中我们现在使用它:
-- Single Value ;WITH ByDate AS ( SELECT UserId, Value, ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) RowNum FROM UserDates ) SELECT UserId, Value FROM ByDate WHERE RowNum = 1 -- Multiple values where dates match ;WITH ByDate AS ( SELECT UserId, Value, RANK() OVER (PARTITION BY UserId ORDER BY Date DESC) Rnk FROM UserDates ) SELECT UserId, Value FROM ByDate WHERE Rnk = 1
我没有Oracle来测试它,但最有效的解决方案是使用分析查询.它应该看起来像这样:
SELECT DISTINCT UserId , MaxValue FROM ( SELECT UserId , FIRST (Value) Over ( PARTITION BY UserId ORDER BY Date DESC ) MaxValue FROM SomeTable )
我怀疑你可以摆脱外部查询,并在内部区分,但我不确定.与此同时,我知道这个有效.
如果您想了解分析查询,我建议您阅读http://www.orafaq.com/node/55和http://www.akadia.com/services/ora_analytic_functions.html.这是简短的总结.
在引擎盖下,分析查询对整个数据集进行排序,然后按顺序处理它.在处理它时,您根据特定条件对数据集进行分区,然后为每一行查看某个窗口(默认为分区中第一个到当前行的值 - 默认值也是最有效的)并且可以使用分析函数的数量(其列表与聚合函数非常相似).
在这种情况下,这是内部查询的作用.整个数据集按UserId排序,然后按Date DESC排序.然后它一次处理它.对于每一行,您返回UserId和该UserId看到的第一个日期(因为日期是DESC排序的,即最大日期).这为您提供了重复行的答案.然后外部DISTINCT压缩重复.
这不是分析查询的一个特别壮观的例子.要获得更大的胜利,可以考虑为每个用户和收据计算一份财务收据表,计算他们支付的总额.分析查询可以有效地解决问题.其他解决方案效率较低.这就是为什么它们是2003 SQL标准的一部分.(不幸的是Postgres还没有它们.Grrr ......)
QUALIFY条款不是最简单也最好的吗?
select userid, my_date, ... from users qualify rank() over (partition by userid order by my_date desc) = 1
对于上下文,在Teradata上这里有一个不错的尺寸测试,在17秒内运行此QUALIFY版本,在23秒内运行'内联视图'/ Aldridge解决方案#1.
使用PostgreSQL 8.4或更高版本,您可以使用:
select user_id, user_value_1, user_value_2 from (select user_id, user_value_1, user_value_2, row_number() over (partition by user_id order by user_date desc) from users) as r where r.row_number=1
在Oracle 12c+
,你可以使用前n个查询和分析函数rank
来实现这个非常简洁,没有子查询:
select * from your_table order by rank() over (partition by user_id order by my_date desc) fetch first 1 row with ties;
以上内容返回每个用户最大my_date的所有行.
如果你想只有一排,最大日期,然后更换rank
有row_number
:
select * from your_table order by row_number() over (partition by user_id order by my_date desc) fetch first 1 row with ties;