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 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
4

3 回答 3

6

如何将架构更改为

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) 
于 2013-07-18T10:23:57.217 回答
4

如果我没有错过要求,我认为您可以使用而不是 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
于 2013-07-18T12:35:48.863 回答
0

破折号建议检查约束

类似于 (select count(*) From table where customerid = @customerid and 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)

这样做的原因是它停止了不会导致没有设置默认地址的插入。但它无法检测到更改默认标志的更新和删除默认记录的删除。

于 2013-07-18T09:24:09.177 回答