13

我在 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 |时间戳|
4

2 回答 2

14

Q1:FIRST_VALUE/LAST_VALUE

Q2:分区(正如 Roman Pekar 已经建议的那样)

在这里看小提琴

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;
于 2013-08-17T16:56:58.707 回答
9

更新我忘记了PostgreSQL 中的first_value 和 last_value 函数,感谢 dnoeth 他提醒了我这件事。但是,他的查询不起作用,因为last_value正在使用默认窗口RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW并且不会返回正确的结果,因此您必须更改 over 子句内的范围或使用first_valuewith order 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

于 2013-08-17T16:00:49.210 回答