【问题标题】:Exclusion constraint on a bitstring column with bitwise AND operator使用按位 AND 运算符对位串列进行排除约束
【发布时间】:2012-06-20 18:48:15
【问题描述】:

所以我只是在 PostgreSQL 中阅读有关 Exclusion Constraints 的信息,我似乎找不到在位串上使用位运算符的方法,我想知道这是否可能。

我的用例是我有一个name: text 列和一个value: bit(8) 列。我想创建一个基本上这样说的约束:

ADD CONSTRAINT route_method_overlap
EXCLUDE USING gist(name WITH =, value WITH &)

但这不起作用,因为

算子 &(bit,bit) 不是算子家族“gist_bit_ops”的成员

我认为这是因为 bit_ops & 运算符不返回布尔值。但是有没有办法做我想做的事情?有没有办法强制 operator & 将其返回值转换为布尔值?

编辑

忘记版本号。这是在 9.1.4 上安装了“btree_gist”扩展,全部来自 Ubuntu 12.04 存储库。但版本无关紧要。如果上游有修复/更新,我可以从存储库安装。我仍处于设计阶段。

【问题讨论】:

  • 向您的版本号提供此类高级问题非常重要。

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


【解决方案1】:

您安装了扩展程序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 中更新具体化视图中的行以使其保持最新。必须仔细权衡额外对象的成本和收益。很大程度上取决于您的典型负载。

【讨论】:

  • 嗯,很有趣。这里使用触发器是为了提高速度吗?与说 CHECK 约束相反?我要防止的是位重叠。因此,对于具有相等name 字段的任何 2 列,val1 和 val2 必须 = B'00000000'。这就是为什么我认为排除约束会在这里起作用。所以我不认为在~ value 上建立索引是有建设性的。此外,在这种情况下,请记住,只有 8 列与匹配的 name 字段。
  • @Falmarri:我根据您的说明修改了我的答案,解决了检查限制并添加了更多内容。
  • 非常详尽的回答,谢谢。我认为更复杂的触发器会很好地工作,因为与选择相比,插入应该“相对”少见。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多