使用按位AND运算符对位串列进行排除约束

时间:2012-06-20 18:48:15

标签: postgresql indexing constraints bit-manipulation postgresql-9.1

所以我只是在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安装。我还处于设计阶段。

1 个答案:

答案 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.

应该表现得非常好,实际上比排除约束更好,因为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性能至关重要,特别是如果许多尝试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中实体化视图中的行以使其保持最新状态。必须将额外对象的成本与增益进行权衡。很大程度上取决于您的典型负载。