如何限制一列中的 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.
中存在的值
对于 json
和 jsonb
,都没有内置方法来保证 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'
设置单独的逻辑可能是值得的: 提取 OLD
和 NEW
之间的更改集,并将其仅应用于 example_key
.
对于 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.
中存在的值对于 json
和 jsonb
,都没有内置方法来保证 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 anyON DELETE
triggers that might exist for the tables. But it will fireON TRUNCATE
triggers.
如果您打算 TRUNCATE example
,请确保您也 TRUNCATE example_key
,或者为此创建另一个触发器。
性能应该不错。如果您的 totally_unique
列包含 多个 键并且通常每个 UPDATE
仅进行少量更改,那么在您的触发器中为 TG_OP = 'UPDATE'
设置单独的逻辑可能是值得的: 提取 OLD
和 NEW
之间的更改集,并将其仅应用于 example_key
.