Question

Firstly, I apologise if this is a dupe - I suspect it may be but I can't find it.

Say I have a table of companies:

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

...and a table of contacts:

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

Is it possible to set up the contacts table in such a way that it requires that one and only one record has the is_primary flag set for each common company_id?

So if I tried to do:

UPDATE contacts
SET is_primary = 1
WHERE id = 4

...the query would fail, because Tom (id = 1) is already flagged as the primary contact for company_id = 1. Or even better, would it be possible to construct a trigger so that the query would succeed, but Tom's is_primary flag would be cleared by the same operation?

I am not too bothered about checking whether company_id exists in the companies table, my PHP code would already have performed this check before I got to this stage (although if there is a way to do this in the same operation it would be nice, I suppose).

When I initially thought about this I thought "that will be easy, I'll just add a unique index across the company_id and is_primary columns" but obviously that won't work as it would restrict me to one primary and one non-primary contact - any attempt to add a third contact would fail. But I can't help feeling there would be a way to configure a unique index that gives me the minimum functionality I require - to reject an attempt to add a second primary contact, or reject an attempt to leave a company with no primary contact.

I am aware that I could just add a primary_contact field to the companies table with an FK to the contacts table but it feels messy. I don't like the idea of both tables having an FK to the other - it seems to me that the one table should rely on the other, not both tables relying on each other. I guess I just think that over time there is more chance of something going wrong.

To sum up:

  • How can I restrict the contacts table so that one and only one record with a given company_id has the is_primary flag set?
  • Anyone have any thoughts on whether two tables having FKs to each other is a good/bad idea?
Was it helpful?

Solution

Circular refenences between tables are indeed messy. See this (decade old) article: SQL By Design: The Circular Reference

The cleanest way to make such a constraint is to add another table:

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

This will also require a UNIQUE constraint in table Contact on (company_id, id)

OTHER TIPS

You could just do a query before that one setting

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

or even

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

Just putting an alternative out there - personally I'd probably look to the FK approach though instead of this type of workaround i.e. have a field in the companies table with a primary_user_id field.

EDIT method w/o relying on a contact.is_primary field

Alternative method, first of all remove is_primary from contacts. Secondly add a "primary_contact_id" INT field into companies. Thirdly, when changing the primary contact, just change that primary_contact_id thus preventing any possibility of there being more than 1 primary contact at any time and all without the need for triggers etc in the background.

This option would work fine in any engine as it's simply updating an INT field, any reliance on FK's etc could be added/removed as required but at it's simplest it's just changing an INT fields value

This option is viable as long as you need one and precisely one link from companies to contacts flagging a primary

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top