如何限制一列中的 JSON/JSONB 值完全不同?

How to constrain that JSON/JSONB values in a column be completely different?

对于 table 这样的人:

CREATE TABLE example (
    totally_unique JSONB
);

如何限制 totally_unique 中所有键的值必须不同?键和值可以是任何字符串,所以我不能只为每个可能的键编写单独的约束。

换句话说,如果 {"a": "one", "b": "two"} 在 table 中,我想防止插入 {"a": "one", "b": "three"},因为值 totally_unique->>'a' = 'one' 已经存在。

UNIQUE 约束不足以解决此问题,但我看不出哪种约束或索引会起作用。

如果键的个数确定且比较小,可以为所有键创建部分唯一索引。

create unique index example_uq_a on example
((totally_unique->'a'))
where (totally_unique->'a') notnull;

create unique index example_uq_b on example
((totally_unique->'b'))
where (totally_unique->'b') notnull;

一些检查:

test=# insert into example values ('{"a": 1, "b": 2}');
INSERT 0 1

test=# insert into example values ('{"a": 1, "b": 1}');
ERROR:  duplicate key value violates unique constraint "example_uq_a"

test=# insert into example values ('{"a": 2, "b": 2}');
ERROR:  duplicate key value violates unique constraint "example_uq_b"

test=# insert into example values ('{"a": 1}');
ERROR:  duplicate key value violates unique constraint "example_uq_a"

test=# insert into example values ('{"b": 2}');
ERROR:  duplicate key value violates unique constraint "example_uq_b"

如果键的数量没有确定,我看不到创建约束的可能性。在这种情况下,我会为插入和更新创建一个触发器,这将拒绝 table.

中存在的值

对于 jsonjsonb,都没有内置方法来保证 JSON 值内的唯一 key/value 对.

但是您可以使用助手 table 和索引来实现您的目标。仅考虑 JSON 值的 最外层 。这不是为嵌套值准备的。

jsonb

的解决方案

显然需要 Postgres 9.4。
在稍作修改后,也适用于 Postgres 9.3 中的 json

Table布局

CREATE TABLE example (
  example_id     serial PRIMARY KEY
, totally_unique jsonb NOT NULL
);

CREATE TABLE example_key (
  key   text
, value text
, PRIMARY KEY (key, value)
);

触发函数&触发器

CREATE OR REPLACE FUNCTION trg_example_insupdelbef()
  RETURNS trigger AS
$func$
BEGIN
   -- split UPDATE into DELETE & INSERT to simplify
   IF TG_OP = 'UPDATE' THEN
      IF OLD.totally_unique IS DISTINCT FROM NEW.totally_unique THEN  -- keep going
      ELSE RETURN NEW;  -- exit, nothing to do
      END IF;
   END IF;

   IF TG_OP IN ('DELETE', 'UPDATE') THEN
      DELETE FROM example_key k
      USING  jsonb_each_text(OLD.totally_unique) j(key, value)
      WHERE  j.key = k.key
      AND    j.value = k.value;

      IF TG_OP = 'DELETE' THEN RETURN OLD;  -- exit, we are done
      END IF;
   END IF;

   INSERT INTO example_key(key, value)
   SELECT *
   FROM   jsonb_each_text(NEW.totally_unique) j;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

CREATE TRIGGER example_insupdelbef
BEFORE INSERT OR DELETE OR UPDATE OF totally_unique ON example
FOR EACH ROW EXECUTE PROCEDURE trg_example_insupdelbef();

SQL Fiddle 演示 INSERT / UPDATE / DELETE.
请注意,sqlfiddle.com 尚未提供 Postgres 9.4 集群。该演示模拟了第 9.3 页上的 json

处理 jsonb 的关键函数是 jsonb_each_text(),它正是您所需要的,因为您的值应该是 text.

具有更多解释的 Postgres 数组列的密切相关答案:

  • Can PostgreSQL have a uniqueness constraint on array elements?

还要考虑规范化的 "righteous path"。在这里也适用。

这不像 UNIQUE 约束那样牢不可破,因为触发器可以被其他触发器绕过并且更容易停用,但如果您不执行任何此类操作,您的约束将完全强制执行次。

特别注意,per documentation:

TRUNCATE will not fire any ON DELETE triggers that might exist for the tables. But it will fire ON TRUNCATE triggers.

如果您打算 TRUNCATE example,请确保您也 TRUNCATE example_key,或者为此创建另一个触发器。

性能应该不错。如果您的 totally_unique 列包含 多个 键并且通常每个 UPDATE 仅进行少量更改,那么在您的触发器中为 TG_OP = 'UPDATE' 设置单独的逻辑可能是值得的: 提取 OLDNEW 之间的更改集,并将其仅应用于 example_key.