1

我正在设置一个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 值,但现在一切似乎都在工作。

4

8 回答 8

3

One approach is to use a CHECK constraint instead of a unique. This CHECK constraint will be backed by a SCALAR function that will

  1. take as input ClientID
  2. cross-ref ClientID against a lookup table to see if duplicates are allowed (client.dups)
  3. if not allowed, check for duplicates in the table

Something like

ALTER TABLE TBL ADD CONSTRAINT CK_TBL_UNIQ CHECK(dbo.IsThisOK(clientID)=1)
于 2011-01-13T22:34:42.623 回答
2

If you can identify rows in the table for each client, depending on your DBMS you could do something like this:

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

(The above is valid for PostgreSQL and and I think SQL Server 2005)

The downsize is, that you will need to re-create that index each time a new client is added that requires those columns to be unique.

You will probably have some checks in the business layer as well, mostly to be able to show proper error messages.

于 2011-01-13T22:35:49.110 回答
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

于 2011-01-13T23:27:47.583 回答
1

There are a few things you can do with this, just depends on when/how you want to handle this.

  1. You could use a CheckConstrain and modify this to do different lookups based on the client that was using it
  2. You could use the business tier to handle this, but it will not protect you from raw database updates.

I personally have found that #1 can get too hard to maintain, especially if you get a high number of clients. I've found that doing it at the business level is a lot easier, and you can control it at a centralized location.

Thee are other options such as a table per client and others that could work as well, but these two are at least the most common that I've seen.

于 2011-01-13T22:33:16.597 回答
1

You could add a helper column. The column would be equal the the primary key for the application that allows duplicates, and a constant value for the other application. Then you create a unique constraint on UniqueHelper, Col1.

For the non-dupe client, it will have a constant in the helper column, forcing the column to be unique.

For the dupe column, the helper column is equal to the primary key, so the unique constraint is satisfied by that column alone. That application can add any number of dupes.

于 2011-01-13T22:35:15.613 回答
1

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

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

于 2011-01-13T22:36:11.683 回答
0

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

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

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

如果我正在开发一个 Saas 应用程序,我会在适当的地方使用专用的事务表。客户可以共享邮政编码、县等标准表格,甚至可以共享特定领域的表格,如产品或类别或 WidgetTypes 等。我可能会在存储过程中构建动态 SQL 语句,其中为当前客户选择正确的表并将其放置在正在构造的语句中,例如

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

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

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

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

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

因此,当一切都说完之后,您确实可以节省创建专用表所需的精力,并避免在基本查询中使用一些动态 SQL。为基本查询编写动态 SQL 并不需要花费太多精力,而且肯定比在同一个共享表上管理多个客户特定索引更容易;如果您正在编写动态 SQL,您可以轻松地将专用表名替换为将 customerid=25 子句附加到每个查询。动态 SQL 的性能损失将被专用表的性能增益所抵消。

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

于 2011-01-14T12:33:05.427 回答
-1

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

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

于 2011-01-13T22:49:12.247 回答