表:
UserId, Value, Date.
我想获得UserId,为每个UserId的最大值(日期)的值。也就是说,具有最新日期的每个UserId的值。有没有一种方法可以在SQL中简单地做到这一点?(最好是Oracle)
更新:为任何歧义道歉:我需要得到所有的用户id。但是对于每个UserId,只有该用户拥有最新日期的行。
表:
UserId, Value, Date.
我想获得UserId,为每个UserId的最大值(日期)的值。也就是说,具有最新日期的每个UserId的值。有没有一种方法可以在SQL中简单地做到这一点?(最好是Oracle)
更新:为任何歧义道歉:我需要得到所有的用户id。但是对于每个UserId,只有该用户拥有最新日期的行。
当前回答
难道一个qualified子句不是既简单又最好吗?
select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1
对于上下文,在Teradata这里一个像样的大小测试运行在17秒与这个合格版本和在23秒与“内联视图”/Aldridge解决方案#1。
其他回答
我看到许多人使用子查询或其他窗口函数来执行此操作,但我经常以以下方式执行这种不带子查询的查询。它使用普通的标准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的行。
(我把标识符“Date”放在分隔符中,因为它是一个SQL保留字。)
如果是t1。Date = t2。“日期”,加倍出现。通常表有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的行,并对t2的所有列使用NULL作为占位符。这就是外层连接的工作原理。
此查询中的技巧是设计联接的匹配条件,使t2必须匹配相同的用户id和更大的日期。它的意思是,如果t2中存在一个日期更大的行,那么t1中与它比较的行就不能是该userid的最大日期。但是如果没有匹配——也就是说,如果t2中不存在比t1中的行日期大的行——我们就知道t1中的行是给定userid中日期最大的行。
在这些情况下(当没有匹配时),t2的列将为NULL——即使是连接条件中指定的列。这就是为什么我们用WHERE t2。UserId IS NULL,因为我们正在搜索没有为给定的UserId找到日期更大的行的情况。
我没有Oracle来测试它,但最有效的解决方案是使用分析查询。它应该看起来像这样:
SELECT DISTINCT
UserId
, MaxValue
FROM (
SELECT UserId
, FIRST (Value) Over (
PARTITION BY UserId
ORDER BY Date DESC
) MaxValue
FROM SomeTable
)
我怀疑您可以摆脱外部查询,并在内部放置distinct,但我不确定。与此同时,我知道这招管用。
如果您想了解分析查询,我建议您阅读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还没有。叽阿…)
MySQL没有分区KEEP, DENSE_RANK概念的解决方案。
select userid,
my_date,
...
from
(
select @sno:= case when @pid<>userid then 0
else @sno+1
end as serialnumber,
@pid:=userid,
my_Date,
...
from users order by userid, my_date
) a
where a.serialnumber=0
参考:http://benincampus.blogspot.com/2013/08/select-rows-which-have-maxmin-value-in.html
我已经很晚了,但下面的黑客将超越相关子查询和任何分析功能,但有一个限制:值必须转换为字符串。所以它适用于日期,数字和其他字符串。代码看起来不太好,但执行配置文件很棒。
select
userid,
to_number(substr(max(to_char(date,'yyyymmdd') || to_char(value)), 9)) as value,
max(date) as date
from
users
group by
userid
这段代码运行良好的原因是它只需要扫描表一次。它不需要任何索引,最重要的是,它不需要像大多数分析函数那样对表进行排序。如果您需要为单个用户id过滤结果,索引将有所帮助。
只是需要在工作中写一个“活”的例子:)
它支持在同一日期为UserId设置多个值。
列: 用户id,值,日期
SELECT
DISTINCT UserId,
MAX(Date) OVER (PARTITION BY UserId ORDER BY Date DESC),
MAX(Values) OVER (PARTITION BY UserId ORDER BY Date DESC)
FROM
(
SELECT UserId, Date, SUM(Value) As Values
FROM <<table_name>>
GROUP BY UserId, Date
)
您可以使用FIRST_VALUE而不是MAX,并在解释计划中查找它。我没有时间玩它。
当然,如果搜索巨大的表,在查询中使用FULL提示可能会更好。