回答:
編集:これは、MySQLを知る前にSQL Serverを対象としています。
これを行うには4つの 5つの方法があります。
ただし、すべてのRDBMSが当てはまるわけではありません
最適な方法は、フィルター選択されたインデックスです。これは、DRIを使用して一意性を維持します。
一意性を持つ計算列(ジャックダグラスの回答を参照)(編集2で追加)
DRIを使用したフィルター選択されたインデックスのようなインデックス付き/マテリアライズドビュー
トリガー(他の回答による)
UDFを使用して制約を確認します。これは、並行性とスナップショットの分離にとって安全ではありません。参照してください。一つ 二つ 三 つの
「1つのデフォルト」の要件は、ストアドプロシージャではなくテーブルにあることに注意してください。ストアドプロシージャには、udfを使用したチェック制約と同じ同時実行の問題があります。
注:何度も聞かれました:
注:この回答は、質問者がMySQL固有の何かを望んでいることが明らかになる前に与えられました。この答えはSQL Serverに偏っています。
UDFを呼び出すテーブルにCHECK制約を適用し、その戻り値が1以下であることを確認します。UDFはテーブルWHEREの行をカウントするだけです。これにより、テーブルにデフォルトの行が1つしか存在しないことが保証されます。isDefault = TRUE
このUDFが非常に高速に実行されるように、ビットマップまたはフィルター処理されたインデックスをisDefault
(プラットフォームに応じて)列に追加する必要があります。
注意事項
PostalCodes
テーブルの一括更新を実行することはほとんどありませんが、セットベースのアクティビティが発生する可能性が高い状況では、これがパフォーマンスの懸念事項のままです。これらのすべての警告を考えると、チェック制約の代わりにフィルター処理された一意のインデックスのgbnの提案を使用することをお勧めします。
ストアドプロシージャ(MySQL Dialect)は次のとおりです。
DELIMITER $$
DROP PROCEDURE IF EXISTS SetDefaultForZip;
CREATE PROCEDURE SetDefaultForZip (NEWID INT)
BEGIN
DECLARE FOUND_TRUE,OLDID INT;
SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
IF FOUND_TRUE = 1 THEN
SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
IF NEWID <> OLDID THEN
UPDATE PostalCode SET isDefault = FALSE WHERE ID = OLDID;
UPDATE PostalCode SET isDefault = TRUE WHERE ID = NEWID;
END IF;
ELSE
UPDATE PostalCode SET isDefault = TRUE WHERE ID = NEWID;
END IF;
END;
$$
DELIMITER ;
ID 200がデフォルトであると仮定して、テーブルがクリーンでストアドプロシージャが機能していることを確認するには、次の手順を実行します。
ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
CALL SetDefaultForZip(200);
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;
ストアドプロシージャの代わりに、トリガーはどうですか?
DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
DECLARE FOUND_TRUE,OLDID INT;
IF NEW.isDefault = TRUE THEN
SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
IF FOUND_TRUE = 1 THEN
SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
END IF;
END IF;
END;
$$
DELIMITER ;
ID 200がデフォルトであると仮定して、テーブルがクリーンでトリガーが機能していることを確認するには、次の手順を実行します。
DROP TRIGGER postalcodes_bu;
ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
DECLARE FOUND_TRUE,OLDID INT;
IF NEW.isDefault = TRUE THEN
SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
IF FOUND_TRUE = 1 THEN
SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
END IF;
END IF;
END;
$$
DELIMITER ;
UPDATE PostalCodes SET isDefault = TRUE WHERE ID = 200;
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;
トリガーを使用してルールを適用できます。UPDATEまたはINSERTステートメントがisDefaultをTrueに設定すると、トリガー内のSQLは他のすべての行をFalseに設定できます。
これを適用するときは、他の状況を考慮する必要があります。たとえば、複数の行があり、UPDATEまたはINSERTがisDefaultをTrueに設定するとどうなりますか?ルール違反を避けるためにどのルールが適用されますか?
また、デフォルトがない状態は可能ですか?更新がisDefaultをTrueからFalseに設定するとどうなりますか。
ルールを定義したら、トリガーでルールを作成できます。
次に、別の回答に示されているように、チェック制約を適用して、ルールの実施を支援します。
以下は、サンプルのテーブルとデータを設定します。
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MyTable]') AND type in (N'U'))
DROP TABLE [dbo].[MyTable]
GO
CREATE TABLE dbo.MyTable
(
[id] INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
, [IsDefault] BIT DEFAULT 0
)
GO
INSERT dbo.MyTable DEFAULT VALUES
GO 100
ストアドプロシージャを作成してIsDefaultフラグを設定し、ベーステーブルを変更するアクセス許可を削除する場合、以下のクエリでこれを強制できます。毎回テーブルスキャンが必要です。
DECLARE @Id INT
SET @Id = 10
UPDATE
dbo.MyTable
SET
IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
GO
テーブルのサイズによっては、IsDefault(DESC)のインデックスにより、以下のクエリのいずれかまたは両方が完全スキャンを回避する場合があります。
DECLARE @Id INT
SET @Id = 10
UPDATE
dbo.MyTable
SET
IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
dbo.MyTable
WHERE
[id] = @Id
OR IsDefault = 1
UPDATE
dbo.MyTable
SET
IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
dbo.MyTable
WHERE
[id] = @Id
OR ([id] != @Id AND IsDefault = 1)
ベーステーブルからアクセス許可を削除できず、他の方法で整合性を適用する必要があり、SQL2008を使用している場合は、フィルター処理された一意のインデックスを使用できます。
CREATE UNIQUE INDEX IX_MyTable_IsDefault ON dbo.MyTable (IsDefault) WHERE IsDefault = 1
フィルター選択されたインデックスを持たないMySQLの場合、次のようなことができます。MySQLは一意のインデックスで複数のNULLを許可するという事実を利用し、専用テーブルと外部キーを使用して、値としてNULLと1のみを持つことができるデータ型をシミュレートします。
このソリューションでは、true / falseの代わりに1 / NULLを使用します。
CREATE TABLE DefaultFlag (id TINYINT UNSIGNED NOT NULL PRIMARY KEY);
INSERT INTO DefaultFlag (id) VALUES (1);
CREATE TABLE PostalCodes (
ID INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
Zip VARCHAR (32) NOT NULL,
isDefault TINYINT UNSIGNED NULL DEFAULT NULL,
CONSTRAINT FOREIGN KEY (isDefault) REFERENCES DefaultFlag (id),
UNIQUE KEY (isDefault)
);
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('123', 1 );
/* Only one row can be default */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('abc', 1 );
/* ERROR 1062 (23000): Duplicate entry '1' for key 'isDefault' */
/* MySQL allows multiple NULLs in unique indexes */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('456', NULL);
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', NULL);
/* only NULL and 1 are admitted in isDefault thanks to foreign key */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', 2 );
/* ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`PostalCodes`, CONSTRAINT `PostalCodes_ibfk_1` FOREIGN KEY (`isDefault`) REFERENCES `DefaultFlag` (`id`))*/
間違っている可能性があるのは、誰かがDefaultFlag
テーブルに行を追加した場合だけだと思います。これは大きな問題ではないと思います。本当に安全にしたい場合は、いつでも権限で修正できます。
SELECT ID, Zip FROM PostalCodes WHERE isDefault=True
フィールドを間違った場所に置いたので、これは本質的に問題を引き起こしていました。それがすべてです。
探しているIDを保持するPostalCodeを含む「デフォルト」テーブルがあります。一般に、1つのレコードに従ってテーブルに名前を付けることも良いので、最初のテーブルの名前は 'PostalCode'と呼ばれる方が良いでしょう。デフォルトは、他の設定(履歴など)に対してデフォルトを特化する必要があるまで、単一行のテーブルになります。
最終的なクエリは次のとおりです。
select Zip from PostalCode p, Defaults d where p.ID=d.PostalCode;
PostalCodes
空の場合はどうですか?行にすでにプロパティが必要な場合、同じSQLステートメント内で別の行(存在する場合)がtrueに設定されていない限り、falseに設定されないようにしますか?トランザクションの境界の間にゼロ行のプロパティを設定できますか?テーブルの最後の行にプロパティを設定し、削除されないようにする必要がありますか?経験上、「正確に1行を保証する」とは、実際には異なるものを意味する傾向があり、多くの場合は単に「多くても1行」という意味です。