6

そのため、PostgreSQL の除外制約について読んでいたところ、ビット文字列でビットごとの演算子を使用する方法が見つからなかったようで、それが可能かどうか疑問に思っていました。

私のユースケースは、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 &戻り値をブール値としてキャストするように強制する方法はありますか?

編集

バージョン番号を忘れました。これは、すべてUbuntu 12.04リポジトリからの「btree_gist」拡張機能がインストールされた9.1.4にあります。しかし、バージョンは関係ありません。アップストリームに修正/更新がある場合は、リポジトリからインストールできます。私はまだこれの設計段階にあります。

4

1 に答える 1

6

拡張機能をインストールしましたbtree_gist。それがなければ、この例は ですでに失敗しname WITH =ます。

CREATE EXTENSION btree_gist;

によってインストールされる演算子クラスは、btree_gist多くの演算子をカバーしています。残念ながら、&オペレーターはその中にいません。boolean明らかに、修飾する演算子に期待されるa を返さないためです。

代替ソリューション

代わりに、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
  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();

見る:

~は逆演算子です。

このシナリオでは、拡張子btree_gistは必要ありません。

効率を高めるために、トリガーをINSERT OR UPDATE OF関連する列に制限しました。

チェック制約は機能しません。のマニュアルCREATE TABLEを引用します:

現在、式にサブクエリを含めることも、現在の行のCHECK列以外の変数を参照することもできません。

大胆強調鉱山。

bツリーインデックスのメンテナンスは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);

あなたは、 ごとに最大 8 つの異なる行しか存在できないとコメントしましたがname、実際にはそれより少なくなります。したがって、これはまだ速いはずです。

究極の INSERT パフォーマンス

パフォーマンスが最重要である場合INSERT、特に試行された INSERT の多くが条件に失敗した場合は、さらに多くのことができます: ごとに事前に集計されたマテリアライズド ビューを作成しますvaluename

CREATE TABLE mv_t AS 
SELECT name, bit_or(value) AS value
FROM   t
GROUP  BY 1
ORDER  BY 1;

nameここで一意であることが保証されています。PRIMARY KEYonを使用してname、目的のインデックスを提供します。

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状態に保ちます。追加オブジェクトのコストとゲインは慎重に検討する必要があります。典型的な負荷に大きく依存します。

于 2012-06-20T21:13:50.580 に答える