Необязательные уникальные ограничения? - PullRequest
1 голос
/ 14 января 2011

Я настраиваю 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

EDIT:
Пришлось несколько раз модифицировать функцию, чтобы получить значения NULL в проверке на дублирование, но теперь все работает.

Ответы [ 8 ]

3 голосов
/ 14 января 2011

Один из подходов - использовать ограничение CHECK вместо уникального. Это ограничение CHECK будет поддерживаться функцией SCALAR, которая будет

  1. принять в качестве входных данных ClientID
  2. Перекрестный идентификатор клиента с таблицей поиска, чтобы узнать, разрешены ли дубликаты (client.dups)
  3. если не разрешено, проверьте наличие дубликатов в таблице

Что-то вроде

ALTER TABLE TBL ADD CONSTRAINT CK_TBL_UNIQ CHECK(dbo.IsThisOK(clientID)=1)
2 голосов
/ 14 января 2011

Теперь это вполне возможно на 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

2 голосов
/ 14 января 2011

Если вы можете определить строки в таблице для каждого клиента, в зависимости от вашей СУБД вы можете сделать что-то вроде этого:

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)

Уменьшение состоит в том, что вам нужно будет заново создавать этот индекс каждый раз, когда добавляется новый клиент, который требует, чтобы эти столбцы были уникальными.

Вероятно, у вас также будет несколько проверок на бизнес-уровне, в основном для отображения правильных сообщений об ошибках.

1 голос
/ 14 января 2011

Одной из возможностей может быть использование триггеров BEFORE INSERT и BEFORE UPDATE, которые могут выборочно обеспечивать уникальность.

И еще одна возможность (разновидность кладжа) будет иметь дополнительное фиктивное поле, заполненное уникальными значениями для одного клиента и дублирующими значениями для другого клиента. Затем создайте уникальный индекс для комбинации фиктивного поля и видимого поля.

1 голос
/ 14 января 2011

Вы можете добавить вспомогательный столбец.Столбец будет равен первичному ключу для приложения, которое допускает дублирование, и постоянному значению для другого приложения.Затем вы создаете уникальное ограничение для UniqueHelper, Col1.

Для клиента без дублирования в столбце помощника будет постоянная константа, что делает столбец уникальным.столбец, вспомогательный столбец равен первичному ключу, поэтому уникальное ограничение удовлетворяется только этим столбцом.Это приложение может добавить любое количество дубликатов.

1 голос
/ 14 января 2011

Есть несколько вещей, которые вы можете сделать с этим, только зависит от того, когда / как вы хотите с этим справиться.

  1. Вы можете использовать CheckConstrain и изменить это навыполнять различные поиски в зависимости от клиента, который его использовал
  2. Вы можете использовать бизнес-уровень для обработки этого, но он не защитит вас от необработанных обновлений базы данных.

У меня лично естьобнаружил, что № 1 может быть слишком сложно поддерживать, особенно если вы получаете большое количество клиентов.Я обнаружил, что делать это на бизнес-уровне намного проще, и вы можете управлять им в централизованном месте.

Есть и другие варианты, такие как таблица для каждого клиента и другие, которые также могут работать, но эти два, по крайней мере, наиболее распространенные, которые я видел.

0 голосов
/ 14 января 2011

@ Нил. Я спросил в своем комментарии выше, каковы ваши причины для того, чтобы поместить все в одну таблицу, а вы просто проигнорировали этот аспект моего комментария и сказали, что все «просто и понятно». Вы действительно хотите услышать о недостатках подхода условных ограничений в контексте Saas?

Вы не говорите, сколько разных наборов правил может в итоге включить эта таблица в вашем приложении Saas. Будут ли только два варианта?

Производительность является соображением. Хотя каждый клиент будет иметь доступ к выделенному условному индексу | индексы, выборка данных из базовой таблицы может становиться все медленнее и медленнее по мере добавления к ней данных от дополнительных клиентов и увеличения таблицы.

Если бы я занимался разработкой приложения Saas, я бы использовал специальные таблицы транзакций, где это уместно. Клиенты могут делиться стандартными таблицами, такими как почтовые индексы, округа, и даже общими таблицами, относящимися к домену, такими как «Продукты», «Категории», «Виджеты» и т. Вероятно, я бы построил динамические операторы SQL в хранимых процедурах, в которых была выбрана правильная таблица для текущего клиента и помещена в создаваемый оператор, например

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

Если производительность снижалась из-за того, что динамические операторы приходилось все время компилировать, я мог бы подумать о написании выделенного генератора хранимых процедур: sp_ORDERS_SELECT_25_v1.0 {где "25" - это идентификатор, назначенный конкретному пользователю Saas приложение и есть суффикс версии}.

Вам нужно будет использовать некоторый динамический SQL, потому что идентификатор клиента должен быть добавлен к WHERE-предложению каждого из ваших ad hoc запросов, чтобы воспользоваться вашими условными индексами:

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

Ваши условные индексы включают ваш столбец клиент / пользователь, поэтому этот столбец должен быть частью каждого запроса, чтобы гарантировать, что из таблицы выбрано только то подмножество клиентов, которое указано в таблице.

Итак, когда все сказано и сделано, вы действительно экономите силы, необходимые для создания выделенной таблицы, и избегаете некоторого динамического SQL в ваших простых запросах. Написание динамического SQL для простых запросов не требует больших усилий, и это, безусловно, не так сложно, как управление несколькими индексами, специфичными для клиента, в одной общей таблице; и если вы пишете динамический SQL, вы можете так же легко заменить выделенное имя таблицы, как добавить к пункту customerid = 25 к каждому запросу. Потеря производительности динамического SQL будет более чем компенсирована увеличением производительности выделенных таблиц.

P.S. Допустим, ваше приложение работает в течение года или около того, и у вас есть несколько клиентов, и ваш стол стал большим. Вы хотите добавить другого клиента и его новый набор пользовательских индексов в большую производственную таблицу. Можете ли вы использовать эти новые индексы и ограничения в обычное рабочее время или вам придется планировать создание этих индексов на время, когда их использование относительно невелико?

0 голосов
/ 14 января 2011

Вы не ясно понимаете, какая польза от объединения данных из разных юниверсов в одной и той же таблице.

Ограничения уникальности являются частью определения сущности, и каждая сущность нуждается в своей собственной таблице.Я бы создал отдельные таблицы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...