web-dev-qa-db-ja.com

すべての関係を含むネストされたJSONドキュメントに行を変換します

PostgreSQL 10.10では、NEW行をto_jsonb(NEW)を使用してJSONオブジェクトに変換するトリガー関数をPL/pgSQLで作成しました。しかし、今度は、NEWレコードの外部キーの反対側にあるレコードを、ネストされた方法でJSONオブジェクトに含める必要があります。

例えば:

前:

employee = {
    "id": 1,
    "name": "myname",
    "department": 2,
    "phone_no": "123456789"
}

後:

employee = {
    "id": 1,
    "name": "myname",
    "department": {
        "id": 2,
        "name": "IT"
    },
    "phone_no": "123456789"
}

NEWレコードのスキーマに関する事前の知識なしにこれを達成するための最良かつ最も一般的な方法は何ですか?すべてのテーブルで使用する予定なので、このトリガー関数をできるだけ汎用的にする必要があります。現在のところ、外部キーの追跡の深さは1レベルで十分です。また、簡単にするために、すべての外部キーは単一の列であると想定できます。

私が理解しているように、NEWレコードのすべての列をループして、列がinformation_schemaまたはpg_catalogを使用して外部キーであるかどうかを調べ、次のような外部キーの詳細を見つける必要があります。ターゲットテーブルのどの列を指し、次にターゲットレコードのターゲットテーブルに対して動的SQL SELECTを実行します(テーブルと列名はSQL識別子ではなく文字列であるため)、レコードをJSONに変換します最後に、それを最上位行のJSONオブジェクトの適切なキーに割り当てます。

私はまだこのための実際の作業コードを記述しようとしています。そのためのヘルプや指示は歓迎します。そして、私が知りたい、この問題のより簡単な解決策があるかもしれません。

2
zaadeh

推測はかなり近いです。動的SQLが必要になります。

しかし、これはNEWレコードなどのすべての列をループするよりもはるかに高速でエレガントなはずです。

_CREATE OR REPLACE FUNCTION trg_jsonb_row_with_fk()
  RETURNS trigger AS
$func$
DECLARE
   _sql text;
   _jsonb_row jsonb;
BEGIN
   SELECT 'SELECT to_jsonb($1) || '
       || string_agg(
            format('(SELECT jsonb_build_object(%1$L, t.*)
                     FROM %2$s t WHERE %3$I = $1.%1$I)'
                 , a.attname                 -- %1$L, %1$I
                 , c.confrelid::regclass     -- %2$s
                 , f.attname)                -- %3$I
            , ' || ')
   FROM   pg_constraint c
   JOIN   pg_attribute  a ON a.attrelid = c.conrelid
   JOIN   pg_attribute  f ON f.attrelid = c.confrelid
   WHERE  c.conrelid = TG_RELID     
   AND    c.contype  = 'f'               -- to select only FK constraints
   AND    a.attnum   = c.conkey[1]       -- assuming only single-col FKs!
   AND    f.attnum   = c.confkey[1]
   INTO   _sql;

   IF FOUND THEN                         -- FKs found
      EXECUTE _sql USING NEW INTO _jsonb_row;
   ELSE                                  -- no FKs found, plain conversion
      _jsonb_row := to_jsonb(NEW);
   END IF;

   RAISE NOTICE '%', _jsonb_row;         -- do something with it ...
   RETURN NEW;                           -- proceed with org. row
END
$func$  LANGUAGE plpgsql;
_

上記の関数を使用したトリガーの例:

_CREATE TRIGGER upd_bef_jsonb_row_with_fk
  BEFORE UPDATE ON tbl
  FOR EACH ROW EXECUTE PROCEDURE trg_jsonb_row_with_fk();
_

これにより、 Postgresカタログテーブル からすべてのFKのサブクエリが作成され、SQLコマンドが動的に実行されます。簡単にするため、ルックアップテーブル(_t.*_)の対応する行のすべてのユーザー列を含めます。

Nitpick:jsonb_build_object(%1$L, t.*)jsonb_build_object(%1$L, t)ではありません。
ノイズを追加するように見えますが、コーナーケースの問題は回避されます。これはany入力テーブルで機能するはずであり、tという名前の列が含まれる場合があります。次に、上記の式のtという名前は、テーブルエイリアス(行全体)ではなくcolumnに解決されます。 _t.*_を使用すると、行全体にしか解決できないため、このあいまいさが解消されます。 (括弧は、_(t).*_のように、複合型columnを参照する必要があります)。マニュアル ここ および ここ をお読みください。

元のFK列の列名を拡張オブジェクトのキー名として使用するため、プレーンな _||_ との連結は必要なことを実行します。既存の単純な値をjsonbオブジェクトに置き換えます。

参考文献:

1