web-dev-qa-db-ja.com

Postgres次/前行SQLクエリ

Postgres 9.1データベースには次のテーブル構造がありますが、理想的なソリューションは、可能であればDBに依存しないことです。

表:users 
 | id | username | 
 | 1 | one | 
 | 2 | two | 
 | 3 | three | 
 
表:items 
 | id | userid | itemname | created | 
 | 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を提供するクエリ(ビュー用)があります。

例えば.

表示:UserItems 
 | id | userid | itemname | nextitemid | previtemid | created | 
 | 1 | 1 | a | 2 | null | timestamp | 
 | 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)IDをラップさせる方法はありますか?.

  • nextitemid列の最後の行のNULLitemidは1である必要があります
  • previtemid列の最初の行のNULLitemidは9である必要があります

2)ユーザーIDで次と前のアイテムIDをグループ化するパフォーマンスの高い方法はありますか?.

注意: この例では、ユーザーのitemidはシーケンシャルですが、実際のデータの場合はそうではなく、各ユーザーのitemidはインターリーブされています。

表示:UserItems 
 | 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 |タイムスタンプ| 
13
davec

Q1:FIRST_VALUE/LAST_VALUE

Q2:PARTITION BY(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;
13
dnoeth

updatePostgreSQLでは first_value関数とlast_value関数 を忘れてしまいました。dnoethのおかげで、彼はそれについて私に思い出させました。ただし、last_valueがデフォルトウィンドウで機能しているため、彼のクエリは機能していません 境界のない前の行と現在の行の間の範囲 正しい結果を返さないため、範囲内で範囲を変更する必要があります句を使用するか、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

9
Roman Pekar