SQL - уникальный ключ для двух столбцов одной таблицы? - PullRequest
0 голосов
/ 23 мая 2018

Я использую SQL Server 2016. У меня есть таблица базы данных с именем «Member».

В этой таблице у меня есть 3 столбца (для целей моего вопроса):

  • idMember [INT - личность - первичный ключ]
  • memEmail
  • memEmailPartner

Я хочу запретить строке использовать электронную почту, которая уже существует в таблице.

Оба столбца электронной почты не являются обязательными, поэтому их можно оставить пустыми (NULL).

Если я создаю нового участника:

Если не пусто, введенные значениядля "memEmail" и "memEmailPartner" (независимо) не должно быть найдено ни в каких других строках столбцов memEmail или memEmailPartner.

Поэтому, если я хочу создать строку с электронной почтой (dominic@email.com), я долженЯ не могу найти вхождения этого значения в memEmail или memEmailPartner.

Если я обновляю существующего члена:

, я не должен находить вхождения этого значения в memEmail или memEmailPartner, за исключением того, что яя обновляю строку (idMembre) которыйh уже имеет значение в memEmail или memEmailPartner.

-

Из того, что я читаю в Google, должно быть возможно что-то сделать с функциональным ограничением проверки, но я не могузаставить это работать.

У кого-нибудь есть решение моей проблемы?

Спасибо.

Ответы [ 3 ]

0 голосов
/ 24 мая 2018

TL; DR

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

Игнорирование предупреждений

Тем не менее, я считаю, что то, что вы хотите, этооднако возможно в UDF-ограничении, хотя и с потенциально ужасными последствиями производительности * 1 , и, вероятно, склонен к условиям гонки в обновлениях на основе набора

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

Мой код также зависитдля поведения ANSI NULL, т. е. что предикаты NULL = NULL и X IN (NULL) оба возвращают NULL и, следовательно, исключаются из проверки на сбой (чтобы удовлетворить ваше требование, чтобы NULLS не нарушали правило).

Нам также нужно проверить, чтобы вставка ОБА новых столбцов отличалась от NULL, но дублировалась.

Итак, UDF выполняет проверку:

CREATE FUNCTION dbo.CheckUniqueEmails(@id int, @memEmail varchar(50), 
                                      @memEmailPartner varchar(50))
RETURNS bit
AS 
BEGIN
   DECLARE @retval bit;
   IF @memEmail = @memEmailPartner
     OR EXISTS (SELECT 1 FROM MyTable WHERE memEmail IS NOT NULL 
                AND memEmail IN(@memEmail, @memEmailPartner) AND idMember <> @id)
     OR EXISTS (SELECT 1 FROM MyTable WHERE memEmailPartner IS NOT NULL 
                AND memEmailPartner IN(@memEmail, @memEmailPartner) AND idMember <> @id)
     SET @retval = 0
   ELSE 
     SET @retval = 1;
  RETURN @retval;
END;
GO

, которая затем применяется вCHECK ограничение:

ALTER TABLE MyTable ADD CHECK (dbo.CheckUniqueEmails(
                                       idMember, memEmail, memEmailPartner) = 1);

Я поставил SQLFiddle здесь

Раскомментируйте «неудачные» тестовые случаи, чтобы убедиться, что вышеуказанное проверочное ограничение работает.

Я не проверял это с обновлениями, и, согласно совету Мартина по ссылке, это, вероятно, сломается при вставке с несколькими строками.

* 1 - нам нужны индексы на оба адреса электронной почтыстолбцы.

0 голосов
/ 24 мая 2018

Триггер - это традиционный способ сделать то, что вы просите.Вот простая демонстрация:

--if object_id('member') is not null drop table member
go

create table member (
    idMember INT Identity Primary Key,
    memEmail varchar(100),
    memEmailPartner varchar(100) 
)
go

create trigger trg_member on member after insert, update as
begin
    set nocount on

    if exists (select 1 from member m join inserted i on i.memEmail = m.memEmail and i.idMember <> m.idMember) or
       exists (select 1 from member m join inserted i on i.memEmail = m.memEmailPartner and i.idMember <> m.idMember) or
       exists (select 1 from member m join inserted i on i.memEmailPartner = m.memEmail and i.idMember <> m.idMember) or
       exists (select 1 from member m join inserted i on i.memEmailPartner = m.memEmailPartner and i.idMember <> m.idMember) 
    begin
        raiserror('Email addresses must be unique.', 16, 1)
        rollback
    end 
end
go

insert member(memEmail, memEmailPartner) values('a@a.com', null), ('b@b.com', null), (null, 'c@c.com'), (null, 'd@d.com')
go

select * from member

insert member(memEmail, memEmailPartner) values('a@a.com', null) -- should fail
go
insert member(memEmail, memEmailPartner) values(null, 'a@a.com') -- should fail
go
insert member(memEmail, memEmailPartner) values('c@c.com', null) -- should fail
go
insert member(memEmail, memEmailPartner) values(null, 'c@c.com') -- should fail
go

insert member(memEmail, memEmailPartner) values('e@e.com', null) -- should work
go
insert member(memEmail, memEmailPartner) values(null, 'f@f.com') -- should work
go

select * from member

-- Make sure updates still work!
update member set memEmail = memEmail, memEmailPartner = memEmailPartner

Я не тестировал это подробно, но этого должно быть достаточно для начала, если вы хотите попробовать этот подход.

StuartLC отмечает потенциал дляСбой проверки UDF в обновлениях на основе набора и / или в различных других условиях, триггеры не имеют этой проблемы.

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

0 голосов
/ 23 мая 2018

Я, возможно, неправильно понял, что именно вы спрашивали, но похоже, что вы хотите простой запрос upsert с условиями IF EXISTS.

DECLARE @emailAddress VARCHAR(255)= 'dominic@email.com', --dummy value
        @id INT= 2;                                      --dummy value

IF NOT EXISTS
(
   SELECT 1
   FROM @Member
   WHERE memEmail = @emailAddress
         OR memEmailPartner = @emailAddress
)
    BEGIN
        SELECT 'insert';
    END;
ELSE IF EXISTS 
(
  SELECT 1
  FROM @Member
  WHERE idMember = @id
)
    BEGIN
       SELECT 'update';
    END;
...