使用触发器在插入或更新时将列与 json 列中的字段同步

kag*_*ag0 5 postgresql trigger plpgsql json postgresql-9.4

我是一个数据库/postgres 初学者,所以请耐心等待。
如果我有一张桌子,就像这样。

CREATE TABLE testy (
    id INTEGER REFERENCES other_table,
    name varchar(128) PRIMARY KEY,
    json JSONB NOT NULL
);
Run Code Online (Sandbox Code Playgroud)

我在寻找插入或更新,将设置列之前创建一个触发器id,并name与在相同名称字段的值json

因此,例如,如果testy包含以下内容UPDATE testy SET json = '{"id":2,"name":"jim"}' WHERE id = 1并被调用。

id | name | json
---+------+-----
 1 | "jim"| {"id":1,"name":"jim"}
Run Code Online (Sandbox Code Playgroud)

想要的结果是

id | name | json
---+------+-----
 2 | "jim"| {"id":2,"name":"jim"}
Run Code Online (Sandbox Code Playgroud)

我希望使其相当通用,因此不需要对列名进行硬编码。如果相应的 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();
Run Code Online (Sandbox Code Playgroud)

这不起作用,因为您不能灵活地使用roow.column_name。我试过玩 EXECUTE 没有成功,尽管我可能只是做得不对。

任何帮助将不胜感激!!

编辑:这样做的动机是可以将外键约束放置在表现为 json 字段的内容上。

编辑:plv8 很棒。使用了@Daniel Vérité 答案的修改版本,以便在 json 中未表示为字段的列将被清空

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();
Run Code Online (Sandbox Code Playgroud)

Erw*_*ter 4

实际上,这就是您所需要的:

\n
NEW := jsonb_populate_record(NEW, NEW.json);\n
Run Code Online (Sandbox Code Playgroud)\n

手册(从第 13 页开始):

\n
\n

jsonb_populate_record( , ) \xe2\x86\x92base anyelementfrom_json jsonbanyelement

\n

将顶级 JSON 对象扩展为具有基本参数复合类型的行。扫描 JSON 对象以查找名称与输出行类型的列名称匹配的字段,并将它们的值插入到输出的这些列中。(不对应于任何输出列名称的字段将被忽略。)在典型使用中,base 的值只是NULL,这意味着与任何对象字段不匹配的任何输出列都将用空值填充。但是,如果\nbase 不是,NULL则它包含的值将用于\n不匹配的列。

\n
\n

作为第一个参数提供的行保留所有未被覆盖的值(json 值中没有匹配的键)在 Postgres 13 之前没有记录

\n

如果“未记录”对您来说太不确定,请使用执行完全相同hstore操作的运算符。#=

\n
NEW := (NEW #= hstore(jsonb_populate_record(NEW, NEW.json)));\n
Run Code Online (Sandbox Code Playgroud)\n

无论如何,该hstore模块应该安装在大多数系统中。指示:

\n\n

任一解决方案均可源自:

\n\n

需要注意的一件事 - 你写道:

\n
\n

如果相应的 json 字段不存在,则将该列设置为 NULL 就可以了。

\n
\n

会保留JSON 值中没有匹配键的所有值,这应该更好。

\n

功能码

\n
CREATE OR REPLACE FUNCTION json_fn()\n  RETURNS TRIGGER\n  LANGUAGE plpgsql AS\n$func$\nBEGIN\n   NEW := jsonb_populate_record(NEW, NEW.json); -- or hstore alternative\n   RETURN NEW;\nEND\n$func$;\n
Run Code Online (Sandbox Code Playgroud)\n

您的设置中的其他所有内容看起来都正确,只需将 PK 添加到testy

\n
CREATE TABLE testy (\n    id   int   PRIMARY KEY REFERENCES testy_index\n  , data jsonb NOT NULL\n);
Run Code Online (Sandbox Code Playgroud)\n

在 pg 9.4 中进行了测试,它对我来说如广告所示有效。

\n

将其他列设置为NULL

\n

根据评论:

\n
CREATE OR REPLACE FUNCTION json_fn()\n  RETURNS trigger\n  LANGUAGE plpgsql AS\n$func$\nDECLARE\n   _j jsonb := NEW.json;  -- remember the json value\nBEGIN\n   NEW := jsonb_populate_record(NULL::testy, _j);\n   NEW.json := _j;   -- reassign\n   RETURN NEW;\nEND\n$func$;\n
Run Code Online (Sandbox Code Playgroud)\n

显然,您需要确保列名称或您的jsonb列不会在 JSON 值中显示为键名称。我不会使用作json​​为列名称,因为它是基本数据类型名称,这可能会令人困惑。

\n