4

テーブルの複数の列を同じテーブルの別の行の値で更新しようとしています。

CREATE TEMP TABLE person (
  pid INT
, name VARCHAR(40)
, dob DATE
, younger_sibling_name VARCHAR(40)
, younger_sibling_dob DATE
);

INSERT INTO person VALUES (pid, name, dob)
  (1, 'John' , '1980-01-05')
, (2, 'Jimmy', '1975-04-25')
, (3, 'Sarah', '2004-02-10')
, (4, 'Frank', '1934-12-12')
;

タスクは、年齢が最も近いが、それ以上の年齢や同じ年齢ではない人の名前と誕生日をyounger_sibling_name入力することです。younger_sibling_dob

これは、相関サブクエリで使用するレコードを決定する値であるため、若い兄弟をdob簡単に設定できます(これはその例だと思いますか?):

UPDATE person SET younger_sibling_dob = (
SELECT MAX(dob)
FROM person AS sibling
WHERE sibling.dob < person.dob);

name?を取得する方法がわかりません。
これの実際のクエリは、MAXの選択ごとに100〜500のグループで約100万行にわたって実行されるため、パフォーマンスが問題になります。

編集

多くの異なるアプローチを試した後、私はこれを決定しました。これは、中間結果でデータを検証でき、ロジックが何であるかの意図を示し、適切に実行できることのバランスが取れていると思います。

WITH sibling AS (
  SELECT person.pid, sibling.dob, sibling.name,
         row_number() OVER (PARTITION BY person.pid
                            ORDER BY sibling.dob DESC) AS age_closeness
  FROM person
  JOIN person AS sibling ON sibling.dob < person.dob
)
UPDATE person
  SET younger_sibling_name = sibling.name
     ,younger_sibling_dob  = sibling.dob
FROM sibling
WHERE person.pid = sibling.pid
   AND sibling.age_closeness = 1;

SELECT * FROM person ORDER BY dob;
4

3 に答える 3

6

2022を書き直します

追加されたソリューションは、不必要な作業を行っているため、パフォーマンスが低下すると思います。以下ははるかに高速であるはずです。

質問と追加された解決策は、同じが複数ある場合にどの行を選択するかを定義していませんdob。通常、決定論的な選択が必要になります。このクエリは、同じ。を持つピアの各グループからアルファベット順の名を選択しますdob。あなたのニーズに適応します。

UPDATE person p
SET    younger_sibling_name = y.name
     , younger_sibling_dob  = y.dob
FROM  (
   SELECT dob, name, lead(dob) OVER (ORDER BY dob) AS next_dob
   FROM  (
      SELECT DISTINCT ON (dob)
             dob, name
      FROM   person p
      ORDER  BY dob, name  -- ①
      ) sub
   ) y
WHERE  p.dob = y.next_dob;

db <>fiddlehere-拡張テストケース付き

少なくともPostgres8.4以降で動作します。

のインデックスが高速である必要があります。dob理想的には、の複数列のインデックス(dob, name)です。

サブクエリsubはテーブル全体を1回渡し、ごとに個別の行を抽出しますdob
①アルファベット順の名の行を選択するためにタイブレーカーとして追加nameしました。ORDER BY私たちのニーズに適応します。

外側SELECTで、次の後でdobnext_dob)を各行に-simple nowwithdistinctで追加lead()しますdob。次にそれに参加するnext_dobと、残りは簡単です。

若い人がいない場合、何も起こらUPDATEず、列は残りNULLます。

多くの重複に対するDISTINCT ON、おそらくより高速なクエリ手法:

同じ行から取得dobnameて、同期を維持することを保証します。複数の相関サブクエリはこの保証を提供せず、とにかくより高価になります。

元の回答

まだ有効。

古いクエリ1

WITH cte AS (
   SELECT *, dense_rank() OVER (ORDER BY dob) AS drk
   FROM   person
    )
UPDATE person p
SET    younger_sibling_name = y.name
     , younger_sibling_dob  = y.dob
FROM   cte x
JOIN   (SELECT DISTINCT ON (drk) * FROM cte) y ON y.drk = x.drk - 1
WHERE  x.pid = p.pid;

古いsqlfiddle

CTE ではcte、ウィンドウ関数を使用して、すべての人dense_rank()に応じてギャップのないランクを取得します。dop

自分自身に参加しますが、2番目のインスタンスからcte重複を削除します。dobこれにより、誰もが正確に1つを取得しますUPDATE。複数の人が同じdopを共有している場合、次のすべての人の若い兄弟として同じ人dobが選択されます。私はこれを行います:

   (SELECT DISTINCT ON (rnk) * FROM cte)

このサブクエリに追加ORDER BY rnk, ...して、ごとに特定の人を選択しますdob

古いクエリ2

WITH cte AS (
   SELECT dob, min(name) AS name
        , row_number() OVER (ORDER BY dob) rn
   FROM   person p
   GROUP  BY dob
   )
UPDATE person p
SET    younger_sibling_name = y.name
     , younger_sibling_dob  = y.dob
FROM   cte x
JOIN   cte y ON y.rn = x.rn - 1
WHERE  x.dob = p.dob;

古いsqlfiddle

集計関数はウィンドウ関数の前に適用されるため、これは機能します。また、両方の操作がソート順で一致するため、非常に高速である必要があります。

DISTINCTクエリ1のように後で必要になることはありません。

結果はクエリ1とまったく同じです。
ここでも、列を追加してORDER BY、ごとに特定の人物を選択できますdob

于 2013-03-20T01:26:21.340 に答える
2

1)MAX()を見つけることは、常にNOT EXISTSの観点から書き直すことができます(...)

UPDATE person dst
SET younger_sibling_name = src.name
        ,younger_sibling_dob = src.dob
FROM person src
WHERE src.dob < dst.dob
   OR src.dob = dst.dob AND src.pid < dst.pid
AND NOT EXISTS (
        SELECT * FROM person nx
        WHERE nx.dob < dst.dob
           OR nx.dob = dst.dob AND nx.pid < dst.pid
        AND nx.dob > src.dob
           OR nx.dob = src.dob AND nx.pid > src.pid
        );

2)rank()/ row_number()の代わりに、WINDOWに対してLAG()関数を使用することもできます。

UPDATE person dst
SET younger_sibling_name = src.name
        ,younger_sibling_dob = src.dob
FROM    (
        SELECT pid
        , LAG(name) OVER win AS name
        , LAG(dob) OVER win AS dob 
        FROM person
        WINDOW win AS (ORDER BY dob, pid)
        ) src
WHERE src.pid = dst.pid
        ;

UPDATEではウィンドウ関数が許可されていないため、どちらのバージョンでも自己結合サブクエリ(またはCTE)が必要です。

于 2013-03-20T17:37:08.333 に答える
1

ドブと名前を取得するには、次のようにします。

update person
    set younger_sibling_dob = (select dob
                               from person p2
                               where s.dob < person.dob
                               order by dob desc
                               limit 1),
       younger_sibling_name = (select name
                               from person p2
                               where s.dob < person.dob
                               order by dob desc
                               limit 1)

にインデックスがある場合dob、クエリはより高速に実行されます。

于 2013-03-19T22:56:12.420 に答える