【问题标题】:Optional Unique Constraints?可选的唯一约束?
【发布时间】:2011-01-13 22:28:25
【问题描述】:

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

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

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

解决方案:
首先我考虑了唯一索引,但排除了它,因为它们不能进行任何类型的连接或查找,只能表达值。而且我不想在每次添加客户端或更改客户端的唯一性偏好时都修改索引。

然后我查看了 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

编辑:
不得不修改函数几次以在重复检查中捕获 NULL 值,但现在似乎一切正常。

【问题讨论】:

  • 什么样的实体可以被复制并且仍然有效?
  • @Ronnis:我认为他不是在谈论重复的 ,而是在谈论一组特定的列是否在一行中 可以在特定客户的行中重复。

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


【解决方案1】:

一种方法是使用 CHECK 约束而不是唯一的。 此 CHECK 约束将由一个 SCALAR 函数支持,该函数将

  1. 以 ClientID 作为输入
  2. 对照查找表交叉引用 ClientID 以查看是否允许重复 (client.dups)
  3. 如果不允许,请检查表中的重复项

类似

ALTER TABLE TBL ADD CONSTRAINT CK_TBL_UNIQ CHECK(dbo.IsThisOK(clientID)=1)

【讨论】:

  • 这正是最终的工作。调用标量函数的检查约束,该函数根据特定的客户端偏好检查唯一性。
【解决方案2】:

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

创建唯一索引 uq_some_col ON the_table(some_column, other_column, client_id) WHERE client_id IN (1,2,3);

(以上对 PostgreSQL 有效,并且我 认为 SQL Server 2005)

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

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

【讨论】:

  • 为什么我必须每次都重新创建它?我不能进行查找,并将您的示例更改为 WHERE client_id IN (SELECT id FROM Clients WHERE someFlag = true) 吗?
  • 不,我认为您不能在 WHERE 子句中使用子选择。即使可以,它也不会改变任何东西,因为当底层子选择发生变化时不会重新创建索引
【解决方案3】:

现在在 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

【讨论】:

    【解决方案4】:

    你可以做一些事情,这取决于你想何时/如何处理。

    1. 您可以使用 CheckConstrain 并修改它以根据使用它的客户端进行不同的查找
    2. 您可以使用业务层来处理此问题,但它不会保护您免受原始数据库更新的影响。

    我个人发现 #1 可能很难维护,尤其是在您拥有大量客户的情况下。我发现在业务层面做这件事要容易得多,而且你可以在一个集中的位置控制它。

    还有其他选项,例如每个客户一张桌子,以及其他也可以使用的选项,但这两个至少是我见过的最常见的。

    【讨论】:

    • 为什么#1 很难维护?如果我的检查约束查看一个表,该表指定了客户希望哪些列是唯一的,并且该表由客户自己(通过他们的管理面板)填充,这不会完美地扩展吗?
    • 是/否,记住你必须在那个时候对每一个进行查找以查看值是否存在。不是太大,但考虑“添加”新列等可能会增加维护。
    【解决方案5】:

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

    对于非重复的客户端,它会在帮助列中有一个常量,强制该列是唯一的。

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

    【讨论】:

      【解决方案6】:

      一种可能是使用可以选择性地强制唯一性的 BEFORE INSERT 和 BEFORE UPDATE 触发器。

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

      【讨论】:

        【解决方案7】:

        @尼尔。我在上面的评论中问过您将所有内容放在同一张表中的原因是什么,您只是忽略了我评论的这一方面,并​​说一切都“简单明了”。您真的想听听 Saas 环境中条件约束方法的缺点吗?

        您没有说明您的 Saas 应用程序中的该表最终可能需要合并多少组不同的规则。会不会只有两种变体?

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

        如果我正在开发 Saas 应用程序,我会在适当的地方使用专用事务表。客户可以共享邮政编码、县等标准表格,甚至可以共享特定领域的表格,如产品或类别或 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 的性能损失将被专用表的性能提升所抵消。

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

        【讨论】:

        • 索引不会受到影响。我不打算改变任何索引。只有少数列的独特要求。
        • 对不起,仍然没有正当的理由为每个客户制作一张新桌子来满足这个小要求。如果必须(就像我在 OP 中所说的那样),我将在插入业务层之前进行唯一检查。
        • 您认为数据库将如何实现唯一性?带索引。所以索引会受到影响。并且随着每个新客户端的出现,必须创建一个新索引来支持 条件 唯一性( .... create index .... where clientid = x)。具有讽刺意味的是,你不知道自己在做什么,却对我的回复投了反对票。
        【解决方案8】:

        您不清楚将来自不同领域的数据混合在同一个表中有什么好处。

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

        【讨论】:

        • 这里没有“不同的宇宙”。简单明了:有些客户希望它独一无二,有些则不
        • 将不同的实体放在同一个表中有什么好处?你还没有证明这个选择是合理的。仅仅是程序员的懒惰吗?客户可以有他们想要的,事情可以正确地完成。
        • @Tim:这似乎是一个非常苛刻(而且非常不成熟)的判断电话;如果客户都使用同一个系统,那么将他们的数据存储在一个公共存储库中,而不是在没有充分理由的情况下维护多个结构相同的存储库,这似乎不是“良好设计的祸根”。
        • 在关系方面,将所有客户数据放在一个表中对我来说非常有意义。在 E/R 建模方面可能不那么重要。 Tim 正在使用 E/R 建模语言,但 E/R 建模并不适合所有情况——它通常假设数据库可以简化为仅一组非常简单的约束。关系模型比这更强大。然而,大多数 SQL DBMS 对复杂约束的支持非常有限,尤其是引用约束。您的软件的局限性可能是决定因素。
        • "将不同的实体放在同一个表中有什么好处?"它们不是不同的实体,它们是完全相同的东西,所有列、数据类型等都是相同的。唯一的区别是,一些客户可能希望在某个列上强制执行唯一性(例如 VIN 或 SSN),而其他客户则可以接受欺骗。
        猜你喜欢
        • 2016-08-29
        • 1970-01-01
        • 2018-02-02
        • 1970-01-01
        • 2015-03-06
        • 2012-10-27
        • 2019-09-25
        • 2021-01-22
        • 2015-06-07
        相关资源
        最近更新 更多