获取具有列最大值的行


574

表:

UserId, Value, Date.

我想获取UserId,每个UserId的max(Date)值。即,具有最新日期的每个UserId的值。有没有办法在SQL中简单地做到这一点?(最好是Oracle)

更新:对于任何歧义,我们深表歉意:我需要获取所有UserIds。但是对于每个UserId,仅该用户具有最新日期的那一行。


21
如果有多个行具有特定用户ID的最大日期值怎么办?
David Aldridge

该表的关键字段是什么?
vamosrafa

下面比较了一些解决方案:sqlfiddle.com
#!

1
@DavidAldridge,该列可能是唯一的。
和平者

Answers:


397

这将检索其my_date列值等于该用户ID的my_date最大值的所有行。这可能会为用户ID检索多行,其中最大日期在多行上。

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子句,则默认情况下不应用任何窗口条款,并且不能明确指定任何子句。

该代码有效。


38
当将其应用于具有880万行的表时,在其他一些高度投票的答案中,此查询花费的时间仅为查询的一半。
德里克·马哈尔

4
有人在乎发布与MySQL等效的链接,如果有的话?
2015年

2
这个返回值不能重复吗?例如。如果两行具有相同的user_id和相同的日期(恰好是最大值)。
2016年

2
@jastr我认为这个问题已得到承认
David Aldridge

3
作为替代,MAX(...) OVER (...)您也可以使用ROW_NUMBER() OVER (...)(对于每组最高的n)或RANK() OVER (...)(对于每组最大的n)。
MT0

441

我看到许多人使用子查询或其他特定于供应商的功能来执行此操作,但是我经常通过以下方式在没有子查询的情况下进行此类查询。它使用普通的标准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的评论:

这是更详细的说明:

外部连接试图加入t1t2。默认情况下,t1将返回的所有结果,如果中存在匹配项t2,则也将返回它。如果没有匹配t2的给定行t1,则查询仍返回的行t1,并NULL用作所有t2列的占位符。通常,外部联接就是这样工作的。

此查询的诀窍是设计联接的匹配条件,以使其t2必须匹配相同的条件 userid,以及更大的条件 date。这个想法是如果存在一排t2,有一个更大的date,然后在排t1它反对相比,不能是最大date的那个userid。但是,如果没有匹配项(即,如果不存在比输入的行t2更大date的行),t1我们知道输入的行t1date给定行最大的行userid

在这些情况下(如果没有匹配项),的列t2将为NULL-甚至是联接条件中指定的列。这就是我们之所以使用它的原因WHERE t2.UserId IS NULL,因为我们正在寻找找不到date给定更大行的情况userid


7
哇,比尔 这是我见过的最有创意的解决方案。在我相当大的数据集上,它也表现出色。这肯定会击败我见过的许多其他解决方案,或者我自己尝试解决这一难题的尝试。
贾斯汀·诺埃尔

36
当应用于具有880万行的表时,此查询花费的时间几乎是接受的答案中的两倍。
德里克·马哈尔

16
@Derek:优化取决于RDBMS的品牌和版本,以及适当的索引,数据类型等的存在。
比尔Karwin

7
在MySQL上,这种查询似乎实际上导致它遍历表之间的笛卡尔联接的结果,从而导致O(n ^ 2)时间。相反,使用子查询方法将查询时间从2.0s减少到0.003s。YMMV。
杰西2012年

1
有没有一种方法可以使它适应日期最大日期小于或等于用户给定日期的行?例如,如果用户输入日期“ 23-OCT-2011”,并且该表包含“ 24-OCT-2011”,“ 22-OCT-2011”,“ 20-OCT-2011”的行,那么我想得到“ 22-OCT-2011”。正在抓挠我的头并阅读了此片段一段时间……
Cory Kendall 2012年

164
SELECT userid, MAX(value) KEEP (DENSE_RANK FIRST ORDER BY date DESC)
  FROM table
  GROUP BY userid

3
在我使用具有大量行的表的测试中,此解决方案花费的时间是公认答案中的两倍。
德里克·马哈尔

7
请显示您的测试
Rob van Wijk 2012年

我确认它比其他解决方案要快得多
tamersalama 2012年

5
麻烦的是它不会返回完整记录
Used_By_Already 2014年

@ user2067753不,它不返回完整记录。您可以在多个列上使用相同的MAX().. KEEP ..表达式,因此可以选择所需的所有列。但是,如果您想要大量的列并且更喜欢使用SELECT *,这是不方便的。
戴夫·科斯塔

51

我不知道您确切的列名称,但是可能是这样的:

    选择用户ID,值
      来自用户u1
     日期=(选择最大(日期)
                     来自用户u2
                    其中u1.userid = u2.userid)

3
史蒂夫,可能效率不高。
David Aldridge

7
您可能低估了Oracle查询优化器。
拉法尔Dowgird

3
一点也不。几乎可以肯定这将实现为具有嵌套循环联接的完整扫描以获取日期。您所谈论的逻辑io的数量是表中行数的4倍,并且对于非平凡的数据量感到恐惧。
David Aldridge

4
仅供参考,“效率不高,但有效”与“效率高,但效率不高”相同。我们什么时候放弃了将效率作为设计目标的?
David Aldridge

6
+1是因为当您的数据表的长度不是数百万行时,这是最容易理解的解决方案。当您有多个技能水平各不相同的开发人员来修改代码时,易懂性比那么重要的性能要重要得多。
n00b

35

没有工作,我没有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

这样就可以了,尽管其他地方提到的一些新事物可能更有效。


4
这在PostgreSQL上也很好用。我喜欢它的简单性和通用性–子查询说“这是我的标准”,外部查询说“这是我想看到的细节”。+1。
j_random_hacker 2010年

13

我知道您要求使用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

7

我没有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/55http://www.akadia.com/services/ora_analytic_functions.html。这是简短的摘要。

在底层,分析查询对整个数据集进行排序,然后按顺序对其进行处理。在处理数据时,您将根据某些条件对数据集进行分区,然后为每一行查看某个窗口(默认为分区到当前行的第一个值-该默认值也是最有效的),并且可以使用分析函数的数量(其列表与聚合函数非常相似)。

在这种情况下,这就是内部查询的作用。整个数据集按UserId排序,然后按Date DESC排序。然后一遍处理它。对于每一行,您都返回UserId和该UserId看到的第一个日期(因为日期按DESC排序,因此是最大日期)。这为您提供了重复行的答案。然后,外部DISTINCT壁球重复。

这不是解析查询的特别引人注目的示例。要获得更大的胜利,可以考虑制作一张财务收据表,并为每个用户和收据计算他们所支付的总金额。分析查询可以有效地解决这一问题。其他解决方案效率较低。这就是为什么它们成为2003 SQL标准的一部分。(不幸的是Postgres还没有它们。Grrr...)


您还需要返回日期值才能完全回答问题。如果这意味着另一个first_value子句,则建议该解决方案比应该的复杂得多,并且基于max(date)的分析方法更易读。
David Aldridge

问题陈述中没有提到返回日期。您可以通过添加另一个FIRST(Date)或仅通过查询Date并将外部查询更改为GROUP BY来实现。我会使用第一个,并期望优化器一次计算出两者。
user11318

“问题陈述没有说明返回日期”……是的,您是对的。抱歉。但是添加更多FIRST_VALUE子句将很快变得混乱。这是一个单一的窗口排序,但是如果您有20列要返回的那一行,那么您已经编写了很多代码来进行遍历。
David Aldridge

在我看来,这种解决方案对于单个用户ID具有多个具有最大日期和不同值的行的数据是不确定的。但是,问题多于答案。
David Aldridge

1
我同意这很痛苦。但是,SQL通常不是这样吗?没错,解决方案是不确定的。有多种处理关系的方法,有时每种方法都是您想要的。
user11318

6

QUALIFY子句既不是最简单的又是最好的?

select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1

就上下文而言,在此Teradata上使用此QUALIFY版本对此进行了不错的尺寸测试,对于“内联视图” / Aldridge解决方案#1在23s中进行了测试。


1
我认为这是最好的答案。但是,rank()在有联系的情况下,请谨慎使用该功能。您可能会得到不止一个rank=1row_number()如果您确实只希望返回一条记录,则最好使用。
cartbeforehorse 2012年

1
另外,请注意,该QUALIFY子句特定于Teradata。在Oracle中(至少),您必须使用WHERE包装的select语句上的子句嵌套查询和过滤器(我想,这可能会降低性能)。
cartbeforehorse 2012年

5

在中Oracle 12c+,您可以结合使用Top 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的所有行。

如果你想只有一排,最大日期,然后更换rankrow_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; 

5

使用ROW_NUMBER()分配唯一的排名上降序Date对每个UserId过滤器,然后到第一行的每个UserId(即,ROW_NUMBER= 1)。

SELECT UserId, Value, Date
FROM (SELECT UserId, Value, Date,
        ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) rn
      FROM users) u
WHERE rn = 1;

5

在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

3

我把shuold设为先前查询的变体:

SELECT UserId, Value FROM Users U1 WHERE 
Date = ( SELECT MAX(Date)    FROM Users where UserId = U1.UserId)

3
Select  
   UserID,  
   Value,  
   Date  
From  
   Table,  
   (  
      Select  
          UserID,  
          Max(Date) as MDate  
      From  
          Table  
      Group by  
          UserID  
    ) as subQuery  
Where  
   Table.UserID = subQuery.UserID and  
   Table.Date = subQuery.mDate  

3

只需在工作中写一个“实时”示例:)

该日期在同一日期支持UserId的多个值。

列:UserId,值,日期

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提示。


3
select VALUE from TABLE1 where TIME = 
   (select max(TIME) from TABLE1 where DATE= 
   (select max(DATE) from TABLE1 where CRITERIA=CRITERIA))

2

我认为是这样的。(原谅我任何语法错误;这时我已经习惯了使用HQL!)

编辑:也误解了问题!更正了查询...

SELECT UserId, Value
FROM Users AS user
WHERE Date = (
    SELECT MAX(Date)
    FROM Users AS maxtest
    WHERE maxtest.UserId = user.UserId
)

不符合“针对每个UserId”的条件
David Aldridge

哪里会失败?对于用户中的每个用户ID,将确保至少返回包含该用户ID的一行。还是我在某处缺少特殊情况?
jdmichal

2

(T-SQL)首先获取所有用户及其maxdate。与该表联接,以在maxdates上为用户找到相应的值。

create table users (userid int , value int , date datetime)
insert into users values (1, 1, '20010101')
insert into users values (1, 2, '20020101')
insert into users values (2, 1, '20010101')
insert into users values (2, 3, '20030101')

select T1.userid, T1.value, T1.date 
    from users T1,
    (select max(date) as maxdate, userid from users group by userid) T2    
    where T1.userid= T2.userid and T1.date = T2.maxdate

结果:

userid      value       date                                    
----------- ----------- -------------------------- 
2           3           2003-01-01 00:00:00.000
1           2           2002-01-01 00:00:00.000

2

答案只有Oracle。这是所有SQL的更复杂的答案:

谁拥有最佳的家庭作业总成绩(最大家庭作业分数)?

SELECT FIRST, LAST, SUM(POINTS) AS TOTAL
FROM STUDENTS S, RESULTS R
WHERE S.SID = R.SID AND R.CAT = 'H'
GROUP BY S.SID, FIRST, LAST
HAVING SUM(POINTS) >= ALL (SELECT SUM (POINTS)
FROM RESULTS
WHERE CAT = 'H'
GROUP BY SID)

还有一个更困难的示例,需要一些解释,而我没有时间atm:

提供2008年最流行的书(ISBN和书名),即2008年最常借用的书。

SELECT X.ISBN, X.title, X.loans
FROM (SELECT Book.ISBN, Book.title, count(Loan.dateTimeOut) AS loans
FROM CatalogEntry Book
LEFT JOIN BookOnShelf Copy
ON Book.bookId = Copy.bookId
LEFT JOIN (SELECT * FROM Loan WHERE YEAR(Loan.dateTimeOut) = 2008) Loan 
ON Copy.copyId = Loan.copyId
GROUP BY Book.title) X
HAVING loans >= ALL (SELECT count(Loan.dateTimeOut) AS loans
FROM CatalogEntry Book
LEFT JOIN BookOnShelf Copy
ON Book.bookId = Copy.bookId
LEFT JOIN (SELECT * FROM Loan WHERE YEAR(Loan.dateTimeOut) = 2008) Loan 
ON Copy.copyId = Loan.copyId
GROUP BY Book.title);

希望对大家有帮助。.::)

问候,Guus


接受的答案不是“仅Oracle”-它是标准SQL(许多DBMS支持)
a_horse_with_no_name 2014年

2

假定日期对于给定的用户ID是唯一的,下面是一些TSQL:

SELECT 
    UserTest.UserID, UserTest.Value
FROM UserTest
INNER JOIN
(
    SELECT UserID, MAX(Date) MaxDate
    FROM UserTest
    GROUP BY UserID
) Dates
ON UserTest.UserID = Dates.UserID
AND UserTest.Date = Dates.MaxDate 

2

我参加聚会的时间很晚,但是以下破解将胜过相关子查询和任何分析功能,但有一个限制:值必须转换为字符串。因此,它适用于日期,数字和其他字符串。代码看起来不好,但是执行配置文件很棒。

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的结果,索引将有所帮助。


与大多数应用程序相比,这是一个不错的执行计划,但是将所有这些技巧应用到多个领域将是乏味的,并且可能会不利于它。但非常有趣-谢谢。参见sqlfiddle.com/#!4/2749b5/23
Used_By_Already

没错,它可能变得乏味,这就是为什么仅在查询性能需要时才执行此操作的原因。ETL脚本通常就是这种情况。
aLevelOfIndirection

这非常好。使用LISTAGG做类似的事情,但是看起来很丑。postgres使用array_agg有更好的选择。看到我的回答了:)
Bruno Calza 2014年

1
select userid, value, date
  from thetable t1 ,
       ( select t2.userid, max(t2.date) date2 
           from thetable t2 
          group by t2.userid ) t3
 where t3.userid t1.userid and
       t3.date2 = t1.date

恕我直言,这有效。高温超导


1

我认为这应该工作吗?

Select
T1.UserId,
(Select Top 1 T2.Value From Table T2 Where T2.UserId = T1.UserId Order By Date Desc) As 'Value'
From
Table T1
Group By
T1.UserId
Order By
T1.UserId

1

首先尝试我误解了问题,然后再回答最高答案,这是一个完整的示例,具有正确的结果:

CREATE TABLE table_name (id int, the_value varchar(2), the_date datetime);

INSERT INTO table_name (id,the_value,the_date) VALUES(1 ,'a','1/1/2000');
INSERT INTO table_name (id,the_value,the_date) VALUES(1 ,'b','2/2/2002');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'c','1/1/2000');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'d','3/3/2003');
INSERT INTO table_name (id,the_value,the_date) VALUES(2 ,'e','3/3/2003');

-

  select id, the_value
      from table_name u1
      where the_date = (select max(the_date)
                     from table_name u2
                     where u1.id = u2.id)

-

id          the_value
----------- ---------
2           d
2           e
1           b

(3 row(s) affected)

1

这还将处理重复项(每个user_id返回一行):

SELECT *
FROM (
  SELECT u.*, FIRST_VALUE(u.rowid) OVER(PARTITION BY u.user_id ORDER BY u.date DESC) AS last_rowid
  FROM users u
) u2
WHERE u2.rowid = u2.last_rowid

1

刚刚测试过,它似乎可以在日志表上工作

select ColumnNames, max(DateColumn) from log  group by ColumnNames order by 1 desc

1

这应该很简单:

SELECT UserId, Value
FROM Users u
WHERE Date = (SELECT MAX(Date) FROM Users WHERE UserID = u.UserID)

1

不具有分区KEEP,DENSE_RANK概念的MySQL解决方案。

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


这“ 在其他DB上也不起作用”。这仅适用于MySQL,也可能适用于SQL Server,因为它具有类似的变量概念。在Oracle,Postgres,DB2,Derby,H2,HSQLDB,Vertica,Greenplum上绝对不能使用。此外,可接受的答案是标准的ANSI SQL(据了解,仅MySQL不支持)
a_horse_with_no_name

马,我想你是对的。我不了解其他数据库或ANSI。我的解决方案能够解决MySQL中的问题,该问题没有对ANSI SQL的适当支持以标准方式解决它。
本林

1

如果你使用的是Postgres,你可以使用array_agg

SELECT userid,MAX(adate),(array_agg(value ORDER BY adate DESC))[1] as value
FROM YOURTABLE
GROUP BY userid

我对Oracle不熟悉。这就是我想出的

SELECT 
  userid,
  MAX(adate),
  SUBSTR(
    (LISTAGG(value, ',') WITHIN GROUP (ORDER BY adate DESC)),
    0,
    INSTR((LISTAGG(value, ',') WITHIN GROUP (ORDER BY adate DESC)), ',')-1
  ) as value 
FROM YOURTABLE
GROUP BY userid 

这两个查询返回的结果与接受的答案相同。请参见SQLFiddles:

  1. 接受的答案
  2. 我的Postgres解决方案
  3. 我的Oracle解决方案

0

如果(UserID,Date)是唯一的,即同一用户没有两次出现任何日期,则:

select TheTable.UserID, TheTable.Value
from TheTable inner join (select UserID, max([Date]) MaxDate
                          from TheTable
                          group by UserID) UserMaxDate
     on TheTable.UserID = UserMaxDate.UserID
        TheTable.[Date] = UserMaxDate.MaxDate;

我相信您也需要通过UserID来加入
Tom H

0
select   UserId,max(Date) over (partition by UserId) value from users;

2
这将返回所有行,而不是每个用户仅返回一行。
乔恩·海勒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.