null列ではなく1ビットの列を持つテーブルを作成していますIsDefault
。UserId
(同じテーブルのフィールド)ごとにデフォルト値が1つだけになるようにする制約を作成する必要があります。デフォルト以外の値が多数ある可能性があるため、これに一意性制約を使用することはできません。
MS SQL Server 2008を使用してこれを行うための最良のアプローチは何ですか?
ありがとう。
null列ではなく1ビットの列を持つテーブルを作成していますIsDefault
。UserId
(同じテーブルのフィールド)ごとにデフォルト値が1つだけになるようにする制約を作成する必要があります。デフォルト以外の値が多数ある可能性があるため、これに一意性制約を使用することはできません。
MS SQL Server 2008を使用してこれを行うための最良のアプローチは何ですか?
ありがとう。
私が見る最も簡単な方法は、UDF(ユーザー定義関数)を使用したチェック制約です。
たとえば、ここを見てください。 http://sqljourney.wordpress.com/2010/06/25/check-constraint-with-user-defined-function-in-sql-server/
テストされていない例
CREATE FUNCTION dbo.CheckDefaultUnicity(@UserId int)
RETURNS int
AS
BEGIN
DECLARE @retval int
SELECT @retval = COUNT(*) FROM <your table> where UserId = @UserId and <columnwithDefault> = 1-- or whatever is your default value
RETURN @retval
END;
GO
テーブルを変更します
ALTER TABLE <yourTable>
ADD CONSTRAINT Ck_UniqueDefaultForUser
CHECK (dbo.CheckDefaultUnicity(UserId) <2)
もう1つの比較的単純なオプションは、を使用することCLUSTERED INDEXED VIEW
です。これの要点は
UserID
ビュー内のテーブルからすべてを選択しIsDefault=1
ます。UserID
クラスター化インデックスビュー
CREATE VIEW dbo.VIEW_Users_IsDefault WITH SCHEMABINDING AS
SELECT UserID, IsDefault
FROM dbo.Users
WHERE IsDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_USERS_ISDEFAULT
ON dbo.VIEW_Users_IsDefault (UserID)
GO
テストスクリプト
BEGIN TRAN
CREATE TABLE dbo.Users (UserID INT, IsDefault BIT)
GO
CREATE VIEW dbo.VIEW_Users_IsDefault WITH SCHEMABINDING AS
SELECT UserID, IsDefault
FROM dbo.Users
WHERE IsDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_USERS_ISDEFAULT ON dbo.VIEW_Users_IsDefault (UserID)
GO
INSERT INTO dbo.Users VALUES (1, 0)
INSERT INTO dbo.Users VALUES (1, 1)
INSERT INTO dbo.Users VALUES (1, 1) -- Fails because of clustered index
ROLLBACK TRAN
チェック制約は間違いなく機能しますが、私の意見では設計上の選択としては適切ではありません。その理由は、制約のUDFが次のようになるためです。
SELECT @Count = COUNT(UserId)
FROM User
WHERE IsDefault = 1
GROUP BY UserId
HAVING COUNT(UserId) > 1
IF @Count > 0
....'FAIL
これは2つの列に関係するため、テーブルレベルの制約である必要があり、レコードが多いほど、挿入/更新/削除にかかる時間が遅くなります。
より良いオプションは、ストアドプロシージャを介してのみそのテーブルへのアクセスを許可することです。そのため、挿入/更新の前に、非常に迅速に実行できます。
IF EXISTS(SELECT UserId FROM User where UserId = @UserId and IsDefault = 1)
挿入/更新/削除の前
ただし、ORMを使用している可能性があり、システムにストアドプロシージャを含めたくない場合があるため、テーブルのデザインを以下に変更できます。これは、
tblUser:UserId、FirstName、Suranameなど
tblUserDefault:UserId(一意の制約)
システムでIsDefaultが何を表しているのかわからないので、上記ではユーザーがデフォルトであるかどうかを想定しています。あなたがそれを参照として使うことができる人は誰でも。これにより、USPまたは恐ろしいテーブル全体のチェック制約(またはトリガー)を使用せずに制約を適用でき、適切なORMにマッピングできます。
はどうですかCHECK Constraints
。ここを参照してください:http://msdn.microsoft.com/en-us/library/ms188258 (v = sql.105).aspx
ALTER TABLE yourtable
ADD CONSTRAINT IsDefaultChecked CHECK (IsDefault = T );
この場合、トリガーを使用することをお勧めします。ユーザーがデフォルトを変更しているとき、トリガーは現在のユーザーの現在のデフォルトを自動的にfalseに反転させる可能性があります。
基本的に、AFTER挿入/更新トリガーを使用して、IsDefault値が1に設定されている挿入/更新のすべてのユーザーのIsDefault列を0に設定します。
CREATE TRIGGER dbo.tr_default
ON dbo.MyTable
AFTER INSERT, UPDATE
AS
if(exists(select * from inserted where IsDefault = 1)
begin
update dbo.MyTable
set IsDefault = 0
from inserted i
join dbo.MyTable t on i.userid = t.userid
where i.IsDefault = 1
and i.TheValue != t.TheValue
end
これまでのところ、どの回答にも問題はありませんがCHECK CONSTRAINTS
、TRIGGERS
少し逆の解決策のように思えます。
UserID
私はあなたのテーブルでテーブルへの外部キーであると仮定することしかできないUser
ので、デフォルトとしてマークするのではなく、デフォルトのCardIDを格納するためにUserテーブルに列を追加してみませんか?これにより、ユーザーがコストのかかるトリガー/制約なしに複数のデフォルトのCardIDを持つことは不可能になります。列をnull不可にすると、必要に応じてユーザーがデフォルトのCardIDを持たないようにすることもできなくなります。
トリガーと制約のソリューションの方が優れていると思いますが、ストアドプロシージャを介して挿入/更新を制御する場合、はるかに簡単なアプローチは、最初に競合する行を更新することです(新しいデフォルトが常に優先されると仮定します)。
ALTER PROCEDURE dbo.UserWhateverTable_<action>
@UserID INT,
@CardID INT
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.UserWhatever
SET IsDefault = 0
WHERE UserID = @UserID
AND CardID = @CardID
AND IsDefault = 1;
-- insert or update here
END
GO
実際、トリガーまたは制約で保護することに加えて(DMLプロシージャでビジネスロジックが100%明確になるように)これを行うことは悪い考えではないかもしれません(プロシージャの外部で更新が行われる場合をキャッチするため) 。