您安装了扩展程序btree_gist。没有它,该示例已经在name WITH = 失败。
CREATE EXTENSION btree_gist;
btree_gist 安装的算子类涵盖了很多算子。不幸的是,& 运算符不在其中。显然,因为它没有返回 boolean ,这是操作员应有的资格。
替代解决方案
我会使用 b-tree 多列索引(为了速度)和 触发器 的组合。考虑这个演示,在 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
LANGUAGE plpgsql AS
$func$
BEGIN
IF EXISTS (
SELECT FROM t
WHERE (name, value) = (NEW.name, ~ NEW.value) -- example: exclude inversion
) THEN
RAISE EXCEPTION 'Your text here!';
END IF;
RETURN NEW;
END
$func$;
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 FUNCTION trg_t_name_value_inversion_prohibited();
INSERT INTO t VALUES ('a', ~ B'10101010'); -- fails with your error msg.
在 Postgres 10 或更早版本中使用:
...
EXECUTE PROCEDURE trg_t_name_value_inversion_prohibited();
见:
~ is the inversion operator.
扩展名btree_gist 在这种情况下不需要。
为了提高效率,我限制了trigger to INSERT OR UPDATE OF relevant columns。
检查约束不起作用。我引用the manual on CREATE TABLE:
目前,CHECK 表达式不能包含子查询,也不能引用
当前行的列以外的变量。
我的大胆强调。
应该执行得很好,实际上比排除约束要好,因为维护 b-tree 索引比 GiST 索引便宜。使用基本= 运算符进行查找应该比使用& 运算符进行假设查找更快。
此解决方案不像排除约束那样万无一失,因为触发器可以更容易地被规避 - 例如,在同一事件的后续触发器中,或者如果触发器被暂时禁用。如果出现此类情况,请准备好对整张桌子进行额外检查。
更复杂的条件
示例触发器仅捕获 value 的反转。正如您在评论中澄清的那样,您实际上需要这样的条件:
IF EXISTS (
SELECT 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 都未能满足条件,您可以做更多:创建一个物化视图,根据 name 预先聚合 value:
CREATE TABLE mv_t AS
SELECT name, bit_or(value) AS value
FROM t
GROUP BY 1
ORDER BY 1;
name 在这里保证是唯一的。我会在name 上使用PRIMARY KEY 来提供我们所追求的索引:
ALTER TABLE mv_t SET (FILLFACTOR=90);
ALTER TABLE mv_t
ADD CONSTRAINT mv_t_pkey PRIMARY KEY(name);
那么您的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 中更新具体化视图中的行以使其保持最新。必须仔细权衡额外对象的成本和收益。很大程度上取决于您的典型负载。