Идентификаторы в отношении один ко многим

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

Так Barкак полностью зависит от Fooтого, как бы я настроил первичный ключ, Barучитывая следующие ограничения:

  • Записи для Bar генерируются программно, поэтому на ввод данных нельзя полагаться в качестве идентификатора.
  • Несколько процессов генерируют записи Bar, поэтому все, что связано Select Max()с генерацией a, IDбудет представлять состояние гонки.

Я предложил два возможных решения, которые меня не устраивают:

  • Обращайтесь с таблицами так, как если бы они были отношениями «многие ко многим» с третьей таблицей, которая отображает их записи вместе, а код приложения обрабатывает вставку записей, чтобы сопоставление между записями создавалось правильно. Мне это не нравится, поскольку это вводит в заблуждение дизайн базы данных, и ошибки в коде приложения могут привести к неверным данным.
  • Дайте Бару два столбца: FooIDи FooBarIDсгенерируйте значение для FooBarID, выбрав max(FooBarID)+1для некоторых FooID, но, как было указано ранее, это создает условие гонки.

Я ценю любые идеи для альтернативного стола.

10.12.2008 18:21:04
4 ОТВЕТА
РЕШЕНИЕ

Дайте Bar автоматический первичный ключ так же, как с Foo. Добавьте столбец FooID внешнего ключа в Bar.

Если я что-то упустил, кажется, нет причины, почему бы это не сработало.

5
10.12.2008 18:28:14

Исходя из вашего описания, я предполагаю, что ваша база данных не поддерживает поля идентификаторов автоинкремента (в 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, так что решение Билла это все, что необходимо. Оставляю этот ответ в качестве примера того, как это сделать в случае, если кто-то использует базу данных, которая не поддерживает идентифицирующие или последовательные конструкции.

0
10.12.2008 18:43:31
Вы заметили, что ОП пометил этот вопрос «оракул»? IDENTITY не поддерживается Oracle, только SEQUENCE.
Bill Karwin 10.12.2008 18:35:37
@ [Билл Карвин]: нет, пропустил это! Последовательности в Oracle могут быть использованы для реализации идентификаторов автоинкремента
Steven A. Lowe 10.12.2008 18:41:23

Если я не упустил что-то в вашем описании, это звучит как обычный случай. Обычное решение примерно так:

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()возвращается в вашем сеансе.

3
10.12.2008 19:24:07
Поскольку речь идет именно об Oracle, обратите внимание, что вложенный синтаксис VALUES в Oracle недопустим. Вам понадобятся 3 отдельных оператора INSERT ... VALUES или один INSERT SELECT, который выбрал 3 строки (предположительно из DUAL)
Justin Cave 10.12.2008 18:57:51
Я думаю, что более условно вы вставляете в FOO, возвращая foo_id в переменную, а затем ссылаетесь на переменную в последующих вставках.
David Aldridge 10.12.2008 20:27:58

Я не совсем понимаю, как в Ant P и других ответах почему не сработает просто генерирование уникального идентификатора бара и удаление идентификатора Foo. Но предположим, что вы находитесь в ситуации, когда автоматически увеличивающиеся идентификаторы недоступны, тогда есть два решения, которые не включают выбор max (barid) +1

  1. Предварительно сгенерируйте таблицу уникальных идентификаторов и используйте транзакцию, чтобы извлечь следующий доступный идентификатор из таблицы и удалить его (как элементарную операцию). Это прекрасно работает, но имеет тот недостаток, что вам нужно держать таблицу заполненной.

  2. Создайте UUID в качестве первичного ключа. Как правило, это не очень хороший вариант, поскольку UUID неэффективны для этого использования, но он имеет то преимущество, что никаких дополнительных таблиц инфраструктуры не требуется. Генераторы UUID широко доступны, и некоторые базы данных имеют их встроенные.

0
10.12.2008 18:43:42