所以我只是在PostgreSQL中阅读Exclusion Constraints,我似乎找不到在位串上使用按位运算符的方法,我想知道它是否可行。
我的用例是我有一个name: text
列和一个value: bit(8)
列。我想创建一个基本上这样说的约束:
ADD CONSTRAINT route_method_overlap
EXCLUDE USING gist(name WITH =, value WITH &)
但是这不起作用,因为
运算符&(位,位)不是运算符族“gist_bit_ops”的成员
我认为这是因为bit_ops& operator不返回布尔值。但有没有办法做我想做的事情?有没有办法强制operator &
将其返回值强制转换为布尔值?
忘记版本号。这是在9.1.4上安装了“btree_gist”扩展,全部来自Ubuntu 12.04 repos。但版本并不重要。如果上游有修复/更新,我可以从repos安装。我还处于设计阶段。
答案 0 :(得分:3)
在您的编辑澄清时,您安装了扩展程序btree_gist
。没有它,该示例将在name WITH =
处失败。
CREATE EXTENSION btree_gist;
btree_gist
安装的运算符类涵盖了许多运算符。不幸的是,&
运算符不在其中。显然是因为它没有返回boolean
,这对于运营商来说是合格的。
我会使用b树多列索引(对于速度)和触发器的组合。考虑一下这个在PostgreSQL 9.1 上测试的演示:
CREATE TABLE t (
name text
,value bit(8)
);
INSERT INTO t VALUES ('a', B'10101010');
CREATE INDEX t_name_value_idx ON t (name, value);
CREATE OR REPLACE FUNCTION trg_t_name_value_inversion_prohibited()
RETURNS trigger AS
$func$
BEGIN
IF EXISTS (
SELECT 1 FROM t
WHERE (name, value) = (NEW.name, ~ NEW.value) -- example: exclude inversion
) THEN
RAISE EXCEPTION 'Your text here!';
END IF;
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
CREATE TRIGGER insup_bef_t_name_value_inversion_prohibited
BEFORE INSERT OR UPDATE OF name, value -- only involved columns relevant!
ON t
FOR EACH ROW
EXECUTE PROCEDURE trg_t_name_value_inversion_prohibited();
INSERT INTO t VALUES ('a', ~ B'10101010'); -- fails with your error msg.
在此方案中,扩展程序btree_gist
不。
检查约束不起作用。我引用the manual on CREATE TABLE
:
目前,
CHECK
表达式不能包含子查询,也不能引用 除当前行列之外的其他变量。
大胆强调我的:
应该表现得非常好,实际上比排除约束更好,因为b树索引的维护比GiST索引便宜。基本=
运算符的查找速度应该比使用&
运算符的假设查找速度快。
此解决方案不像排除约束那样安全,因为触发器可以更容易被规避 - 例如,在同一事件的后续触发器中,或者如果暂时禁用触发器。如果适用这些条件,请准备对整个桌子进行额外检查。
示例触发器仅捕获value
的反转。正如您在评论中澄清的那样,您实际上需要这样的条件:
IF EXISTS (
SELECT 1 FROM t
WHERE name = NEW.name
AND value & NEW.value <> B'00000000'::bit(8)
) THEN
这种情况稍贵,但仍然可以使用索引。上面的多列索引可以工作 - 无论如何你都需要它。或者,效率稍高,名称上的简单索引:
CREATE INDEX t_name_idx ON t (name);
正如您所评论的,每name
最多只能有8个不同的行,实际上更少。所以这应该还很快。
如果INSERT
性能至关重要,特别是如果许多尝试INSERT失败的情况,您可以执行更多操作:创建一个按value
预先汇总name
的实体化视图:
CREATE TABLE mv_t AS
SELECT name, bit_or(value) AS value
FROM t
GROUP BY 1
ORDER BY 1;
name
保证在这里是唯一的。我在PRIMARY KEY
上使用name
来提供我们之后的索引:
ALTER TABLE mv_t SET (fillfactor=90);
ALTER TABLE mv_t
ADD CONSTRAINT mv_t_pkey PRIMARY KEY(name) WITH (fillfactor=90);
然后你的INSERT
看起来像这样:
WITH i(n,v) AS (SELECT 'a'::text, B'10101010'::bit(8))
INSERT INTO t (name, value)
SELECT n, v
FROM i
LEFT JOIN mv_t m ON m.name = i.n
AND m.value & i.v <> B'00000000'::bit(8)
WHERE m.n IS NULL; -- alternative syntax for EXISTS (...)
fillfactor
仅在您的表获得大量更新时才有用。
更新TRIGGER AFTER INSERT OR UPDATE OF name, value OR DELETE
中实体化视图中的行以使其保持最新状态。必须将额外对象的成本与增益进行权衡。很大程度上取决于您的典型负载。