2012-02-17 1 views
3

Во-первых, прошу прощения, если это обман - я подозреваю, что это может быть, но я не могу его найти.Разрешить/потребовать только одну запись с общим FK, чтобы иметь флаг «primary»

Скажем, у меня есть таблица компаний:

id | company_name 
----+-------------- 
    1 | Someone 
    2 | Someone else 

... и таблицу контактов:

id | company_id | contact_name | is_primary 
----+------------+--------------+------------ 
    1 |  1  | Tom   |  1 
    2 |  2  | Dick   |  1 
    3 |  1  | Harry  |  0 
    4 |  1  | Bob   |  0 

Можно ли настроить contacts таблицу таким образом, что требует, что одна и только одна запись имеет флаг is_primary для каждого общего company_id?

Так что, если я пытался сделать:

UPDATE contacts 
SET is_primary = 1 
WHERE id = 4 

... запрос потерпит неудачу, потому что Tom (id = 1) уже попадает в качестве первичного контакта для company_id = 1. Или даже лучше, можно ли построить триггер так, чтобы запрос был успешным, но флаг is_primary будет очищен той же операцией?

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

Когда я изначально думал об этом, я подумал: «Это будет легко, я просто добавлю уникальный индекс по столбцам company_id и is_primary», но, очевидно, это не сработает, поскольку это ограничит меня одним основным и одним не первичный контакт - любая попытка добавить третьего контакта не удалась. Но я не могу не чувствовать, что будет способ настроить уникальный индекс, который даст мне минимальную функциональность, требующую - отклонить попытку добавить второй первичный контакт или отказаться от попытки покинуть компанию без основного контакта.

Я знаю, что я мог бы просто добавить поле primary_contact в таблицу companies с FK на таблицу contacts, но она кажется беспорядочной. Мне не нравится идея того, что обе таблицы имеют FK для другого - мне кажется, что одна таблица должна опираться на другую, а не на обе таблицы, полагающиеся друг на друга. Думаю, я просто думаю, что со временем больше шансов на то, что что-то пойдет не так.

Подводя итог:

  • Как я могу ограничить таблицу контактов, так что одна и только одна запись с данной company_id установил is_primary флаг?
  • У кого-нибудь есть мысли о том, являются ли две таблицы, имеющие FK друг для друга, хорошей/плохой идеей?
+0

Я бы взял здесь взаимный подход FK. Очевидно, что вы не можете сделать оба этих «NOT NULL», так как тогда у вас есть проблема с курицей и яйцом, но при условии, что значение «primary_contact» с нулевым значением может быть решено, а также точно моделирует вашу существующую ситуацию (не может быть первичного контакта для компании). Не согласитесь, что это кажется грязным - если есть отношение 1 к (0 или 1), как здесь, это совершенно естественно. – Jon

+0

@Jon Извините, может быть, я был недостаточно ясен, мне нужно избегать первичного контакта «NULL», поскольку он закручивает один из моих запросов. У меня есть запрос, чтобы перечислить все компании на одной странице, и как часть этого Я присоединяюсь к контактам на contacts.company_id = company.id' и имеет 'where contacts.is_primary = 1'. Таким образом, нет первичного контакта, я не беру отчет о компании, и если есть более того, я получаю больше одного. На самом деле я не беспокоился о том, чтобы ограничение было основано на обеих таблицах, я просто хочу, чтобы таблица контактов проверяла, что одна запись контакта всегда установлена ​​на первичную. – DaveRandom

+0

Циркулярные ссылки между таблицами, по крайней мере, грязные. –

ответ

3

Циркулярные перегородки между столами действительно грязные.Смотрите это (десять лет) статья: SQL By Design: The Circular Reference

Чистейшим способ сделать такое ограничение является добавлением еще одну таблицы:

Company_PrimaryContact 
---------------------- 
company_id 
contact_id 
PRIMARY KEY (company_id) 
FOREIGN KEY (company_id, contact_id) 
    REFERENCES Contact (company_id, id) 

Это также потребует UNIQUE ограничения в таблице Contact на (company_id, id)

+0

Похоже, что это, вероятно, хороший вариант - могу ли я спросить, что означает '' СПИСОК ЛИТЕРАТУРЫ Contact (company_id, id) '? Мой SQL-fu не большой, я буду первым, кто признает ... – DaveRandom

+0

Это означает, что вы не можете добавить '(company_id, contact_id)' в таблицу 'Company_PrimaryContact', если в таблице' Контакт' не существует строки с одинаковыми значениями. Таким образом, он гарантирует, что PrimaryContact компании действительно является контактом с этой компанией. –

+0

Основной ключ гарантирует, что компания может иметь не более одного PrimaryContact. –

2

Вы можете просто сделать запрос до того, как он установит

UPDATE contacts SET is_primary = 0 WHERE company_id = ..... 

или даже

UPDATE contacts 
SET is_primary = IF(id=[USERID],1,0) 
WHERE company_id = (
    SELECT company_id FROM contacts WHERE id = [USERID] 
); 

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

РЕДАКТИРОВАТЬ метод без полагаясь на contact.is_primary поле

Альтернативный метод, прежде всего удалить is_primary от контактов. Во-вторых, добавьте поле «primary_contact_id» INT в компании. В-третьих, при изменении первичного контакта просто измените параметр primary_contact_id таким образом, чтобы предотвратить любую возможность наличия более 1 первичного контакта в любое время и все без необходимости запуска триггеров и т. Д. В фоновом режиме.

Эта опция будет работать нормально в любом двигателе, как это просто обновляя поле INT, любая зависимость от и т.д. FK могли быть добавлены/удалены по мере необходимости, но в то проще это просто Изменение поля INT дорожим

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

+0

Я рассмотрел первый вариант и отбросил его, потому что он подвержен проблемам параллелизма (два клиента одновременно устанавливают два контакта на первичные, тогда вы получаете клиентский 1-> ясный первичный, клиент 2-> ясный первичный, клиент 1-> set primary, client 2-> set primary, и вы в конечном итоге получаете два праймера), но вторая идея выглядит интересной - я не уверен, что это все равно будет восприимчиво к ней, но я думаю, что с MyISAM это будет хорошо, но не InnoDB (который я использую)? – DaveRandom

+0

Добавит некоторую информацию для подхода FK –

+0

+1 для хорошего, разумного и хорошо объясненного ответа, но я думаю, что я собираюсь пойти с ypercube, потому что он лучше подходит моим требованиям и моей существующей логике приложения. – DaveRandom

 Смежные вопросы

  • Нет связанных вопросов^_^