Уникальное ограничение в группе записей, где некоторое значение одинаково - PullRequest
8 голосов
/ 31 июля 2010

СУБД: MS Sql Server 2005, Standard

Я бы хотел сделать ограничение таблицы, чтобы только одна запись имела определенное значение в подмножестве таблицы (где строки разделяют значение в определенном столбце). Возможно ли это?

Пример: У меня есть записи в myTable, которые имеют неуникальный внешний ключ (fk1) и битовый столбец с именем isPrimary, чтобы отметить, что этот конкретный должен использоваться нашим приложением для специальной логики.

в аннотации это выглядит так:

myTable
-------------
pk1       (int, not null)
name      (varchar(50), null)
fk1       (int, not null)
isPrimary (bit, not null)

Я хочу убедиться, что существует одна и только одна запись с флагом isPrimary, установленным в 1, для каждого уникального значения fk1.

Пример данных: Это должно быть законно:

pk1     name     fk1    isPrimary
----    -----    -----  ----------
1       Bill     111    1
2       Tom      111    0
3       Dick     222    1
4       Harry    222    0

Но это должно быть , а не (более одного, где fk = 111):

pk1     name     fk1    isPrimary
----    -----    -----  ----------
1       Bill     111    1
2       Tom      111    1
3       Dick     222    1
4       Harry    222    0

И это тоже не должно (ни где fk = 222):

pk1     name     fk1    isPrimary
----    -----    -----  ----------
1       Bill     111    1
2       Tom      111    0
3       Dick     222    0
4       Harry    222    0

Есть ли способ сделать это с помощью ограничения таблицы?

UPDATE Я согласился с ответом Мартина Смита, хотя я буду настаивать на рефакторе JohnFx в следующем выпуске, так как это лучшее долгосрочное решение. Однако я хотел опубликовать свой обновленный UDF, основываясь на ответе Raze2dust, на случай, если будущие читатели решат, что он лучше соответствует их потребностям.

CREATE FUNCTION [dbo].[OneIsPrimaryPerFK1](@fk1 INT, @dummyIsPrimary BIT)
RETURNS INT
AS 
BEGIN
    DECLARE @retval INT;
    DECLARE @primarySum INT;
    SET @retval = 0;
    DECLARE @TempTable TABLE (
    fk1 INT,
    PrimarySum INT)

INSERT INTO @TempTable
    SELECT fk1, SUM(CAST(isPrimary AS INT)) AS PrimarySum
    FROM FacAdmin
    WHERE fk1 = @fk1
    GROUP BY fk1;

    SELECT @primarySum = PrimarySum FROM @TempTable;
    IF(@primarySum=1)
        BEGIN
            SET @retval = 1
        END
    RETURN @retval
END;

Изменения:

  1. Используется @tempTable вместо

    tempTable (в памяти v. Записано на диск) в соответствии с требованиями udf

  2. передал @ fk1 в качестве параметра, чтобы я мог выбрать уникальность в пределах одного группа значений fk1.
  3. хитрый должен был также передать isPrimary, хотя это не так необходимо для логики функция, иначе SQL2005 оптимизатор не запускает проверку ограничение, когда isPrimary обновлен.

Ответы [ 4 ]

9 голосов
/ 31 июля 2010

Начал новый ответ, так как я плохо исказил первый.

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

Как насчет удаления столбца IsPrimary из MyTable и добавления столбца PrimaryPersonID в другую таблицу, которая ссылается на основного пользователя?

Таким образом, сама структура будет обеспечивать, чтобы 1 и только 1 запись в таблице FK была первичной для каждого человека.

6 голосов
/ 31 июля 2010

SQL 2005 не предоставляет возможность применять предложение Where к уникальному индексу, такому как SQL 2008. Однако существует несколько способов решения проблемы в SQL 2005:

  1. Создайте индексированное представление, которое фильтрует IsPrimary = 1, и добавьте уникальный индекс в это представление.
  2. Создайте триггер, в котором вы убедитесь, что только один может быть основным.
  3. Инкапсулируйте вашу логику в сохраненный процесс и заставьте пользователей проходить через сохраненный процесс для вставки или обновления из вашей таблицы.
4 голосов
/ 31 июля 2010

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

Предполагая, что все ваши значения fk1 и pk1 в настоящее время (и всегда будут) положительными, вы можете создать вычисляемый столбец со следующим определением

CASE WHEN isPrimary = 1 THEN fk1 ELSE -pk1 END

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

CASE WHEN isPrimary = 0 THEN 1.0/pk1 ELSE fk1 END
3 голосов
/ 31 июля 2010

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

CREATE FUNCTION ChkFn()
RETURNS INT
AS 
BEGIN
   DECLARE @retval INT
   DECLARE @distinct INT
   DECLARE @top INT
   SET @retval = 0
   SELECT fk1 AS ForeignKey, SUM(isPrimary) AS PrimarySum
    INTO #TempTable 
    FROM myTable
    GROUP BY fk1
   SELECT @distinct = COUNT(DISTINCT(PrimarySum)) FROM #TempTable
   SELECT @top = top PrimarySum FROM #TempTable
   IF(@distinct=1 AND @top=1)
    BEGIN
    @retval = 1
    END
   RETURN @retval
END;
GO

ALTER TABLE myTable
ADD CONSTRAINT chkFkPk CHECK (dbo.ChekFn() = 1 );
GO

Попробуйте и дайте мне знать, если это сработало. Не очень элегантно, хотя ..

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