如何约束列中的JSON / JSONB值完全不同?

时间:2015-07-07 19:14:00

标签: sql json postgresql database-design unique-constraint

对于像这样的表:

CREATE TABLE example (
    totally_unique JSONB
);

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

换句话说,如果表格中有{"a": "one", "b": "two"},我希望阻止插入{"a": "one", "b": "three"},因为值totally_unique->>'a' = 'one'已经存在。

UNIQUE约束是不够的,但我不知道哪种约束或索引会起作用。

2 个答案:

答案 0 :(得分:3)

没有内置方法可以保证表中JSON值内的唯一键/值对,jsonjsonb都不是。

但您可以使用帮助程序表和索引来实现目标。仅考虑JSON值的最外层。这不适用于嵌套值。

jsonb

的解决方案

显然需要Postgres 9.4 经过微小的修改后,也可以在Postgres 9.3中用于json

表格布局

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数组列的密切相关答案有更多解释:

还要考虑那里规范化的 “正义路径” 。也适用于此。

这不像UNIQUE约束那样牢不可破,因为触发器可以被其他触发器规避并且更容易停用,但如果您不执行任何此类操作,则会始终强制执行约束。

请特别注意per documentation:

  

TRUNCATE不会触发任何可能存在的ON DELETE触发器   表。但它会触发ON TRUNCATE触发器。

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

表现应该不错。如果您的totally_unique列包含许多键,并且通常每UPDATE只有少量更改,则可能需要为触发器中的TG_OP = 'UPDATE'分别设置逻辑:提取a在OLDNEW之间进行更改,并仅将其应用于example_key

答案 1 :(得分:2)

如果确定了键的数量并且相当小,则可以为所有键创建部分唯一索引。

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"

如果未确定键数,我看不到创建约束的可能性。在这种情况下,我将为插入和更新创建一个触发器,它将拒绝表中存在的值。