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

标签 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 约束不足以满足此要求,但我不知道哪种约束或索引会起作用。

最佳答案

jsonjsonb 都没有内置方法来保证整个表中 JSON 值中的唯一键/值对。

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

jsonb的解决方案

显然需要 Postgres 9.4 或更高版本。
(在稍作修改后,也适用于 Postgres 9.3 中的 json。)

表格布局

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

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

触发函数&触发器

CREATE OR REPLACE FUNCTION trg_example_insupdelbef()
  RETURNS trigger
  LANGUAGE plpgsql AS
$func$
BEGIN
   -- split UPDATE into DELETE & INSERT to simplify
   IF TG_OP = 'UPDATE' THEN
      -- Works in pg 9.4+ with jsonb:
      -- Does not work with json since there is no "=" operator for json
      IF OLD.totally_unique IS NOT DISTINCT FROM NEW.totally_unique THEN
         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$;

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

db<> fiddle here - 演示INSERT/UPDATE/DELETE
<子>旧sqlfiddle

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

与 Postgres 数组列密切相关的答案以及更多解释:

还要考虑那里列出的规范化的“正道”。在这里也适用。

这不像 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 =触发器中的“更新”:提取OLDNEW 之间的更改集,并将其仅应用于example_key

关于sql - 如何限制列中的 JSON/JSONB 值完全不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31277383/

相关文章:

MySQL 升级后 MySQL NOT IN 查询速度变慢

mysql - SQL:在单行而不是不同列中显示数据

sql - SAS : data steps versus proc sql

javascript - Knockout.js:从 JSON 获取可观察值

需要 SQL Server 查询帮助

javascript - 分组数组内的编号

sql - 通过多个连接、分组依据和排序依据加快查询速度

sql - PostgreSQL 中 Informix GLOBAL 修饰符的等价物

sql - PostgreSQL `COPY TO` 命令是否(或可以)保证特定的行顺序?

c# - 使用 JavaScriptSerializer 格式化 JSON