_(content_md5 UUID, content TEXT)
_という構造のテーブルがあり、_content_md5
_(md5(content)
の値)を主キーとして使用し、外部キーとして使用したい他のテーブルのキー。
これは「静的」テーブルであり、コンテンツ(一部の大きなドキュメント)は、単純化のために_md5
_値によって参照され、テーブル内の重複を防ぎます(単純なSERIAL
では指定されません)。 PKEY)。
ただし、content
はNULL
にすることができます。これは、参照するテーブルに存在しないコンテンツフィールドを宣言する空の値とは異なります。md5(NULL)
はNULL
を返し、NULL
は主キー制約では使用できないため、md5(NULL)
がNULL
ではなくすべてゼロを返すようにしたい。
例:
_-- setup
CREATE TABLE example (content_md5 UUID PRIMARY KEY, content TEXT);
CREATE TABLE test (id SERIAL PRIMARY KEY, tags TEXT, content_md5 UUID REFERENCES example(content_md5) ON DELETE RESTRICT);
INSERT INTO example VALUES ('00000000000000000000000000000000'::uuid, NULL);
-- usage
INSERT INTO example VALUES (md5('some text')::uuid, 'some text');
INSERT INTO test (tags, content_md5) VALUES ('some content defining tags', md5('some text')::uuid);
SELECT tags, content FROM test LEFT JOIN example USING (content_md5);
-- QUESTION: Having an md5-like function to return zero-filled "md5"/uuid?
INSERT INTO example VALUES (md5(NULL)::uuid, NULL); -- ignored, because already existing record
INSERT INTO test (tags, content_md5) VALUES ('non-existing-document', md5(NULL)::uuid);
_
戻り値をゼロで埋められた文字列にキャストしたり、md5()
に基づいてカスタム関数を作成したりして、NULL
を_00000000000000000000000000000000
_に置き換えるなど、この結果を達成する方法はありますか?
/ edit:または、おそらくこのテーブルにNULL
値は必要ありません。参照する外部キー列をNULL
に設定するだけで同じ結果を得ることができますか?
私はこの代替設計を提案します:
_-- setup
CREATE TABLE example (content_id serial PRIMARY KEY, content text);
CREATE TABLE test (id serial PRIMARY KEY, tags TEXT, content_id int REFERENCES example);
CREATE UNIQUE INDEX ON example ((md5(content)::uuid)) INCLUDE (content_id); -- !
-- usage
INSERT INTO example(content) VALUES (NULL); -- allowed multiple times
INSERT INTO example(content) VALUES ('some text');
INSERT INTO test (tags, content_id)
SELECT 'some content defining tags', content_id
FROM example
WHERE md5(content)::uuid = md5('some text')::uuid;
_
db <> fiddle ここ
シリアル列(_content_id
_)をテーブルexample
のサロゲートPKとして、およびどこでもFK参照として使用します。 16ではなく4バイト。
式md5(example)::uuid
の一意のインデックスを使用して一意性を適用します。ハッシュの衝突が発生する可能性があることに注意してください(テーブルが大きくない場合は非常にまれです)。
そのとき、serial
PK列をINCLUDE
句を使用してインデックスに追加し(Postgres 11以降)、インデックスのみを高速に検索するためのカバーするインデックスにします。
PK列とは異なり、これはNULL
を許可し、NULL
はNULL
の複製とは見なされないため、ユースケースをカバーする必要があります。見る:
Postgres 10以前では、_content_id
_をインデックスに追加しないでください。もちろん、インデックスのみのスキャンは取得できません。
_CREATE UNIQUE INDEX ON example ((md5(content)::uuid));
_
以下の場合を除きNULLの単一インスタンスのみを許可します。これは、投稿したような関数で適用できます(衝突のリスクを導入-たとえありそうもない)または上記のものに加えて小さな部分インデックス:
_CREATE UNIQUE INDEX ON example (content_id)
WHERE md5(content)::uuid IS NULL;
_
見る:
Md5値をテーブル列として(冗長に)まったく保存しないでください。
もしあなたが投稿した関数を使い続けたい場合あなたの答え 、それを最適化することを検討してください:
_CREATE OR REPLACE FUNCTION pg_temp.md5zero(data text)
RETURNS uuid PARALLEL SAFE IMMUTABLE LANGUAGE sql AS
$func$
SELECT COALESCE(md5(data)::uuid, '00000000000000000000000000000000')
$func$
_
より速く、インライン化できます。見る:
あなたのリクエストは、主キーの概念を少し壊します-主キーを別の列に依存させる必要があります(他の列--content
-あなたの場合-主キーにしないでください)。それと同時に、その派生列を一意にする必要があります。この設定をすることは可能ですが、設計は混乱を招きます(つまり、将来のDBA /開発者は、設計の決定内容を解読する必要があるでしょう)。
また、md5()
はUUID
タイプを返しません(ただし、UUID
にキャストするつもりだと思います)。
そうは言っても、シーケンスと一緒にCOALESCE()
を使用できると思います。
edb=# create sequence abc_seq;
CREATE SEQUENCE
edb=# create table abc (content_md5 text primary key, content text);
CREATE TABLE
edb=# insert into abc values (md5(coalesce('mycontent',nextval('abc_seq')::text)),'mycontent');
INSERT 0 1
edb=# insert into abc values (md5(coalesce(null,nextval('abc_seq')::text)),null);
INSERT 0 1
edb=# select * from abc;
content_md5 | content
----------------------------------+-----------
c8afdb36c52cf4727836669019e69222 | mycontent
c4ca4238a0b923820dcc509a6f75849b |
(2 rows)
_ [にDEFAULT
を設定できないことにも注意してくださいcontent_md5
以下のため:
edb=# create table abc (content_md5 text primary key default md5(coalesce(content,nextval('abc_seq')::text)), content text);
ERROR: cannot use column references in default expression
自分のやりたいことを行うカスタム関数を作成する方法を見つけました。
これがそれを解決するための最良の方法であるかどうかはわかりませんが、私にとってはうまくいくので、次のようにします:
CREATE OR REPLACE FUNCTION md5zero(data text) RETURNS text AS $$
BEGIN
IF data IS NULL
THEN
RETURN '00000000000000000000000000000000';
ELSE
RETURN md5(data);
END IF;
END;
$$ LANGUAGE plpgsql;
-- TEST:
INSERT INTO example VALUES (md5zero(NULL)::uuid, NULL); -- ignored, because already existing record
-- ERROR: duplicate key value violates unique constraint "example_pkey"
-- DETAIL: Key (content_md5)=(00000000-0000-0000-0000-000000000000) already exists.
-- Time: 0.477 ms
INSERT INTO test (tags, content_md5) VALUES ('non-existing-document', md5zero(NULL)::uuid);
-- id | tags | content_md5
------+-----------------------+--------------------------------------
-- 4 | non-existing-document | 00000000-0000-0000-0000-000000000000