【问题标题】:Postgres Next/Previous row SQL QueryPostgres 下/上一行 SQL 查询
【发布时间】:2013-08-19 21:05:25
【问题描述】:

我在 Postgres 9.1 数据库中有以下表结构,但如果可能的话,理想的解决方案应该与数据库无关:

表:用户 |id|用户名| |1 |一个 | |2 |两个 | |3 |三 | 表:项目 |id|userid|项目名|已创建 | |1 |1 |a |时间戳| |2 |1 |b |时间戳| |3 |1 |c |时间戳| |4 |2 |d |时间戳| |5 |2 |e |时间戳| |6 |2 |f |时间戳| |7 |3 |g |时间戳| |8 |3 |h |时间戳| |9 |3 |i |时间戳|

我有一个查询(针对视图),它提供了下一个和上一个 item.id。

例如

查看:用户项 |id|userid|itemname|nextitemid|previtemid|created | |1 |1 |a |2 |null |时间戳| |2 |1 |b |3 |1 |时间戳| |3 |1 |c |4 |2 |时间戳| |4 |2 |d |5 |3 |时间戳| |5 |2 |e |6 |4 |时间戳| |6 |2 |f |7 |5 |时间戳| |7 |3 |g |8 |6 |时间戳| |8 |3 |h |9 |7 |时间戳| |9 |3 |i |null |8 |时间戳|

我可以通过以下查询来做到这一点:

SELECT
  DISTINCT i.id AS id,
  i.userid AS userid,
  i.itemname AS itemname,
  LEAD(i.id) OVER (ORDER BY i.created DESC) AS nextitemid,
  LAG(i.id) OVER (ORDER BY i.created DESC) AS previtemid,
  i.created AS created
FROM items i
  LEFT JOIN users u
  ON i.userid = u.id
ORDER BY i.created DESC;

您能帮忙解决以下问题吗:

1) 有没有办法让 ids 换行,即

  • nextitemid 列最后一行的 NULL itemid 应该是 1
  • previtemid 列第一行中的 NULL itemid 应为 9

2) 是否有一种高效的方法可以按用户 ID 对下一个和上一个 itemid 进行分组,例如

注意:在此示例中,用户的 itemid 是连续的,实际数据并非如此,每个用户的 itemid 是交错的。

查看:用户项 |id|userid|itemname|nextitemid|previtemid|nextuseritemid|prevuseritemid|created | |1 |1 |a |2 |9 |2 |3 |时间戳| |2 |1 |b |3 |1 |3 |1 |时间戳| |3 |1 |c |4 |2 |1 |2 |时间戳| |4 |2 |d |5 |3 |5 |6 |时间戳| |5 |2 |e |6 |4 |6 |4 |时间戳| |6 |2 |f |7 |5 |4 |5 |时间戳| |7 |3 |g |8 |6 |8 |9 |时间戳| |8 |3 |h |9 |7 |9 |7 |时间戳| |9 |3 |i |1 |8 |7 |8 |时间戳|

【问题讨论】:

    标签: sql postgresql ansi-sql


    【解决方案1】:

    Q1:FIRST_VALUE/LAST_VALUE

    Q2:PARTITION BY(正如 Roman Pekar 已经建议的那样)

    SEE FIDDLE HERE

    SELECT
      DISTINCT i.id AS id,
      i.userid AS userid,
      i.itemname AS itemname,
      COALESCE(LEAD(i.id)        OVER (ORDER BY i.created DESC)
              ,FIRST_VALUE(i.id) OVER (ORDER BY i.created DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)) AS nextitemid,
      COALESCE(LAG(i.id)         OVER (ORDER BY i.created DESC)
              ,LAST_VALUE(i.id)  OVER (ORDER BY i.created DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)) AS previtemid,
      COALESCE(LEAD(i.id)        OVER (PARTITION BY i.userid ORDER BY i.created DESC)
              ,FIRST_VALUE(i.id) OVER (PARTITION BY i.userid ORDER BY i.created DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)) AS nextuseritemid,
      COALESCE(LAG(i.id)         OVER (PARTITION BY i.userid ORDER BY i.created DESC)
              ,LAST_VALUE(i.id)  OVER (PARTITION BY i.userid ORDER BY i.created DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)) AS prevuseritemid,
      i.created AS created
    FROM items i
      LEFT JOIN users u
      ON i.userid = u.id
    ORDER BY i.created DESC;
    

    【讨论】:

    【解决方案2】:

    更新 我忘记了 PostgreSQL 中的 first_value and last_value functions,感谢 dnoeth 他提醒了我。但是,他的查询不起作用,因为 last_value 正在使用默认窗口 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 并且不会返回正确的结果,因此您要么必须更改 over 子句中的范围,要么将 first_valueorder by asc 一起使用:

    select
        i.id as id,
        i.userid as userid,
        i.itemname as itemname,
        coalesce(
            lead(i.id) over(order by i.created desc),
            first_value(i.id) over(order by i.created desc)
        ) as nextitemid,
        coalesce(
            lag(i.id) over(order by i.created desc),
            first_value(i.id) over(order by i.created asc)
        ) as previtemid,
        coalesce(
            lead(i.id) over(partition by i.userid order by i.created desc),
            first_value(i.id) over(partition by i.userid order by i.created desc)
        ) as nextuseritemid,
        coalesce(
            lag(i.id) over(partition by i.userid order by i.created desc),
            first_value(i.id) over(partition by i.userid order by i.created asc)
        ) as prevuseritemid,
        i.created as created
    from items as i
       left outer join users as u on u.id = i.userid
    order by i.created desc
    

    sql fiddle demo

    以前的版本
    我认为你可以这样做:

    SELECT
      i.id AS id,
      i.userid AS userid,
      i.itemname AS itemname,
      coalesce(
          LEAD(i.id) OVER (ORDER BY i.created DESC),
          (select t.id from items as t order by t.created desc limit 1)
      ) AS nextitemid,
      coalesce(
          LAG(i.id) OVER (ORDER BY i.created DESC),
          (select t.id from items as t order by t.created asc limit 1)
      ) AS previtemid,
      coalesce(
          LEAD(i.id) OVER (partition by i.userid ORDER BY i.created DESC),
          (select t.id from items as t where t.userid = i.userid order by t.created desc limit 1)
      ) AS nextuseritemid,
      coalesce(
          LAG(i.id) OVER (partition by i.userid ORDER BY i.created DESC),
          (select t.id from items as t where t.userid = i.userid order by t.created asc limit 1)
      ) AS prevuseritemid,
      i.created AS created
    FROM items i
      LEFT JOIN users u
      ON i.userid = u.id
    ORDER BY i.created DESC;
    

    sql fiddle demo

    【讨论】:

    • 是的,我忘记了 ROWS。但是,我不会更改排序顺序,而是将 ROWS 更改为 ROWS BETWEEN UNBOUNDED PRECEDING 和 UNBOUNDED FOLLOWING。这应该允许优化器在一个步骤中使用相同的 PARTITION BY 和 ORDER BY 执行所有 OLAP 函数
    • 是的,做吧,我会为你点赞 :) 我想点赞,但它不起作用,所以你必须先编辑它:)
    • 好的,我修改了我的答案:-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-18
    • 2012-03-21
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多