私は少しデータベース/ postgresの初心者なので、我慢してください。
テーブルがある場合、このようなもの。
CREATE TABLE testy (
id INTEGER REFERENCES other_table,
name varchar(128) PRIMARY KEY,
json JSONB NOT NULL
);
列id
とname
をjson
の同じ名前のフィールドの値に設定する挿入または更新の前にトリガーを作成しようとしています。
したがって、たとえば、testy
に以下が含まれ、UPDATE testy SET json = '{"id":2,"name":"jim"}' WHERE id = 1
が呼び出された場合。
id | name | json
---+------+-----
1 | "jim"| {"id":1,"name":"jim"}
望ましい結果は
id | name | json
---+------+-----
2 | "jim"| {"id":2,"name":"jim"}
これをかなり一般的にしたいので、列名をハードコーディングする必要はありません。対応するjsonフィールドが存在しない場合は、列をNULLに設定しても問題ありません。これまでのところ
CREATE TABLE testy_index (
id INTEGER PRIMARY KEY
);
INSERT INTO testy_index VALUES (1);
INSERT INTO testy_index VALUES (2);
INSERT INTO testy_index VALUES (3);
CREATE TABLE testy (
id INTEGER REFERENCES testy_index,
json JSONB NOT NULL
);
CREATE UNIQUE INDEX testy_id ON testy((json->>'id'));
CREATE OR REPLACE FUNCTION json_fn() RETURNS TRIGGER AS $testy$
DECLARE
roow RECORD;
BEGIN
FOR roow IN
SELECT column_name FROM information_schema.columns WHERE table_name = 'testy'
LOOP
NEW.roow.column_name = (NEW.json->>roow.column_name);
END LOOP;
END;
$testy$ LANGUAGE plpgsql;
CREATE TRIGGER json_trigger
BEFORE INSERT OR UPDATE ON testy FOR EACH ROW
EXECUTE PROCEDURE json_fn();
Roow.column_nameを柔軟に使用できないため、これは機能しません。 EXECUTEをいじってみましたが、うまくいきませんでした。
どんな助けでも大歓迎です!!
編集:これの動機は、jsonフィールドとして動作するものに外部キー制約を設定できるようにするためです。
編集:plv8は素晴らしいです。 @DanielVéritéの回答の修正版を使用して、jsonでフィールドとして表されていない列がnullになるようにしました
CREATE OR REPLACE FUNCTION json_fn() RETURNS trigger AS
$$
var obj = JSON.parse(NEW.json);
for(var col in NEW){
if(col == 'json'){
continue;
}
if(col in obj){
NEW[col]=obj[col];
}else{
NEW[col]=null;
}
}
return NEW;
$$
LANGUAGE plv8;
CREATE TRIGGER json_trigger
BEFORE INSERT OR UPDATE ON testy FOR EACH ROW
EXECUTE PROCEDURE json_fn();
実際、これで十分です:
NEW := jsonb_populate_record(NEW, NEW.json);
jsonb_populate_record(base anyelement, from_json jsonb)
from_json
のオブジェクトを、baseで定義されたレコードタイプと列が一致する行に展開します(下記の注を参照)。
What'snotdocumented: 最初の引数として提供された行は、上書きされないすべての値を保持します(json値に一致するキーがありません)。これが変更される理由はわかりませんが、文書化されていない限り、完全に信頼することはできません。
注意すべきことの1つ-あなたが書いた:
対応するjsonフィールドが存在しない場合は、列をNULLに設定しても問題ありません。
これは、JSON値に一致するキーがないすべての値を保持します。これはさらに優れているはずです。
「文書化されていない」ことが不明確である場合は、hstore
演算子 #=
を使用してくださいまったく同じ。
NEW := (NEW #= hstore(jsonb_populate_record(NEW, NEW.json)));
とにかく、ほとんどのシステムにhstore
モジュールをインストールする必要があります。手順:
両方の解決策は、私の回答から導き出すことができます Danielはすでに参照されています :
CREATE OR REPLACE FUNCTION json_fn()
RETURNS TRIGGER AS
$func$
BEGIN
NEW := jsonb_populate_record(NEW, NEW.json); -- or hstore alternative
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
セットアップの他のすべては正しく見えます。PKをtesty
に追加するだけです。
CREATE TABLE testy (
id int PRIMARY KEY REFERENCES testy_index
, data jsonb NOT NULL
);
9.4ページでテストしましたが、宣伝どおりに動作します。 PLv8機能がパフォーマンスとシンプルさに匹敵するかどうかは疑問です。
コメント通り:
CREATE OR REPLACE FUNCTION json_fn()
RETURNS TRIGGER AS
$func$
DECLARE
_j jsonb := NEW.json; -- remember the json value
BEGIN
NEW := jsonb_populate_record(NULL::testy, _j);
NEW.json := _j; -- reassign
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
明らかに、列名またはjsonb
列がJSON値のキー名として表示されないようにする必要があります。また、json
はデータ型名であり、混乱を招く可能性があるため、列名としては使用しません。
動的フィールドは、plpgsqlでは悪名高いほど困難です。特に、new.variable := something
を書く方法はありません。ここで、variable
は列名を表します。
実行時にカタログを照会する方法については、 動的SQLを使用して複合変数フィールドの値を設定する方法 を参照してください。
個人的には、plv8
言語を使用したより簡単なソリューションをお勧めします。
CREATE FUNCTION json_fn() RETURNS trigger AS
$$
var obj = JSON.parse(NEW.json);
for (var key in obj) {
NEW[key]=obj[key];
}
return NEW;
$$
LANGUAGE plv8;
CREATE TRIGGER json_trigger
BEFORE INSERT OR UPDATE ON testy FOR EACH ROW
EXECUTE PROCEDURE json_fn();
それ以外の場合は、json
フィールドをこのテーブルから別のテーブルに移動しても、実装が単純化されていないかどうかを検討できます。
私は少し時間をかけてこの質問の答えを作成し、あなたのニーズに合うかもしれませんが、詳細な基準がないので、完全ではないかもしれません。しかし、うまくいけば、設計のニーズに合わせて操作できるほど十分に近いです。
最初に、アルゴリズムを設計するためにいくつかの初期仮定を行わなければなりませんでした。
1)この関数を使用すると、jsonの更新と「外部キー」列の更新を同時に実行しているテーブルの名前にアクセスできます。 (私がしたように)変数であるか、関数の各インスタンスに個別にハードコードする必要があります。
2)最初に指定したUPDATE
クエリに基づいて、_UPDATE testy SET json = '{"id":2,"name":"jim"}' WHERE id = 1
_として設計されていることを確認しました。これは、アプリケーションの他の場所に方法があることを示しています述語条件を取得するには、_WHERE id = 1
_。したがって、これは関数への入力としても機能します。
)これを多数のテーブルに適用できる単一の関数として使用し、その再利用性を高めることを意図している場合、関連するjson列の名前が各テーブルで同一である必要があります。それ以外の場合は、列のデータ型を確認するために追加の作業を行う必要があり、他のいくつかのケースでは、これがどこに問題があるのかを考えることができます。これらの列すべてに_json_field
_と名前を付けるだけで大丈夫です。
さらに面倒なく、関数に進みます。
_CREATE OR REPLACE FUNCTION json_prop(json_entry json, table_update text, where_clause text)
RETURNS void AS
$func$
DECLARE
sql text := 'UPDATE ';
colname_row RECORD;
BEGIN
sql := sql || table_update || ' SET ';
FOR colname_row IN
(SELECT col_tab.column_name FROM
(SELECT column_name FROM information_schema.columns WHERE table_name = table_update)
AS col_tab
WHERE (json_entry->column_name) IS NOT NULL)
LOOP
sql := sql || colname_row.column_name::text || ' = ''' || ((json_entry)->>(colname_row.column_name::text)) || ''', ';
END LOOP;
sql := sql || 'json_field = ''' || json_entry || ''' ';
sql := sql || where_clause || ';';
EXECUTE sql;
END
$func$ LANGUAGE plpgsql;
_
つまり、簡単に言うと、関数は3つの入力引数を取り、それらを使用して動的SQLステートメントを生成し、それを実行します。
入力
_json_entry
_-まさにあなたがそう思うもの。更新するjsonエントリ。
_table_update
_-更新するターゲットテーブル。
_where_clause
_-先に述べたように、私はあなたが事前に確立した述語を持っているというあなたの説明に基づいて仮定をしたので、そのエントリーはここに行きます。
操作
この関数は、副選択_table_update
_を実行することにより、_json_entry
_フィールドのキーと名前が一致する列を検索して、テーブル_SELECT column_name FROM information_schema.columns WHERE table_name = table_update
_の列を検索します。
列名と一致するjsonキーの場合、その列名は外側のselect SELECT col_tab.column_name FROM ... AS col_tab WHERE (json_entry->column_name) IS NOT NULL)
によって返されます。
FOR
ループはこれらの一致する各列名を反復処理し、関連する列データを更新するために必要な情報を動的SQLステートメントに追加します。
[〜#〜]注[〜#〜]「無関係な」フィールドは無視されました。つまり、一致する列名がないjsonキーがある場合、または_json_entry
_入力にない列名がある場合、これらのフィールドは無視されます。
関数の呼び出し
関数を呼び出すだけで呼び出すことができます
_SELECT * FROM json_prop('{"id":2,"name":"james"}'::json, 'testy', 'WHERE id = 1');
_
可能な変更
繰り返しますが、これはあなたのニーズに完全ではないかもしれません。トリガーとして使用することを検討しているとのことですが、代わりにトリガーをRETURN
してトリガーを設定する必要があります。 「余分な」フィールドを無視したのが気に入らない場合があり、列をNULL
にしたり、これらの場合にエラーを報告したりできますか?多分私は述語へのアクセスについて間違った仮定をしたのでしょうか?
これは確かに完全な機能実装ではありませんが、ニーズに合わせて修正するだけで十分でしょう。
がんばって!