views:

59

answers:

5

I have a table with three fields, User, City and Country, where exactly one of the fields must be non-NULL at all times. Can I use an SQL constraint for this or should I rethink what I'm doing?

The scenario is that my database should contain documents that can be attached to users, cities or countries. So a row in this table contains exactly one document for either a user, a city or a country. However, one should be able to search for all documents as well, regardless of what entity it has been "attached" to.

The reason I'm not using three different tables instead is that I want to avoid having to JOIN the three tables when searching for documents in all of the three places. I'm imagining that the kind of denormalization I'm attempting to use here will improve performance, but I'm not sure.

Thoughts?

A: 

Put them in a single table Locations with columns LocationID, LocationName, and (if needed) LocationType (User, City, Country). You need only a single join to discover the "location" of the document and you don't have to worry about which field to select to get the name of the location.

With this technique, you can enforce the "one and only one" location requirement with a simple non-null foreign key constraint.

Larry Lustig
A: 

Why don't you have two columns, one called LocationId and one called LocationTypeId. You can then left join onto all three tables:

SELECT COALESCE(User.Name, City.Name, Country.Name) 
FROM Location
INNER JOIN LocationType ON Location.LocationTypeId = LocationType.LocationTypeId
LEFT JOIN User ON Location.LocationId = User.Id AND Location.LocationTypeId = 1
LEFT JOIN City ON Location.LocationId = City.Id AND Location.LocationTypeId = 2
LEFT JOIN Country ON Location.LocationId = Country.Id AND Location.LocationTypeId = 3
openshac
This is about ensuring data integrity in the datbase not about selecting data.
HLGEM
The OP said he had three columns and wanted to ensure he used only one. By having a single column called LocationId (which replaces all three columns) ensures this.
openshac
A: 

Not sure if you can do it with a CHECK CONSTRAINT - I'm been playing around but nothing seems to really work in the end :-(

But you could always create an INSTEAD OF INSERT (and possibly an INSTEAD OF UPDATE) trigger on the table to check these conditions, and if they're not met, throw an exception using RAISERROR and the row won't be inserted (or updated).

marc_s
A: 

I'd suggest sticking with the FK scenario. Here's how I'd ensure that a document is tied to one, and only one DocumentType (what you're calling attached-to).

Here's a very simplified relationship. I realize you've already got the 3 columns on your Document table, but consider making that a FK. alt text

Then load up your Document types with the valid range of values.

alt text

When inserting data, you'll be writing the type as varchar, rather than an int. This shouldn't cause perf issues, and will help you in reporting, etc with the denormalized schema.

alt text

p.campbell
+1  A: 

If I understand properly, the following SQL should add the constraint you are looking for:

ALTER TABLE YourTableName
ADD CONSTRAINT CK_TestNullCount 
    CHECK (Case When [user] is NULL Then 0 Else 1 End 
           + Case When City Is NULL Then 0 Else 1 End 
           + Case When Country Is NULL Then 0 Else 1 End = 1)
G Mastros