Imagine the following:
Personstable:(Id, FirstName, LastName)PersonEmailstable:(Id, PersonId, Address)(to allow a person to have multiple emails)Contactstable:(Id, PersonId, UnsubscribeAll)(special table, because only some people are in the role which allows them to subscribe)Subscriberstable:(Id, ContactId)(contact-role people subscribed to something)SubscriberEmailstable:(Id, SubscriberId, PersonEmailId)(which email address each person would like to receive the notifications at)
It's trivial to imagine how rows can be inserted into SubscriberEmails where SubscriberId points at Subscribers for one person, but PersonEmailId points at PersonEmails for another person. Is there way to enforce this logical structure to avoid this corruption?
Possible but clunky solutions:
- Triggers
- Job on a regular schedule to find problems
3 Answers 3
Foreign key relationships don't have to be single columns, you can have "extra" columns in the relations to enforce your requirement.
Personstable with(Id, ...)PersonEmailstable with(Id, PersonId, ...), relates toPersonsbyPersonIdSubscriberstable with(Id, PersonId, ...), relates toPersonsbyPersonIdSubscriberEmailstable with(Id, PersonId, SubscriberId, PersonEmailId, ...), relates toPersonsbyPersonsId, toPersonEmailsby(PersonId, PersonEmailId)and toSubscribersby(PersonId, SubscriberId)
-
Thank you for the suggestion. This may work for some others who come across this question. Unfortunately, it won't work for me because 1) I actually have another table,
Contacts, whichSubscriberreferences, and thatContactstable hasPersonId, andSubscribers.PersonIdactually referencesContactstable; and 2) it necessitates me addingPersonIdcolumn toSubscriberEmails, which breaks the entity model and is difficult to accomplish cleanly, because I use an ORM. Still, I up-voted your answer because it can be useful to others.Mr. TA– Mr. TA03/30/2023 16:47:10Commented Mar 30, 2023 at 16:47 -
@Mr.TA: The same advice still applies to your situation, it's just another column to track. However, it looks to me like you're creating a complicated data structure instead of a preliminary data validation procedure, and I'm not convinced that you're taking the path of least resistance here.Flater– Flater03/31/2023 00:45:39Commented Mar 31, 2023 at 0:45
You could add a check constraint that validates the relationship, the only problem with doing so is that it only validates one end of the relationship. You either have to add a another check constrain (or validate via a trigger) on the other end.
See https://stackoverflow.com/questions/3880698/can-a-check-constraint-relate-to-another-table for some guidance on what you want to do.
The constraints available to most relational databases do not verify things quite to this level. If you ever notice this kind of inconsistency, application code is likely to blame.
You have identified a few solutions to this problem (using triggers or a scheduled job). A (non-exhaustive) list of other alternatives include:
- Stored procedures
- Performing checks in the application layer
You need to choose from a number of possible options. There is no single best solution here, but generally these checks are enforced in triggers, stored procedures, or application code that executes before SQL is sent across the network. The one place it won't exist is in the database schema, where I use the term "schema" very loosely to mean "database tables, columns, and constraints."
You will need to make a judgement yourself which layer of the application should handle this. These decisions are subjective by nature, so analyze other code in the application to identify pre-established patterns, or consult your team for further guidance. If you are the one making this judgement call, balance the ease of development, testing, and maintenance with the likelihood that something like this would actually happen.
-
SPs are out for me because I use an ORM, and there already exist checks in the application layer, but those failed in this situation (thankfully, it's not a huge problem). I decided to create triggers for now. I would disagree that this is an application logic problem; under no logical circumstance should the persons be different. It seems to be a schema restriction not being supported by database engines problem to me.Mr. TA– Mr. TA03/30/2023 15:45:30Commented Mar 30, 2023 at 15:45
-
@Mr.TA: remember what databases were designed for: storing data. Data consistency is a realm that relational databases can help with, but they don't solve all consistency problems. That's why this is an application logic issue.Greg Burghardt– Greg Burghardt03/30/2023 15:58:07Commented Mar 30, 2023 at 15:58
-
2I believe this is completely wrong. You could just as well ask how the database could ensure that a foreign key points to an existing row in another table. And yet databases do that all the time as it is a data integrity problem and not a business rule problem.Stack Exchange Broke The Law– Stack Exchange Broke The Law03/30/2023 16:06:17Commented Mar 30, 2023 at 16:06
-
@user253751: yes, but relational database systems have easily solved that kind of consistency issue using, just as you said, foreign key constraints. The kind of inconsistency the OP is talking about is not a constraint that relational databases have solved. I never said this wasn't a data integrity problem. I was saying that an RDMS does not have a solution using constraints, and instead you need to choose from a number of other options, some of which reside outside of the database.Greg Burghardt– Greg Burghardt03/30/2023 17:34:24Commented Mar 30, 2023 at 17:34
-
@GregBurghardt then you're going to have to justify why a "single foreign key" is something databases should solve and a "double foreign key" is something they shouldn't. Keep in mind that you did not answer that no database currently has this feature - you answered that this is a bad feature that no database should ever have.Stack Exchange Broke The Law– Stack Exchange Broke The Law03/30/2023 17:35:47Commented Mar 30, 2023 at 17:35
Explore related questions
See similar questions with these tags.
SubscriberEmailsat all. Why wouldn'tSubscribersbe(Id, PersonEmailId)? You can easily get toPersonvia thePersonEmailthey subscribed with. (Or to the associatedContact.)Subscriberstable because 1) it has other state which is by subscriber, and not by email; 2) I have other entities in my model for SMS subscriptions, which share base classes with the email system, and for SMS, a contact can only have one phone number subscribed (per the business requirements).