我有一个与地址表有一对多关系的客户表。 我想约束数据库,以便 地址的客户始终拥有一个(且只有一个)默认地址。
我可以很容易地添加一个约束,以确保每个客户只有一个默认地址。然而,我正在努力应用如何应用约束来确保地址始终标记为默认值。
总结:
以下是问题和一些“单元”测试的示例。我正在使用链接表来加入客户和地址。
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
答案 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强制执行相同的条件。
它不像表设计解决方案那么优雅,它需要更复杂的触发器,我更喜欢触发器,但会通过所有当前的测试。
它实际上是做什么的:
对您的表和数据起作用的触发器如下所示:
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)
这适用于它会停止插入,导致不设置默认地址。但失败检测更改默认标志的更新并删除删除默认记录。