Ограничение только для одной записи, помеченной как значение по умолчанию - PullRequest
32 голосов
/ 12 марта 2009

Как я могу установить ограничение для таблицы, чтобы только для одной из записей в поле isDefault бит было установлено значение 1?

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

Ответы [ 10 ]

49 голосов
/ 03 апреля 2013

Использовать уникальный отфильтрованный индекс

В SQL Server 2008 или более поздней версии вы можете просто использовать уникальный отфильтрованный индекс

CREATE UNIQUE INDEX IX_TableName_FormID_isDefault
    ON TableName(FormID)
    WHERE isDefault = 1

Где таблица

CREATE TABLE TableName(
    FormID INT NOT NULL,
    isDefault BIT NOT NULL
)

Например, если вы попытаетесь вставить много строк с одинаковым FormId и isDefault, равным 1, у вас будет эта ошибка:

Невозможно вставить строку повторяющегося ключа в объект 'dbo.TableName' с уникальным индекс 'IX_TableName_FormID_isDefault'. Значение дубликата ключа (1).

Источник: http://technet.microsoft.com/en-us/library/cc280372.aspx

9 голосов
/ 13 марта 2009

Вот модификация решения Damien_The_Unbeliever, которая допускает одно значение по умолчанию для каждого FormID.

CREATE VIEW form_defaults
AS
SELECT FormID
FROM whatever
WHERE isDefault = 1
GO
CREATE UNIQUE CLUSTERED INDEX ix_form_defaults on form_defaults (FormID)
GO

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

CREATE TABLE form
FormID int NOT NULL PRIMARY KEY
DefaultWhateverID int FOREIGN KEY REFERENCES Whatever(ID)
8 голосов
/ 12 марта 2009

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

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

CREATE TABLE [dbo].[Foo](
    [Id] [int] NOT NULL,
 CONSTRAINT [PK_Foo] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE TABLE [dbo].[DefaultSettings](
    [DefaultFoo] [int] NULL
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[DefaultSettings]  WITH CHECK ADD  CONSTRAINT [FK_DefaultSettings_Foo] FOREIGN KEY([DefaultFoo])
REFERENCES [dbo].[Foo] ([Id])
GO

ALTER TABLE [dbo].[DefaultSettings] CHECK CONSTRAINT [FK_DefaultSettings_Foo]
GO
3 голосов
/ 12 марта 2009

Вы можете использовать триггер вставки / обновления .

В триггере после вставки или обновления, если число строк с isDefault = 1 больше 1, откат транзакции.

2 голосов
/ 30 июля 2016

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

--drop table dev.defaultAddress;
--drop table dev.addresses;
--drop table dev.people;

CREATE TABLE [dev].[people](
    [Id] [int] identity primary key,
    name char(20)
)
GO

CREATE TABLE [dev].[Addresses](
    id int identity primary key,
    peopleId int foreign key references dev.people(id),
    address varchar(100)
) ON [PRIMARY]

GO
CREATE TABLE [dev].[defaultAddress](
    id int identity primary key,
    peopleId int foreign key references dev.people(id),
    addressesId int foreign key references dev.addresses(id))
go
create unique index defaultAddress on dev.defaultAddress (peopleId)
go
create unique index idx_addr_id_person on dev.addresses(peopleid,id);
go
ALTER TABLE dev.defaultAddress
   ADD CONSTRAINT FK_Def_People_Address
   FOREIGN KEY(peopleID, addressesID)
   REFERENCES dev.Addresses(peopleId, id)
go
insert into dev.people (name) 
    select 'Bill' union 
    select 'John' union 
    select 'Harry'
insert into dev.Addresses (peopleid, address) 
    select 1, '123 someplace' union 
    select 1,'work place' union
    select 2,'home address' union
    select 3,'some address'
insert into dev.defaultaddress (peopleId, addressesid)
    select 1,1 union
    select 2,3 
-- so two home addresses are default now
-- try adding another default address to Bill and you get an error
select * from dev.people 
    join dev.addresses on people.id = addresses.peopleid
    left join dev.defaultAddress on defaultAddress.peopleid = people.id and defaultaddress.addressesid = addresses.id 
insert into dev.defaultaddress (peopleId, addressesId)
    select 1,2 

GO
2 голосов
/ 12 марта 2009
CREATE VIEW vOnlyOneDefault
AS
  SELECT 1 as Lock
  FROM <underlying table>
  WHERE Default = 1
GO
CREATE UNIQUE CLUSTERED INDEX IX_vOnlyOneDefault on vOnlyOneDefault (Lock)
GO

Для этого вам нужно включить правильные настройки ANSI.

2 голосов
/ 12 марта 2009

Я не знаю о SQLServer. Но если он поддерживает индексы на основе функций, как в Oracle, я надеюсь, что это можно перевести, если нет, извините.

Вы можете сделать такой индекс при условии, что значением по умолчанию является 1234, столбец - DEFAULT_COLUMN и ID_COLUMN - первичный ключ:

CREATE 
UNIQUE 
 INDEX only_one_default 
    ON my_table
     ( DECODE(DEFAULT_COLUMN, 1234, -1, ID_COLUMN) )

Этот DDL создает уникальный индекс, индексирующий -1, если значение DEFAULT_COLUMN равно 1234 и ID_COLUMN в любом другом случае. Затем, если два столбца имеют значение DEFAULT_COLUMN, возникает исключение.

0 голосов
/ 12 марта 2009

@ Энди Джонс дал ответ выше, самый близкий к моему, но, учитывая правило Три , я поместил логику прямо в сохраненный процесс, который обновляет эту таблицу. Это было мое простое решение. Если мне нужно обновить таблицу из другого места, я переместлю логику в триггер. Одно правило по умолчанию применяется к каждому набору записей, указанному в FormID и ConfigID:

ALTER proc [dbo].[cpForm_UpdateLinkedReport]
    @reportLinkId int,
    @defaultYN bit,
    @linkName nvarchar(150)
as
if @defaultYN = 1
begin
    declare @formId int, @configId int
    select @formId = FormID, @configId = ConfigID from csReportLink where ReportLinkID = @reportLinkId
    update csReportLink set DefaultYN = 0 where isnull(ConfigID,  @configId) = @configId and FormID = @formId
end
update
    csReportLink
 set
    DefaultYN = @defaultYN,
    LinkName = @linkName
where
    ReportLinkID = @reportLinkId
0 голосов
/ 12 марта 2009

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

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

мы хотим отменить вставку, если существует значение по умолчанию, изменить его на 0 вместо 1 или изменить существующее значение по умолчанию на 0 и оставить это значение равным 1? что мы хотим сделать, если запись по умолчанию удалена, а другие записи не по умолчанию все еще там? Делаем ли мы один по умолчанию, если да, то как мы можем определить, какой из них?

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

Вам также понадобится очень обширный сценарий тестирования для этого триггера, прежде чем он будет запущен. Вам нужно проверить: добавление записи без значения по умолчанию, и это первая запись для этого клиента добавление записи со значением по умолчанию, и это первая запись для этого клиента добавление записи без значения по умолчанию, и это не первая запись для этого клиента добавление записи со значением по умолчанию, и это не первая запись для этого клиента Обновление записи, чтобы иметь значение по умолчанию, когда ни одна другая запись не имеет его (при условии, что вам не требуется, чтобы одна запись всегда была установлена ​​по умолчанию) Обновление записи для удаления по умолчанию Удаление записи с глухим Удаление записи без значения по умолчанию Выполнение массовой вставки с несколькими ситуациями в данных, включая две записи, для каждой из которых isdefault установлено значение 1, и все ситуации, протестированные при выполнении отдельных вставок записей Выполнение массового обновления с несколькими ситуациями в данных, включая две записи, для каждой из которых isdefault установлено значение 1, и все ситуации, протестированные при запуске отдельных обновлений записей Выполнение массового удаления с несколькими ситуациями в данных, включая две записи, для каждой из которых isdefault установлено значение 1, и все ситуации, протестированные при запуске отдельной записи, удаляют

0 голосов
/ 12 марта 2009

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

РЕДАКТИРОВАТЬ упс, должно быть <= </strong>

Create table mytable(id1 int, defaultX bit not null default(0))
go

create Function dbo.fx_DefaultExists()
returns int as 
Begin
    Declare @Ret int
    Set @ret = 0
    Select @ret = count(1) from mytable 
    Where defaultX = 1

    Return @ret
End
GO
Alter table mytable add
CONSTRAINT  [CHK_DEFAULT_SET] CHECK 
 (([dbo].fx_DefaultExists()<=(1)))
GO
Insert into mytable (id1, defaultX) values (1,1)

Insert into mytable (id1, defaultX) values (2,1)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...