LoginSignup
3
1

More than 5 years have passed since last update.

WITH RECURSIVEの使用

Posted at

先日、teratailの質問に回答するために一生懸命考えたWITH RECURSIVEを使用したSQL。もったいないので、メモとして残しておきたいと思う。

仕様

  • お客様TBLと変更情報TBLがある。
  • 変更情報TBLには変更の都度データが作成される。このため、お客様TBLのIDを変更情報TBLの旧IDで検索、ヒットした新しいIDをさらに変更情報TBLの旧IDで検索、、、としていくことで最新の変更情報を辿ることができる。
  • お客様TBLを変更情報TBLの最新の情報で更新したい。
  • 変更情報TBLに存在しないIDは更新しない。

クエリ

WITH RECURSIVE OKYAKUSAMA AS ( --お客様TBL
 SELECT '0001' AS ID, 1 AS RENNBANN, '0001001' AS CARDNO
 UNION 
 SELECT '0001' AS ID, 3 AS RENNBANN, '0001001' AS CARDNO
 UNION 
 SELECT '0005' AS ID, 4 AS RENNBANN, '0001010' AS CARDNO
)
, HENNKOU AS ( --変更情報TBL
 SELECT '1001' AS ID, 1 AS RENNBANN, '1001003' AS CARDNO, '1001' AS KYUUID, 2 AS KYUURENNBANN, '1001003' AS KYUUCARDNO, TO_TIMESTAMP('2018/08/20 12:00:00', 'YYYY/MM/DD HH24:MI:SS') AS HENKOUBI
 UNION
 SELECT '1001' AS ID, 2 AS RENNBANN, '1001003' AS CARDNO, '0001' AS KYUUID, 3 AS KYUURENNBANN, '0001001' AS KYUUCARDNO, TO_TIMESTAMP('2018/08/01 11:00:00', 'YYYY/MM/DD HH24:MI:SS') AS HENKOUBI
 UNION
 SELECT '1005' AS ID, 5 AS RENNBANN, '1001005' AS CARDNO, '0005' AS KYUUID, 4 AS KYUURENNBANN, '0001010' AS KYUUCARDNO, TO_TIMESTAMP('2018/08/20 13:00:00', 'YYYY/MM/DD HH24:MI:SS') AS HENKOUBI
)
, TAISYO(ID, RENNBANN, CARDNO, MOTOID, MOTORENNBANN, MOTOCARDNO, DEPTH) AS (
 SELECT CHANGES.ID, CHANGES.RENNBANN, CHANGES.CARDNO, CUSTOMER.ID AS MOTOID, CUSTOMER.RENNBANN AS MOTORENNBANN, CUSTOMER.CARDNO AS MOTOCARDNO, 1 AS DEPTH
 FROM OKYAKUSAMA AS CUSTOMER
 INNER JOIN HENNKOU AS CHANGES
  ON CUSTOMER.ID = CHANGES.KYUUID
  AND CUSTOMER.RENNBANN = CHANGES.KYUURENNBANN
  AND CUSTOMER.CARDNO = CHANGES.KYUUCARDNO
 UNION ALL
 SELECT CHANGES.ID, CHANGES.RENNBANN, CHANGES.CARDNO, TAISYO.MOTOID, TAISYO.MOTORENNBANN, TAISYO.MOTOCARDNO, DEPTH + 1
 FROM HENNKOU AS CHANGES, TAISYO
 WHERE TAISYO.ID = CHANGES.KYUUID
  AND TAISYO.RENNBANN = CHANGES.KYUURENNBANN
  AND TAISYO.CARDNO = CHANGES.KYUUCARDNO
)
SELECT SUB_TAISYO.ID, SUB_TAISYO.RENNBANN, SUB_TAISYO.CARDNO 
FROM OKYAKUSAMA, (
  SELECT SUB.*
  FROM (SELECT TAISYO.*, MAX(TAISYO.DEPTH) OVER(PARTITION BY TAISYO.MOTOID, TAISYO.MOTORENNBANN, TAISYO.MOTOCARDNO) MAX_DEPTH FROM TAISYO) SUB
  WHERE SUB.DEPTH = SUB.MAX_DEPTH
 ) SUB_TAISYO
WHERE OKYAKUSAMA.ID = SUB_TAISYO.MOTOID
 AND OKYAKUSAMA.RENNBANN = SUB_TAISYO.MOTORENNBANN
 AND OKYAKUSAMA.CARDNO = SUB_TAISYO.MOTOCARDNO

説明

TAISYOの部分がWITH RECURSIVEの構文を使用している部分です。
まず、非再帰的表現部分(TAISYOのASの後の最初のSELECT句)でお客様TBLのキーと変更情報TBLの旧キーが同じデータを抽出しています。ここで、お客様情報のキーを元キー(MOTOID, MOTORENNBANN, MOTOCARDNO)として保持しています。さらに、深さを表すDEPTH1として定義しています。
再帰的表現部分(UNION ALLの後の最初のSELECT句)では、非再帰的表現部分で取得できた結果の変更情報TBLのキーが変更情報TBLの旧キーと同じデータを抽出しており、これを繰り返します。繰り返すたびDEPTHは加算されます。
その結果は以下のようになります。

ID;RENNBANN;CARDNO;MOTOID;MOTORENNBANN;MOTOCARDNO;DEPTH
"1001";2;"1001003";"0001";3;"0001001";1
"1001";1;"1001003";"0001";3;"0001001";2
"1005";5;"1001005";"0005";4;"0001010";1

WITH以降のSELECT句でこの結果からお客様TBLのキーと元キーが同じ、かつ、深さが最大のデータを取得すると更新対象(MOTOIDが0001と0005)とその更新値(最新の変更情報)が取得できます。

最後に

WITH RECURSIVEを使用する場面は多くはなさそうですが、知っていると複雑なクエリを容易に組むことができるかもです。

以下、参考サイト
https://www.postgresql.jp/document/9.5/html/queries-with.html

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1