Jur*_*ský 14 sql postgresql locking constraints
假设我有以下PostgreSQL表:
id | key
---+--------
1 | 'a.b.c'
Run Code Online (Sandbox Code Playgroud)
我需要防止使用作为另一个键的前缀的键插入记录.例如,我应该能够插入:
'a.b.b'但是不应接受以下密钥:
'a.b''a.b.c''a.b.c.d'有没有办法实现这一点 - 通过约束或锁定机制(在插入之前检查存在)?
fil*_*rem 10
此解决方案基于PostgreSQL 用户定义的运算符和排除约束(基本语法,更多详细信息).
注意:更多测试显示此解决方案无法正常工作.见底部.
创建一个函数has_common_prefix(text,text),它将逻辑地计算您需要的内容.将该功能标记为IMMUTABLE.
CREATE OR REPLACE FUNCTION
has_common_prefix(text,text)
RETURNS boolean
IMMUTABLE STRICT
LANGUAGE SQL AS $$
SELECT position ($1 in $2) = 1 OR position ($2 in $1) = 1
$$;
Run Code Online (Sandbox Code Playgroud)为索引创建运算符
CREATE OPERATOR <~> (
PROCEDURE = has_common_prefix,
LEFTARG = text,
RIGHTARG = text,
COMMUTATOR = <~>
);
Run Code Online (Sandbox Code Playgroud)创建排除约束
CREATE TABLE keys ( key text );
ALTER TABLE keys
ADD CONSTRAINT keys_cannot_have_common_prefix
EXCLUDE ( key WITH <~> );
Run Code Online (Sandbox Code Playgroud)但是,最后一点会产生此错误:
ERROR: operator <~>(text,text) is not a member of operator family "text_ops"
DETAIL: The exclusion operator must be related to the index operator class for the constraint.
Run Code Online (Sandbox Code Playgroud)
这是因为创建索引PostgreSQL需要逻辑运算符与物理索引方法绑定,通过实体calles"运算符类".所以我们需要提供这样的逻辑:
CREATE OR REPLACE FUNCTION keycmp(text,text)
RETURNS integer IMMUTABLE STRICT
LANGUAGE SQL AS $$
SELECT CASE
WHEN $1 = $2 OR position ($1 in $2) = 1 OR position ($2 in $1) = 1 THEN 0
WHEN $1 < $2 THEN -1
ELSE 1
END
$$;
CREATE OPERATOR CLASS key_ops FOR TYPE text USING btree AS
OPERATOR 3 <~> (text, text),
FUNCTION 1 keycmp (text, text)
;
ALTER TABLE keys
ADD CONSTRAINT keys_cannot_have_common_prefix
EXCLUDE ( key key_ops WITH <~> );
Run Code Online (Sandbox Code Playgroud)
现在,它有效:
INSERT INTO keys SELECT 'ara';
INSERT 0 1
INSERT INTO keys SELECT 'arka';
INSERT 0 1
INSERT INTO keys SELECT 'barka';
INSERT 0 1
INSERT INTO keys SELECT 'arak';
psql:test.sql:44: ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix"
DETAIL: Key (key)=(arak) conflicts with existing key (key)=(ara).
INSERT INTO keys SELECT 'bark';
psql:test.sql:45: ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix"
DETAIL: Key (key)=(bark) conflicts with existing key (key)=(barka).
Run Code Online (Sandbox Code Playgroud)
注意:更多测试显示此解决方案尚未运行:最后一次INSERT应该失败.
INSERT INTO keys SELECT 'a';
INSERT 0 1
INSERT INTO keys SELECT 'ac';
ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix"
DETAIL: Key (key)=(ac) conflicts with existing key (key)=(a).
INSERT INTO keys SELECT 'ab';
INSERT 0 1
Run Code Online (Sandbox Code Playgroud)
您可以使用ltree模块来实现这一点,它将使您创建分层的树状结构。还可以帮助您避免重新发明轮子,创建复杂的正则表达式等。您只需要postgresql-contrib安装软件包。看一看:
--Enabling extension
CREATE EXTENSION ltree;
--Creating our test table with a pre-loaded data
CREATE TABLE test_keys AS
SELECT
1 AS id,
'a.b.c'::ltree AS key_path;
--Now we'll do the trick with a before trigger
CREATE FUNCTION validate_key_path() RETURNS trigger AS $$
BEGIN
--This query will do our validation.
--It'll search if a key already exists in 'both' directions
--LIMIT 1 because one match is enough for our validation :)
PERFORM * FROM test_keys WHERE key_path @> NEW.key_path OR key_path <@ NEW.key_path LIMIT 1;
--If found a match then raise a error
IF FOUND THEN
RAISE 'Duplicate key detected: %', NEW.key_path USING ERRCODE = 'unique_violation';
END IF;
--Great! Our new row is able to be inserted
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER test_keys_validator BEFORE INSERT OR UPDATE ON test_keys
FOR EACH ROW EXECUTE PROCEDURE validate_key_path();
--Creating a index to speed up our validation...
CREATE INDEX idx_test_keys_key_path ON test_keys USING GIST (key_path);
--The command below will work
INSERT INTO test_keys VALUES (2, 'a.b.b');
--And the commands below will fail
INSERT INTO test_keys VALUES (3, 'a.b');
INSERT INTO test_keys VALUES (4, 'a.b.c');
INSERT INTO test_keys VALUES (5, 'a.b.c.d');
Run Code Online (Sandbox Code Playgroud)
当然,我不必为此测试创建主键和其他约束。但是不要忘记这样做。另外,ltree模块上的内容比我展示的要多得多,如果您需要其他功能,请查看其文档,也许您会在这里找到答案。