可选的唯一约束?

时间:2011-01-13 22:28:26

标签: sql sql-server sql-server-2008 database-design data-integrity

我正在设置一个SaaS应用程序,多个客户端将使用该应用程序输入数据。但是,我有一些客户A可能想要强制唯一的字段,客户B可能想要允许欺骗。显然,如果我要允许任何一个客户端有欺骗,那么该表可能没有唯一的约束。缺点是如果我想为某些客户强制执行一个独特的约束,我将不得不以其他方式解决它。

有没有人解决过这样的问题,如果有的话,需要注意哪些常见的解决方案和潜在的陷阱?

我认为检查任何可能的唯一标志的触发器可能是正确执行此操作的唯一方法。如果我依赖业务层,那么应该没有保证应用程序会在每次插入之前进行独特的检查。

SOLUTION:
首先我考虑了唯一索引,但由于它们不能进行任何类型的连接或查找,只能表达值,因此将其排除在外。每次添加客户端或更改客户端的唯一性首选项时,我都不想修改索引。

然后我研究了CHECK CONSTRAINTS,在一些鬼混之后,为一个客户能够选择为唯一的假设列构建了一个函数返回true。

以下是我以前的测试表,数据和功能 验证检查约束是否可以完成我想要的所有操作。

-- Clients Table
CREATE TABLE [dbo].[Clients](
    [ID] [int]  NOT NULL,
    [Name] [varchar](50) NOT NULL,
    [UniqueSSN] [bit] NOT NULL,
    [UniqueVIN] [bit] NOT NULL
) ON [PRIMARY]

-- Test Client Data
INSERT INTO Clients(ID, Name, UniqueSSN, UniqueVIN) VALUES(1,'A Corp',0,0)
INSERT INTO Clients(ID, Name, UniqueSSN, UniqueVIN) VALUES(2,'B Corp',1,0)
INSERT INTO Clients(ID, Name, UniqueSSN, UniqueVIN) VALUES(3,'C Corp',0,1)
INSERT INTO Clients(ID, Name, UniqueSSN, UniqueVIN) VALUES(4,'D Corp',1,1)

-- Cases Table
CREATE TABLE [dbo].[Cases](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [ClientID] [int] NOT NULL,
    [ClaimantName] [varchar](50) NOT NULL,
    [SSN] [varchar](12) NULL,
    [VIN] [varchar](17) NULL
) ON [PRIMARY]

-- Check Uniques Function
CREATE FUNCTION CheckUniques(@ClientID int)
RETURNS int -- 0: Ok to insert, 1: Cannot insert
AS
BEGIN
    DECLARE @SSNCheck int
    DECLARE @VinCheck int
    SELECT @SSNCheck = 0
    SELECT @VinCheck = 0
    IF (SELECT UniqueSSN FROM Clients WHERE ID = @ClientID) = 1
    BEGIN
        SELECT @SSNCheck = COUNT(SSN) FROM Cases cs WHERE ClientID = @ClientID AND (SELECT COUNT(SSN) FROM Cases c2 WHERE c2.SSN = cs.SSN) > 1
    END
    IF (SELECT UniqueVIN FROM Clients WHERE ID = @ClientID) = 1
    BEGIN
        SELECT @VinCheck = COUNT(VIN) FROM Cases cs WHERE ClientID = @ClientID AND (SELECT COUNT(VIN) FROM Cases c2 WHERE c2.VIN = cs.VIN) > 1
    END
    RETURN @SSNCheck + @VinCheck
END

-- Add Check Constraint to table
ALTER TABLE Cases
ADD Constraint chkClientUniques CHECK(dbo.CheckUniques(ClientID) = 0)

-- Now confirm constraint using test data

-- Client A: Confirm that both duplicate SSN and VIN's are allowed
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(1, 'Alice', '111-11-1111', 'A-1234')
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(1, 'Bob', '111-11-1111', 'A-1234')

-- Client B: Confirm that Unique SSN is enforced, but duplicate VIN allowed
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(2, 'Charlie', '222-22-2222', 'B-2345') -- Should work
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(2, 'Donna', '222-22-2222', 'B-2345') -- Should fail
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(2, 'Evan', '333-33-3333', 'B-2345') -- Should Work

-- Client C: Confirm that Unique VIN is enforced, but duplicate SSN allowed
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(3, 'Evan', '444-44-4444', 'C-3456') -- Should work
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(3, 'Fred', '444-44-4444', 'C-3456') -- Should fail
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(3, 'Ginny', '444-44-4444', 'C-4567') -- Should work

-- Client D: Confirm that both Unique SSN and VIN are enforced
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(4, 'Henry', '555-55-5555', 'D-1234') -- Should work
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(4, 'Isaac', '666-66-6666', 'D-1234') -- Should fail
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(4, 'James', '555-55-5555', 'D-2345') -- Should fail
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(4, 'Kevin', '555-55-5555', 'D-1234') -- Should fail
INSERT INTO Cases (ClientID, ClaimantName, SSN, VIN) VALUES(4, 'Lisa', '777-77-7777', 'D-3456') -- Should work

修改
不得不修改函数几次以在dupe检查中捕获NULL值,但现在看起来一切正常。

8 个答案:

答案 0 :(得分:3)

一种方法是使用CHECK约束而不是唯一。 此CHECK约束将由

的SCALAR函数支持
  1. 作为输入ClientID
  2. 针对查找表交叉引用ClientID以查看是否允许重复项(client.dups)
  3. 如果不允许,请检查表格中的重复项
  4. 这样的东西
    ALTER TABLE TBL ADD CONSTRAINT CK_TBL_UNIQ CHECK(dbo.IsThisOK(clientID)=1)
    

答案 1 :(得分:2)

如果您可以在表中为每个客户端标识行,则根据您的DBMS,您可以执行以下操作:

CREATE UNIQUE INDEX uq_some_col 
   ON the_table(some_column, other_column, client_id)
   WHERE client_id IN (1,2,3);

(以上内容适用于PostgreSQL,而我认为 SQL Server 2005)

缩小规模是,每次添加要求这些列唯一的新客户端时,您都需要重新创建该索引。

您可能也会在业务层中进行一些检查,主要是为了能够显示正确的错误消息。

答案 2 :(得分:2)

现在在Sql Server 2008上完全可以实现(在我的Sql Server 2008框中测试):

create table a
(
cx varchar(50)
);

create unique index ux_cx on a(cx) where cx <> 'US';

insert into a values('PHILIPPINES'),('JAPAN'),('US'),('CANADA');


-- still ok to insert duplicate US
insert into a values('US');

-- will fail here
insert into a values('JAPAN');

相关文章:http://www.ienablemuch.com/2010/12/postgresql-said-sql-server2008-said-non.html

答案 3 :(得分:1)

您可以使用此功能做一些事情,这取决于您希望何时/如何处理此问题。

  1. 你可以使用CheckConstrain并修改它以根据使用它的客户端进行不同的查找
  2. 您可以使用业务层来处理此问题,但它不会保护您免受原始数据库更新的影响。
  3. 我个人发现#1可能太难维护,特别是如果你有大量的客户。我发现在业务层面上做这件事要容易得多,你可以在一个集中的位置控制它。

    Thee是其他选项,例如每个客户的表格以及其他可以起作用的表格,但这两个选项至少是我见过的最常见的。

答案 4 :(得分:1)

您可以添加辅助列。该列将等于允许重复的应用程序的主键,以及另一个应用程序的常量值。然后在UniqueHelper, Col1上创建一个唯一约束。

对于非欺骗客户端,它将在辅助列中有一个常量,强制该列是唯一的。

对于dupe列,辅助列等于主键,因此该列仅满足唯一约束。该应用程序可以添加任意数量的欺骗。

答案 5 :(得分:1)

一种可能性是使用BEFORE INSERT和BEFORE UPDATE触发器,它们可以选择性地强制执行唯一性。

另一种可能性(一种kludge)将是一个额外的虚拟字段,其中填充了一个客户的唯一值和另一个客户的重复值。然后在虚拟字段和可见字段的组合上构建唯一索引。

答案 6 :(得分:0)

@Neil。我在上面的评论中已经问过你将所有内容放在同一个表格中的原因是什么,你只是忽略了我评论的那个方面,并说一切都“简单明了”。您真的想听听萨斯环境中条件约束方法的缺点吗?

您没有说出您的Saas应用程序中最终需要合并多少套不同的规则。只有两种变化吗?

表现是一个考虑因素。虽然每个客户都可以访问专用的条件索引|索引,但是从其他客户的数据添加到表并且表增长时,从基表中获取数据可能会变得越来越慢。

如果我正在开发Saas应用程序,我会在适当的时候使用专用的事务表。客户可以共享标准表,如zipcodes,县,甚至共享特定于域的表,如Products或Categories或WidgetTypes等等。我可能在存储过程中构建动态SQL语句,其中选择了当前客户的正确表并将其放置在正在构造的语句中,例如,

        sql = "select * from " + DYNAMIC_ORDERS_TABLE + " where ... ")

如果由于动态语句必须一直编译而导致性能下降,我可能会考虑编写一个专用的存储过程生成器:sp_ORDERS_SELECT_25_v1.0 {其中“25”是分配给Saas特定用户的id应用程序,并且有一个版本后缀}。

您将不得不使用一些动态SQL,因为客户ID必须附加到每个 ad hoc 查询的WHERE子句中,以便利用您的条件索引:

        sql = " select * from orders  where ... and customerid = " + CURRENT_CUSTOMERID

您的条件索引涉及您的客户/用户列,因此必须将该列作为每个查询的一部分,以确保只从该表中选择该客户的行子集。

所以,当所有的事情都说完了,你真的可以节省自己创建专用表所需的工作量,并避免在你的面包和黄油查询中使用一些动态SQL。为面包和黄油查询编写动态SQL并不需要花费太多精力,而且它必须比在同一个共享表上管理多个客户特定索引更加混乱。如果您正在编写动态SQL,您可以轻松地替换专用表名作为每个查询附加customerid = 25子句。动态SQL的性能损失将被专用表的性能增益所抵消。

P.S。假设您的应用已经运行了一年左右,而且您有多个客户,而且您的桌面已经变大了。您希望将另一个客户及其新的客户特定索引集添加到大型生产表中。您可以在正常工作时间内汇总这些新索引和约束,还是必须在使用相对较轻的时候安排创建这些索引?

答案 7 :(得分:-1)

您不清楚将来自不同Universe的数据混合在同一个表中会带来什么好处。

唯一性约束是实体定义的一部分,每个实体都需要自己的表。我会创建单独的表格。