如何从此表中的每个不同用户中选择最高分数?

时间:2016-12-08 22:27:02

标签: sql postgresql greatest-n-per-group

我有下表(scores):

id  user  date         score  
---|-----|------------|--------
1  | 10  | 11/01/2016 | 400   
2  | 10  | 11/03/2016 | 450 
5  | 17  | 10/03/2016 | 305  
3  | 13  | 09/03/2016 | 120   
4  | 17  | 11/03/2016 | 300 
6  | 13  | 08/03/2016 | 120  
7  | 13  | 11/12/2016 | 120  
8  | 13  | 09/01/2016 | 110

我想为每个不同的用户选择max(score),使用date作为打破平局(如果出现平局,应返回最新的记录),以便结果看起来像以下(每个用户的最高分,按score降序排序):

id  user  date         score  
---|-----|------------|--------
2  | 10  | 11/03/2016 | 450   
5  | 17  | 10/03/2016 | 305  
7  | 13  | 11/12/2016 | 120 

我使用的是Postgres,无论如何我都不是SQL专家。我尝试过类似以下的内容,因为我没有id中包含group by列,因此无法正常工作:

select scores.user, max(scores.score) as score, scores.id
from scores
group by scores.user
order by score desc

我有一种感觉,我需要做一个子选择,但我不能让联接正常工作。我发现How can I SELECT rows with MAX(Column value), DISTINCT by another column in SQL?但我似乎无法让任何解决方案适合我,因为我需要返回行id并且我有可能在date列。

4 个答案:

答案 0 :(得分:3)

在Postgres中,通常最快的方法是使用distinct on ()

select distinct on (user_id) *
from the_table
order by user_id, score desc;

这绝对比使用max()的子查询的解决方案更快很多,并且通常比使用窗口函数的等效解决方案快一点(例如{{1} })

我使用row_number()作为列名,因为user_id是保留字,我强烈建议不要使用它。

答案 1 :(得分:1)

试试这个:

with 

-- get maximum scores by user
maxscores as (
  select "user", max(score) as maxscore
  from test
  group by "user"
),

-- find the maximum date as the tie-breaker along with the above information
maxdates as (
  select t."user", mx.maxscore, max(t."date") as maxdate
  from test t
  inner join maxscores mx 
    on mx."user" = t."user" 
    and mx.maxscore = t.score
  group by t."user", mx.maxscore
)

-- select all columns based on the results of maxdates
select t.*
from test t
inner join maxdates md
  on md."user" = t."user"
  and md.maxscore = t.score
  and md.maxdate = t."date";

<强>解释

  • 使用CTE maxdates,让我们找到每个用户的最高分
  • 回到桌面。获取与用户和最高分数匹配的记录。获取该用户/分数组合的最长日期
  • 回到桌面。获取与我们检索的用户,最高分数和最长日期匹配的行

示例:

http://sqlfiddle.com/#!15/0f756/8 - 没有row_number

http://sqlfiddle.com/#!15/0f756/13 - 使用row_number

随意根据需要更改查询。

测试用例

create table test (
  id int,
  "user" int,
  "date" date,
  score int
);

insert into test values 
(1  , 10  , '11/01/2016' , 400   )
,(2  , 10  , '11/03/2016' , 450 )
,(5  , 17  , '10/03/2016' , 305  )
,(3  , 13  , '09/03/2016' , 120   )
,(4  , 17  , '11/03/2016' , 300 )
,(6  , 13  , '08/03/2016' , 120  )
,(7  , 13  , '11/12/2016' , 120  )
,(8  , 13  , '09/01/2016' , 110);

<强>结果

| id | user |                       date | score |
|----|------|----------------------------|-------|
|  2 |   10 | November, 03 2016 00:00:00 |   450 |
|  5 |   17 |  October, 03 2016 00:00:00 |   305 |
|  7 |   13 | November, 12 2016 00:00:00 |   120 |

<强>风险

如果您有两个具有相同分数和日期的用户13的记录(例如),您将获得2个用户13的记录。

风险示例:http://sqlfiddle.com/#!15/cb86e/1

为了降低风险,您可以使用row_number() over(),如下所示:

with
rankeddata as (
  select row_number() over (
    partition by
      "user"
    order by 
      "user", 
      score desc, 
      "date" desc) as sr,
    t.*
  from test t
)
select * from rankeddata where sr = 1;

降低风险的结果

| sr | id | user |                       date | score |
|----|----|------|----------------------------|-------|
|  1 |  2 |   10 | November, 03 2016 00:00:00 |   450 |
|  1 |  7 |   13 | November, 12 2016 00:00:00 |   120 |
|  1 |  5 |   17 |  October, 03 2016 00:00:00 |   305 |

答案 2 :(得分:0)

这样

create table test (
  id int,
  "user" int,
  "date" date,
  score int
);

insert into test values 
(1  , 10  , '11/01/2016' , 400   )
,(2  , 10  , '11/03/2016' , 450 )
,(5  , 17  , '10/03/2016' , 305  )
,(3  , 13  , '09/03/2016' , 120   )
,(4  , 17  , '11/03/2016' , 300 )
,(6  , 13  , '08/03/2016' , 120  )
,(7  , 13  , '11/12/2016' , 120  )
,(8  , 13  , '09/01/2016' , 110);


select * from test where id in (
select distinct(first_value(id)
 over(
partition by "user" order by score desc 
))
from test
)

答案 3 :(得分:0)

用于mysql查询

select sr, id, user, date, MAX(score) score
from the_table
group by user
order by score desc;