在 Postgres 9+ 上提取与日期时间值相关的 MIN 和 MAX 值

Ran*_*ess 7 postgresql postgresql-9.4

我正在尝试查询一个表,其中包含一段时间内记录的学生成绩集合。我想生成一个结果集,获取学生 id、一年级、一年级日期、最后一年级、最后一年级日期。

我想我需要使用MINMAX函数和一些子查询来实现这一点,但我只是没有得到我需要的结果。

有没有一种有效的方法可以在 PostgreSQL 上实现以下结果?

数据库示例:

user_id | grade | grade_date
1       | A     | 01/05/2016
1       | B     | 01/15/2016
1       | C     | 01/31/2016
2       | A     | 01/05/2016
2       | B     | 01/15/2016
2       | C     | 01/31/2016
3       | A     | 01/05/2016
3       | B     | 01/15/2016
3       | C     | 01/31/2016
4       | A     | 01/05/2016
4       | B     | 01/15/2016
4       | C     | 01/31/2016
Run Code Online (Sandbox Code Playgroud)

我的目标是:

user_id | first_grade | first_date | last_grade | last_date
1       | A           | 01/05/2016 | C          | 01/31/2016
2       | A           | 01/05/2016 | C          | 01/31/2016 
3       | A           | 01/05/2016 | C          | 01/31/2016 
4       | A           | 01/05/2016 | C          | 01/31/2016 
Run Code Online (Sandbox Code Playgroud)

Jul*_*eur 8

有多种方法可以做到。在 order/filter/join(user_id 和 grade_date + grade)中使用的列上的索引将在大表中发挥重要作用。性能必须通过真实数据和表/索引设计进行测试。

使用窗口函数 ( ROW_NUMBER()):

SELECT f.user_id, f.grade, f.grade_date, l.grade, l.grade_date 
FROM (
    SELECT user_id, grade, grade_date
        , ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY grade_date) as n
    FROM data
) f
INNER JOIN (
    SELECT user_id, grade, grade_date
        , ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY grade_date DESC) as n
    FROM data
) l
ON f.user_id = l.user_id 
    AND f.n = 1 AND l.n = 1;
Run Code Online (Sandbox Code Playgroud)

ROW_NUMBER 按grade_date 上下给出从1 到N 的每一行的数字,并且只保留每行中的第一个(n=1)。

使用子查询:

SELECT  user_id
    , ( SELECT grade FROM data
        WHERE  user_id = d.user_id
        ORDER BY grade_date LIMIT 1
    )
    , ( SELECT grade_date FROM data
        WHERE  user_id = d.user_id
        ORDER BY grade_date LIMIT 1
    )
    , ( SELECT grade FROM data
        WHERE  user_id = d.user_id
        ORDER BY grade_date DESC LIMIT 1
    )
    , ( SELECT grade_date FROM data
        WHERE  user_id = d.user_id
        ORDER BY grade_date DESC LIMIT 1
    )
FROM (SELECT DISTINCT user_id FROM data) d
;
Run Code Online (Sandbox Code Playgroud)

每个子查询只保留第一行并返回它。

使用 MIN 和 MAX:

SELECT d.user_id, mn.grade, mn.grade_date, mx.grade, mx.grade_date
FROM (
    SELECT user_id, MIN(grade_date) as min_grade_date, MAX(grade_date) as max_grade_date
    FROM data
    GROUP BY user_id
) d
INNER JOIN data mn 
    ON mn.grade_date = d.min_grade_date AND mn.user_id = d.user_id 
INNER JOIN data mx 
    ON mx.grade_date = d.max_grade_date AND mx.user_id = d.user_id 
;
Run Code Online (Sandbox Code Playgroud)

如果用户在第一个或最后一个日期的成绩超过 1 个,则可能会生成重复的行。

请参阅SQL 小提琴