Идентификаторы в отношении один ко многим
-
21-08-2019 - |
Вопрос
У меня есть две таблицы, мы их назовем Foo
и Bar
, с отношением один ко многим, где Foo
является родителем Bar
.Первичный ключ Foo — это целое число, автоматически генерируемое с помощью последовательности.
С Bar
полностью зависит от Foo
как мне настроить первичный ключ Bar
учитывая следующие ограничения:
- Записи для бара генерируются программно, поэтому пользовательский ввод не может быть полагается на идентификатор.
- Несколько процессов генерируют записи пластинок, поэтому все, что связано
Select Max()
создатьID
представил бы состояние гонки.
Я придумал два возможных решения, которые меня не устраивают:
- Относитесь к таблицам так, как если бы они были во многих отношениях, с третьей таблицей, которая собирает их записи вместе и имела вставку зачетов кода приложения, чтобы отображение между записями было создано правильно.Мне это не нравится, так как это делает дизайн базы данных вводить в заблуждение, а ошибки в коде приложения могут привести к неверным данным.
- Дайте Бару два столбца:
FooID
иFooBarID
и сгенерировать значение дляFooBarID
выбравmax(FooBarID)+1
для некоторыхFooID
, но, как говорилось ранее, это создает условие гонки.
Буду признателен за любые идеи альтернативной раскладки стола.
Решение
Дайте Bar автоматический первичный ключ, как и в случае с Foo.Добавьте столбец FooID внешнего ключа в Bar.
Если я ничего не упустил, похоже, нет причин, по которым это не сработает.
Другие советы
Если я что-то не упустил в вашем описании, это похоже на обычный случай.Обычное решение выглядит примерно так:
INSERT INTO Foo (foo_id, othercolumn)
VALUES ( FooSeq.NextVal(), 'yadda yadda');
INSERT INTO Bar (bar_id, foo_id, extracolumn)
VALUES ( BarSeq.NextVal(), FooSeq.CurrVal(), 'blah blah');
INSERT INTO Bar (bar_id, foo_id, extracolumn)
VALUES ( BarSeq.NextVal(), FooSeq.CurrVal(), 'bling bling');
INSERT INTO Bar (bar_id, foo_id, extracolumn)
VALUES ( BarSeq.NextVal(), FooSeq.CurrVal(), 'baz baz');
А CURRVAL()
функция последовательности возвращает только самое последнее значение, сгенерированное этой последовательностью во время текущего сеанса.Другое одновременное использование этой последовательности не влияет на то, что CURRVAL()
возвращается в ваш сеанс.
Судя по вашему описанию, я предполагаю, что ваша база данных не поддерживает поля идентификаторов с автоматическим приращением (MS SQL поддерживает, у Oracle есть «последовательности», которые так же хороши, если не лучше, я не помню, чтобы MySql имел их).
Если да, то все, что вам нужно, это автоинкрементный FooId и автоинкрементный BarId, а у Bar также есть FooId в качестве внешнего ключа.
Если это не так, то вы можете создать однострочную таблицу для распределения следующим образом:
create table SystemCounter
(
SystemCounterId int identity not null,
BarIdAllocator int
)
--initialize SystemCounter to have one record with SystemCounterId = 1
--and BarIdAllocator = 0
insert into SystemCounter values (1,0)
--id allocator procedure
create procedure GetNextBarId ( @BarId int output ) AS
SET NOCOUNT ON
begin tran
update SystemCounter set
@BarId = BarIdAllocator = BarIdAllocator + 1
where SystemCounterId = 1
commit
GO
обратите внимание: если ваша база данных не поддерживает синтаксис
@BarId = BarIdAllocator = BarIdAllocator + 1
тогда вам нужно будет сделать это так
begin tran
update SystemCounter set
BarIdAllocator = BarIdAllocator + 1
where SystemCounterId = 1
select
@BarId = BarIdAllocator
from SystemCounter
where SystemCounterId = 1
commit
РЕДАКТИРОВАТЬ:Изначально я пропустил тег Oracle, поэтому решение Билла — это все, что нужно.Я оставляю этот ответ как пример того, как это сделать, если кто-то использует базу данных, которая не поддерживает конструкции идентификации или последовательности.
Судя по ответам Ant P и другим ответам, я также не совсем понимаю, почему простое создание уникального идентификатора для бара и добавление идентификатора Foo не сработает.Но предположим, что вы находитесь в ситуации, когда автоинкрементные идентификаторы недоступны, тогда есть два решения, которые не требуют выбора max(barid)+1.
Предварительно создайте таблицу уникальных идентификаторов и используйте транзакцию, чтобы извлечь следующий доступный идентификатор из таблицы и удалить его (как атомарную операцию).Это работает нормально, но имеет тот недостаток, что вам придется постоянно заполнять таблицу.
Создайте UUID в качестве первичного ключа.Обычно это не лучший вариант, поскольку UUID неэффективны для такого использования, но у него есть то преимущество, что не требуются дополнительные инфраструктурные таблицы.Генераторы UUID широко доступны, и в некоторые базы данных они встроены.