ユーザーを含むテーブルがあります。各ユーザーには、プライマリメールと、ユーザーが削除されているかどうかを示すフラグがあります(ユーザーを完全に削除することはありません)。
ただし、各ユーザーは追加のメールを受け取ることもできます。
いずれにせよ、電子メールアドレスは一意である必要があり、これをデータベースレベルで適用したいと思います。簡単なメインのメールアドレスの場合。 WHERE not is_deleted
制限付きのUNIQUEインデックスを追加するだけで、削除されたユーザーのメールを再利用できます。
ただし、より注意が必要な予備のメールについては、.
is_deleted
フラグをユーザーからそのテーブルに複製する必要がありますが、これも非常に醜いですが、WHERE not is_deleted
。私がやろうとしていることを達成するためのより良い解決策はありますか?
これを理解する1つの方法は、わずかに異なるルールを持つ2つのユーザークラスを持っているということです。削除されたユーザーの電子メールは衝突する可能性があり、削除されたユーザーの電子メールは一意でなければなりません。
これら2つのクラスには異なる規則(つまり、制約)があるため、ユーザーを削除するかどうかを示すフラグを使用する代わりに、テーブルを複製します。1つは削除されたユーザー用、もう1つは削除されていないユーザー用です。次に、削除されていないユーザーの電子メールのテーブルに一意の制約を作成します。
利点:
is_deleted
フラグを生成します)。CREATE INDEX...WHERE
に依存しません。私がこれに言及する理由は、部分インデックスが普遍的にサポートされていないためです(たとえば、MS-SQLおよびDB2)。明らかに、PostgreSQLに問題はありませんが、将来の移行の潜在的なハードルを回避することは決して害にはなりません。短所:
INSERT..SELECT FROM
のようなステートメントを構築するのに問題はありません。これにより、エントリを削除されていないものから削除されたものに(またはその逆に)移動することが簡単になります。一意のメールアドレスを適用するには、remove競合するすべてのメール列を1つの中央のemail
に格納しますすべてのアクティブなメールのテーブル。削除されたメールの別のテーブル:
_CREATE TABLE users (
user_id serial PRIMARY KEY
, username text UNIQUE NOT NULL
, email text UNIQUE -- FK added below -- can also be NOT NULL
);
CREATE TABLE email (
email text PRIMARY KEY
, user_id int NOT NULL REFERENCES users ON DELETE CASCADE
, UNIQUE (user_id, email) -- seems redundant, but required for FK
);
ALTER TABLE users ADD CONSTRAINT users_primary_email_fkey
FOREIGN KEY (user_id, email) REFERENCES email (user_id, email);
CREATE TABLE email_deleted (
email_id serial PRIMARY KEY
, email text NOT NULL -- not necessarily unique
, user_id int NOT NULL REFERENCES users ON DELETE CASCADE
);
_
こちらです:
email
のPK制約によって強制されます。email
から_email_deleted
_に移動します。users_primary_email_fkey
_が_(user_id, email)
_にまたがるように設計しましたが、最初は冗長に見えます。ただし、この方法では、メインのメールは、実際に同じユーザーが所有しているメールのみとなります。MATCH SIMPLE
_動作により、いずれかの列がnullの場合はFK制約が適用されないため、プライマリメールなしでユーザーを入力できます。_users.email
_のUNIQUE
制約はこのソリューションでは冗長ですが、他の理由で役立つ場合があります。自動的に作成されたインデックスが役立つはずです(この回答の最後のクエリなど)。
この方法で強制されない唯一のことは、everyユーザーがプライマリメールを持っているということです。あなたもこれを行うことができます。 _NOT NULL
_制約を_users.email
_に追加します
FK制約にはUNIQUE (user_id, email)
が必要です:
上記のモデルで循環参照を見つけたことは間違いありません。予想とは逆に、これはうまくいきます。
_users.email
_がNULL
である限り、それは自明です。
INSERT
ユーザー。user_id
_を参照するINSERT
メール。UPDATE
ユーザーがメインのメールアドレスを設定します。_users.email
_を_NOT NULL
_に設定しても機能します。ただし、ユーザーとメールを同時に挿入する必要があります。
_WITH u AS (
INSERT INTO users(username, email)
VALUES ('user_foo', '[email protected]')
RETURNING email, user_id
)
INSERT INTO email (email, user_id)
SELECT email, user_id
FROM u;
_
IMMEDIATE
FK制約(デフォルト)は、各ステートメントの最後でチェックされます。上記はoneステートメントです。これが、2つの別々のステートメントが失敗する場所で機能する理由です。詳細な説明:
ユーザーのすべてのメールを配列として取得するには、最初にメインのメールを送信します。
_SELECT u.*, e.emails
FROM users u
, LATERAL (
SELECT ARRAY (
SELECT email
FROM email
WHERE user_id = u.user_id
ORDER BY (email <> u.email) -- sort primary email first
) AS emails
) e
WHERE user_id = 1;
_
これを使用すると、使いやすくするためにVIEW
を作成できます。LATERAL
にはPostgres 9.3が必要です。 pg 9.2で相関サブクエリを使用します。
_SELECT *, ARRAY (
SELECT email
FROM email
WHERE user_id = u.user_id
ORDER BY (email <> u.email) -- sort primary email first
) AS emails
FROM users u
WHERE user_id = 1;
_
メールを一時削除するには:
_WITH del AS (
DELETE FROM email
WHERE email = '[email protected]'
RETURNING email, user_id
)
INSERT INTO email_deleted (email, user_id)
SELECT email, user_id FROM del;
_
特定のユーザーのプライマリ電子メールをソフト削除するには:
_WITH upd AS (
UPDATE users u
SET email = NULL
FROM (SELECT user_id, email FROM users WHERE user_id = 123 FOR UPDATE) old
WHERE old.user_id = u.user_id
AND u.user_id = 1
RETURNING old.*
)
, del AS (
DELETE FROM email
USING upd
WHERE email.email = upd.email
)
INSERT INTO email_deleted (email, user_id)
SELECT email, user_id FROM upd;
_
詳細:
上記すべてのクイックテスト: SQL Fiddle 。