确保为集合定义了一个且仅有一个默认值

时间:2013-07-17 15:49:16

标签: sql-server

我有一个与地址表有一对多关系的客户表。 我想约束数据库,以便 地址的客户始终拥有一个(且只有一个)默认地址。

我可以很容易地添加一个约束,以确保每个客户只有一个默认地址。然而,我正在努力应用如何应用约束来确保地址始终标记为默认值。

总结:

  • 客户无需拥有任何地址。
  • 如果客户有地址,则必须有默认地址。
  • 每位客户必须只有一个默认地址。

以下是问题和一些“单元”测试的示例。我正在使用链接表来加入客户和地址。

CREATE TABLE Customer
(
    Id INT PRIMARY KEY,
    Name VARCHAR(100) NOT NULL
)

CREATE TABLE [Address]
(
    Id INT PRIMARY KEY,
    Address VARCHAR(500) NOT NULL
)

CREATE TABLE CustAddress
(
    CustomerId INT,
    AddressId INT,
    [Default] BIT NOT NULL,
    FOREIGN KEY (CustomerId) REFERENCES Customer(Id),
    FOREIGN KEY (AddressId) REFERENCES [Address](Id)
)

INSERT INTO Customer VALUES (1, 'Mr Greedy')

INSERT INTO [Address] VALUES (1, 'Roly-Poly House, Fatland')
INSERT INTO [Address] VALUES (2, 'Giant Cottage, A Cave')

-- Should succeed
INSERT INTO CustAddress VALUES (1, 1, 1)
INSERT INTO CustAddress VALUES (1, 2, 0)

DELETE FROM CustAddress

-- Should fail as no default address set
INSERT INTO CustAddress VALUES (1, 1, 0)

DELETE FROM CustAddress

-- Should fail as we end up with no defualt address set
INSERT INTO CustAddress VALUES (1, 1, 1)
INSERT INTO CustAddress VALUES (1, 2, 0)
UPDATE CustAddress SET [Default] = 0 WHERE CustomerId = 1 AND AddressId = 1

DELETE FROM CustAddress

-- Should fail as we end up with no defualt address set
INSERT INTO CustAddress VALUES (1, 1, 1)
INSERT INTO CustAddress VALUES (1, 2, 0)
DELETE FROM CustAddress WHERE CustomerId = 1 AND AddressId = 1

3 个答案:

答案 0 :(得分:5)

如何将架构更改为

CREATE TABLE Customer
(
    Id INT PRIMARY KEY,
    Name VARCHAR(100) NOT NULL
)

CREATE TABLE [Address]
(
    Id INT PRIMARY KEY,
    Address VARCHAR(500) NOT NULL
)


CREATE TABLE CustDefaultAddress
(
    CustomerId INT PRIMARY KEY, /*Ensures no more than one default*/
    AddressId INT,
    FOREIGN KEY (CustomerId) REFERENCES Customer(Id),
    FOREIGN KEY (AddressId) REFERENCES [Address](Id)
)


CREATE TABLE CustSecondaryAddress
(
    CustomerId INT REFERENCES CustDefaultAddress(CustomerId), 
                   /*No secondary address can be added unless default one exists*/
    AddressId INT REFERENCES [Address](Id),
    PRIMARY KEY(CustomerId, AddressId)
)

如果另外要求地址不能同时作为主地址和辅助地址存在,则可以使用帮助程序表和索引视图强制执行此操作。

CREATE TABLE dbo.TwoRows
  (
     X INT PRIMARY KEY
  );

INSERT INTO dbo.TwoRows
VALUES      (1),
            (2)

GO

CREATE VIEW V
WITH SCHEMABINDING
AS
  SELECT D.AddressId,
         D.CustomerId
  FROM   dbo.CustDefaultAddress D
         JOIN dbo.CustSecondaryAddress S
           ON D.AddressId = S.AddressId
              AND D.CustomerId = S.CustomerId
         CROSS JOIN dbo.TwoRows

GO

CREATE UNIQUE CLUSTERED INDEX IX
  ON V(AddressId, CustomerId) 

答案 1 :(得分:4)

如果我没有错过这些要求,我认为您可以使用instead of trigger强制执行相同的条件。

它不像表设计解决方案那么优雅,它需要更复杂的触发器,我更喜欢触发器,但会通过所有当前的测试。

它实际上是做什么的:

  • 在插入或更新的情况下,它实际上将验证整个数据集(旧的和新的对,以查看每个客户是否只有一个(通知默认位的总和)默认。如果有0或更多超过1默认值会引发错误。
  • 如果是删除,它将仅验证每个客户的剩余地址是否具有相同的规则(仅在默认情况下,在地址中存在)。
  • 最后,如果没有错误,它将执行它应该做的相同操作;

对您的表和数据起作用的触发器如下所示:

CREATE TRIGGER dbo.CustAddress1DefaultAddress
    ON  dbo.CustAddress
    Instead of INSERT, DELETE, UPDATE
AS 
BEGIN
    SET NOCOUNT ON;

    declare @cnt int, @operation char(1);
    IF exists (select * from inserted)
    and not exists (select * from deleted) --only insert, no delete/update
        select @operation = 'I';
    else if exists (select * from inserted)
        and exists (select * from deleted) --update
        Select @operation = 'U';
    else
        Select @operation = 'D';
    print 'operation = ' + @operation;

    begin try
    if @operation in ('I', 'U')
    begin
        ;with defaultsPerCustAdd(SumDefault, CustomerId)
        as (
            select sum (x.[Default]), x.CustomerId
            from (
                select i.CustomerId, cast(i.[Default] as tinyint) as [Default]
                from inserted as i
                union all
                select ca.CustomerId, cast(ca.[Default] as tinyint) as [Default]
                from dbo.CustAddress as ca
                join inserted i on i.CustomerId = ca.CustomerId
                and i.AddressId != ca.AddressId
            ) as x
            group by x.CustomerId
        )
        select *
        from defaultsPerCustAdd as d
        where d.SumDefault = 0
        OR d.SumDefault > 1;
        set @cnt = @@ROWCOUNT;
    end
    else -- Delete
    begin
        ;with defaultsPerCustAdd(SumDefault, CustomerId)
        as (
            select sum (x.[Default]), x.CustomerId
            from (
                select ca.CustomerId, cast(ca.[Default] as tinyint) as [Default]
                from dbo.CustAddress as ca
                join deleted d on d.CustomerId = ca.CustomerId
                and d.AddressId != ca.AddressId
            ) as x
            group by x.CustomerId
        )
        select *
        from defaultsPerCustAdd as d
        where d.SumDefault = 0
        OR d.SumDefault > 1;
        set @cnt = @@ROWCOUNT;
    end;

    if @cnt > 0
        raiserror('error when validating one default address per customer', 16, 1)

    if @operation = 'I'
        insert dbo.CustAddress(CustomerId, AddressId, [Default])
        select i.CustomerId, i.AddressId, i.[Default]
        from inserted as i
    else if @operation = 'U'
        update ca
        set [default] = i.[default]
        from dbo.CustAddress as ca
        join inserted as i on i.AddressId = ca.AddressId and i.CustomerId = ca.CustomerId
    else if @operation = 'D'
        delete ca
        from dbo.CustAddress as ca
        join deleted as d on d.AddressId = ca.AddressId and d.CustomerId = ca.CustomerId

    end try
    begin catch
        print 'error when validating one default address per customer';
    end catch;
END
GO

答案 2 :(得分:0)

破折号提示检查约束

  

类似的东西(select count(*)from tableid where customerid =   @customerid和default = 1)= 1

可以使用,所以我创建了这个答案。

CREATE FUNCTION NumberOfCustomerDefaultAddresses
(
    @CustomerId INT
)
RETURNS INT
AS
BEGIN
    RETURN (
        SELECT COUNT(*)
        FROM CustAddress
        WHERE CustomerId = @CustomerId
        AND [Default] = 1
    )
END
GO

ALTER TABLE CustAddress ADD CONSTRAINT CHK_DefaultAddress CHECK (dbo.NumberOfCustomerDefaultAddresses(CustomerId) = 1)

这适用于它会停止插入,导致不设置默认地址。但失败检测更改默认标志的更新并删除删除默认记录。