左边加入Group By

时间:2015-03-23 01:43:44

标签: sql postgresql group-by left-join greatest-n-per-group

我正在使用PostgreSQL 9.4。

我有一张workouts的表格。用户可以为每个results创建多个workoutresultscore

给定一个workout_ids列表和两个user_id,我想为每个用户返回每个锻炼的最佳分数。如果用户没有该锻炼的结果,我想返回填充/ null结果。

SELECT "results".*, "workouts".* 
FROM "results" LEFT JOIN "workouts" ON "workouts"."id" = "results"."workout_id" 
WHERE (
  (user_id, workout_id, score) IN 
  (SELECT user_id, workout_id, MAX(score) 
    FROM results WHERE user_id IN (1, 2) AND workout_id IN (1, 2, 3) 
    GROUP BY user_id, workout_id)
) 

在此查询中,左连接充当内连接;如果用户没有得到锻炼结果,我没有得到任何填充。无论存在多少结果,此查询应始终返回六行。

示例数据:

results
user_id | workout_id | score 
-----------------------------
      1 |          1 |     10
      1 |          3 |     10
      1 |          3 |     15
      2 |          1 |      5

Desired result:

results.user_id | results.workout_id | max(results.score) | workouts.name
-------------------------------------------------------------------------
              1 |                  1 |                 10 | Squat
              1 |                  2 |               null | Bench
              1 |                  3 |                 15 | Deadlift
              2 |                  1 |                  5 | Squat
              2 |                  2 |               null | Bench
              2 |                  3 |               null | Deadlift

3 个答案:

答案 0 :(得分:4)

where过滤掉你的NULL值,这就是为什么结果不符合你的期望。

Joinint WHERE子句结果而不是过滤where子句结果。

SELECT "results".*, "workouts".*,"max_score".*
FROM "results" 
LEFT JOIN "workouts" ON "workouts"."id" = "results"."workout_id"
LEFT JOIN (SELECT user_id, workout_id, MAX(score) 
    FROM results WHERE user_id IN (1, 2) AND workout_id IN (1, 2, 3) 
    GROUP BY user_id, workout_id) max_score ON workouts.workout_id=max_score.workout_id;

您需要更改SELECT以获取正确的列。

答案 1 :(得分:1)

如何使用distinct onrow_number()

SELECT DISTINCT ON (r.user_id, r.workout_id) r.*, w.* 
FROM "results" r LEFT JOIN
     "workouts" w
     ON "w."id" = r."workout_id" 
WHERE r.user_id IN (1, 2) AND r.workout_id IN (1, 2, 3) 
ORDER BY r.user_id, r.workout_id, score desc;

row_number()等效项需要子查询:

SELECT rw.*
FROM (SELECT r.*, w.*,
             row_number() over (partition by user_id, workout_id order by score desc) as seqnum 
      FROM "results" r LEFT JOIN
           "workouts" w
           ON "w."id" = r."workout_id" 
      WHERE r.user_id IN (1, 2) AND r.workout_id IN (1, 2, 3) 
     ) rw
WHERE seqnum = 1;

您应该比使用*更明智地选择列。在重复列名称的情况下,子查询可能会返回错误。

编辑:

您需要先生成行,然后生成每个行的结果。这是一种基于第二个查询的方法:

SELECT u.user_id, w.workout_id, rw.score, rw.name
FROM (SELECT 1 as user_id UNION ALL SELECT 2) u CROSS JOIN
     (SELECT 1 as workout_id UNION ALL SELECT 2 UNION ALL SELECT 3) w LEFT JOIN
     (SELECT r.*, w.*,
             row_number() over (partition by user_id, workout_id order by score desc) as seqnum 
      FROM "results" r LEFT JOIN
           "workouts" w
           ON "w."id" = r."workout_id" 
      WHERE r.user_id IN (1, 2) AND r.workout_id IN (1, 2, 3) 
     ) rw
     ON rw.user_id = u.user_id and rw.workout_id = w.workout_id and
        rw.seqnum = 1;

答案 2 :(得分:1)

SELECT DISTINCT ON (1, 2)
       u.user_id
     , w.id AS workout_id
     , r.score
     , w.name AS workout_name
FROM   workouts w
CROSS  JOIN (VALUES (1), (2)) u(user_id)
LEFT   JOIN  results r ON r.workout_id = w.id
                      AND r.user_id = u.user_id
WHERE  w.id IN (1, 2, 3)
ORDER  BY 1, 2, r.score DESC NULLS LAST;

解释

  1. 形成给定锻炼和用户的完整笛卡尔积。
    假设给定的训练始终存在 假设并非所有给定用户都有针对所有给定锻炼的结果。

  2. 然后 LEFT JOINresults。所有条件都进入ON的{​​{1}}子句,而不是LEFT JOIN子句,这将排除没有结果的WHERE组合。详细说明:

  3. 最后使用(workout_id, user_id)(user_id, workout_id)选择最佳结果。在此期间,产生所需的排序顺序 DISTINCT ON的详细信息:

  4. 根据表的大小和数据分布,可能会有更快的解决方案:

    简易版

    如果您想要的是每个DISTINCT ON组合的最大score,则有简单的版本:

    (user_id, workout_id)

    SQL Fiddle.