Need Tips on Foreign Keys to Alternate Tables - sql-server

I have a table called Message. Each message may be associated with an Invoice table or an Rfp table, but not both. I'm struggling with the best way to implement this:
One approach is for the Message table to have foreign keys to both Invoice and Rfp tables. One FK would be valid while the other must be NULL. (Single-Table Inheritance.) But this seems rather awkward. In addition to the unused column, I need to find ways to prevent cases where the FKs are either both used or both NULL. And this would need to be repeated for every single message.
Another approach is to create a joining table. In this case, my Message table would have an FK to the joining table, and the Invoice and Rfp tables would also have an FK to the joining table. However, the problem here is that, given a reference to the joining table, it is awkward to find the related Invoice or Rfp table because I wouldn't know which contains the FK. So, here, I need to resort to other steps to know how to find the related table, such as adding a column to signify which table is related, for which it is hard to create a constraint.
Finally, I could create two types of Message tables. This solves the problems described above, but this causes problems for our app because we have logic in places that needs to add messages without knowing what type they are. (We can only have an FK to the related table.)
Can anyone offer some tips here. None of these ways are ideal, but perhaps there are aspects I've failed to consider when choosing between the two. Or perhaps there is a better approach altogether.

In option 1, you can use a check constraint to ensure only one FK is set...
CREATE TABLE [dbo].[Rfp] (Id int IDENTITY(1,1) NOT NULL, PRIMARY KEY CLUSTERED (Id))
CREATE TABLE [dbo].[Invoice] (Id int IDENTITY(1,1) NOT NULL, PRIMARY KEY CLUSTERED (Id))
CREATE TABLE dbo.[Message] (Id int IDENTITY(1,1) NOT NULL, RfpId int, InvoiceId int,
PRIMARY KEY CLUSTERED (Id),
FOREIGN KEY (RfpId) REFERENCES [dbo].[Rfp] (Id),
FOREIGN KEY (InvoiceId) REFERENCES [dbo].[Invoice] (Id),
)
ALTER TABLE dbo.[Message]
ADD CONSTRAINT CK_FK CHECK ( (RfpId IS NULL AND InvoiceId IS NOT NULL)
OR (RfpId IS NOT NULL AND InvoiceId IS NULL));

Related

Foreign Key Referencing a Technical Key

So, I've got a table created like so:
create table CharacterSavingThrow
(
CharacterCode int not null,
constraint FK_CharacterSavingThrowCharacterID foreign key (CharacterCode) references Character(CharacterCode),
FortitudeSaveCode int not null,
constraint FK_CharacterSavingThrowFortitudeSaveCode foreign key (FortitudeSaveCode) references SavingThrow(SavingThrowCode),
ReflexSaveCode int not null,
constraint FK_CharacterSavingThrowReflexSaveCode foreign key (ReflexSaveCode) references SavingThrow(SavingThrowCode),
WillSaveCode int not null,
constraint FK_CharacterSavingThrowWillSaveCode foreign key (WillSaveCode) references SavingThrow(SavingThrowCode),
constraint PK_CharacterSavingThrow primary key clustered (CharacterCode, FortitudeSaveCode, ReflexSaveCode, WilSaveCode)
)
I need to know how I would reference the primary key of this table from another table's constraint? Seems like a pretty simple question, either it's possible or not, right? Thanks for your guys's help!
Yes - totally easy - you just have to specify the complete compound index, e.g. your other table also needs to have those four columns that make up the PK here, and then the FK constraint would be:
ALTER TABLE dbo.YourOtherTable
ADD CONSTRAINT FK_YourOtherTable_CharacterSavingThrow
FOREIGN KEY(CharacterCode, FortitudeSaveCode, ReflexSaveCode, WilSaveCode)
REFERENCES dbo.CharacterSavingThrow(CharacterCode, FortitudeSaveCode, ReflexSaveCode, WilSaveCode)
The point is: if you have a compound primary key (made up of more than one column), any other table wanting to reference that table also must have all those columns and use all those columns for the FK relationship.
Also, if you're writing queries that would join those two tables - you would have to use all columns contained in the compound PK for your joins.
That's one of the main drawbacks of using four columns as a PK - it makes FK relationships and JOIN queries awfully cumbersome and really annoying to write and use. For that reason, in such a case, I would probably opt to use a separate surrogate key in the table - e.g. introduce a new INT IDENTITY on your dbo.CharacterSavingThrow table to act as primary key, that would make it a lot easier to reference that table and write JOIN queries that use that table.

PRIMARY KEYs vs. UNIQUE Constraints

In an Alexander Kuznetsov article, he presents the follow code snippet:
CREATE TABLE dbo.Vehicles(
ID INT NOT NULL,
[Type] VARCHAR(5) NOT NULL,
CONSTRAINT Vehicles_PK PRIMARY KEY(ID),
CONSTRAINT Vehicles_UNQ_ID_Type UNIQUE(ID, [Type]),
CONSTRAINT Vehicles_CHK_ValidTypes CHECK([Type] IN ('Car', 'Truck'))
);
This snippet raises a few questions for me.
Why is it necessary to include both ID and Type in the unique constraint? If just ID is unique, then the combination of the two columns will always be unique as well.
Also, I know how to set a primary key and specify if it unique in SSMS. But how would I specify a primary key on one column, and make a unique constraint on a combination of columns? Does this create two indexes?
This came up because I'm trying to implement similar code, which does not create a composite primary key, and I get the following error. So I'm trying to understand this code better.
The columns in table 'MyTable' do not match an existing primary key or UNIQUE constraint.
EDIT
I was able to get this working by simply creating a composite primary key in MyTable. The actual table definition is shown below. Again, this works. But it is not the same as the code quoted above. And I'm not sure if it would be better if I did it the other way.
CREATE TABLE [dbo].[MessageThread](
[Id] [int] IDENTITY(1,1) NOT NULL,
[MessageThreadType] [int] NOT NULL,
CONSTRAINT [PK_MessageThread_1] PRIMARY KEY CLUSTERED
(
[Id] ASC,
[MessageThreadType] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[MessageThread] WITH CHECK ADD CONSTRAINT [CK_MessageThread_ValidType] CHECK (([MessageThreadType]=(2) OR [MessageThreadType]=(1)))
GO
ALTER TABLE [dbo].[MessageThread] CHECK CONSTRAINT [CK_MessageThread_ValidType]
GO
1 : I am not sure of the specific purpose of the given schema. But note that a unique constraint can be applied for multiple reasons, most commonly: (a) to enforce uniqueness and (b) to provide the optimizer with more information to base decisions.
2 : A unique constraint does not create two indexes. It creates a single index with one of the columns as the leading key column. It enforces uniqueness on both. So a unique constraint on a,b could have:
a b
---- ----
1 1
1 2
2 1
2 2
Notice that neither of the columns enforce uniqueness individually. I am not a big fan of using the table designer in SSMS (it has tons of bugs and doesn't support all functionality) but here is how to do it:
a) right-click the grid and choose Indexes/Keys...
b) choose multiple columns using the [...] button in the Columns grid
c) change Type to Unique Key
d) change the Name if desired
Here's an example of a table that already has a primary key. I could add one or more unique indexes if I wanted to:
In my understanding, the reason for unique constraint on ID,[Type] is let detail tables to refer ID,[Type] as foreign key. Usually parent table is required to have unique constraint on columns used for foreign key. For instance, the table in the question can have 2 detail tables:
CREATE TABLE dbo.CARS(
....
vehicle_id INT NOT NULL,
[Type] VARCHAR(5) NOT NULL,
CONSTRAINT CAR_CHK_TYPE CHECK [Type]='Car',
CONSTRAINT CAR_FK_VEHICLE FOREIGN KEY (vehicle_id,[Type]) REFERENCES Vehincle(id,[Type]));
CREATE TABLE dbo.TRUCKS(
....
vehicle_id INT NOT NULL,
[Type] VARCHAR(5) NOT NULL,
CONSTRAINT CAR_CHK_TYPE CHECK [Type]='Truck',
CONSTRAINT CAR_FK_VEHICLE FOREIGN KEY (vehicle_id,[Type]) REFERENCES Vehincle(id,[Type]));
This way Cars will have details only about Car type, whereas TRUCKS only about Truck.
Such design is used to avoid polymorphic relationship, for instance
CREATE TABLE dbo.VEHICLE (
...,
ref_id INT NOT NULL,
-- PK of 'master' table
ref_name VARCHAR(20) NOT NULL,
-- here we put 'truck' or 'car', so we virtually have 2 parents;
-- in this case we cannot use FK constraint, the only thing that may
-- somehow enforce the logical constraint is writing a trigger
Update
Your updated table definition looks good to me. I guess the sample table was initially designed for Oracle and then ported to SQLServer. In Oracle, that unique constraint and primary key can use the same index, so there is no penalty for having both PK and Unique constraint.
Good question. Theoretically you're right; there is no reason, a record can always be uniquely identified by its PK and the unique constraint will always be satisfied as long as this is true. However, if ID and Type have some relationship outside the bounds of the data layer (maybe this table is the data model for an Enum?), then it's unlikely that there would be two different IDs with the same Type because the uniqueness of Type is enforced elsewhere. The constraint also sets up an index that includes both ID and Type, making the table relatively efficient to be queried by that combination of columns.
You set up a unique constraint using the "Manage Indexes and Keys" option. Yes, this will create an index and unique constraint for the primary key, and an index and unique constraint for the combination of PK and Type.
I suspect the reason for having both columns in the UNIQUE constraint is related to the error message you mentioned. SQL Server (in common with other SQL DBMSs) has a limitation that a FOREIGN KEY constraint can only reference exactly the set of columns defined by a uniqueness constraint. So if a FOREIGN KEY constraint references two columns then those two columns must have a uniqueness constraint on them - even if other constraints already guarantee uniqueness. This is a pointless limitation but it is part of standard SQL.
The following example is quite similar and explains why a composite foreign key and nested uniqueness constraints can be useful.
http://consultingblogs.emc.com/davidportas/archive/2007/01/08/Distributed-Keys-and-Disjoint-Subtypes.aspx
Here you go:
Cars and trucks have different attributes, so they do not belong in one table. This is why I have two tables, Cars and Trucks.
Yet cars and trucks share some attributes, such as VIN (vehicle idenification number). More to the point, VIN is unique. This is why I need a table Vehicles. A vehicle cannot be both a car and a truck, so I must make sure it is not possible to enter both (VIN=123456789, Type=Car) and (VIN=123456789, Type=Truck). This is why I have a PK on VIN only.
I must ensure that a vehicle cannot have corresponding rows in both Cars and Trucks tables. This is why I have Type column in Cars and Trucks, and this is why I want (VIN, Type) in child tables Cars and Trucks refer to the parent table Vehicles. The only reason why I need an additional unique constraint on (VIN, Type) is this: it is referred by FK constraints from child tables.
BTW, you could leave a comment on the blog - in that case sqlblog would send me a message. It is a coincidence that I noticed your question here; I was supposed to go skiing, only there is no snow.

Missing FK Relationship in Entity Framework Model

I had a lot of trouble implementing the technique described in an Alexander Kuznetsov article. Basically, the article describes a way to create a FK between one table and alternate tables, and still maintain full constraints on those relationship.
Here's part of Alexander's code:
CREATE TABLE dbo.Vehicles(
ID INT NOT NULL,
[Type] VARCHAR(5) NOT NULL,
CONSTRAINT Vehicles_PK PRIMARY KEY(ID),
CONSTRAINT Vehicles_UNQ_ID_Type UNIQUE(ID, [Type]),
CONSTRAINT Vehicles_CHK_ValidTypes CHECK([Type] IN ('Car', 'Truck'))
)
CREATE TABLE dbo.Cars(ID INT NOT NULL,
[Type] AS CAST('Car' AS VARCHAR(5)) PERSISTED,
OtherData VARCHAR(10) NULL,
CONSTRAINT Cars_PK PRIMARY KEY(ID),
CONSTRAINT Cars_FK_Vehicles FOREIGN KEY(ID, [Type])
REFERENCES dbo.Vehicles(ID, [Type])
)
I finally got it working after errors and confirmed bugs. But when I generate my EF models from the new schema, it is missing a relationship between two of my tables.
The problem is that, in order to have a FK on two columns, there must be an index or unique constraint on both those columns. However, in my case, I also have another table with a FK to a single column in the base table (Vehicles, in Alexander's code).
Since you cannot have more than one PK in a table, this means I cannot have a FK to a PK on both sides. The PK can be for one or two columns, and the other FK will need to reference the non-PK unique constraint.
Unfortunately, Entity Framework will only create relationships for you when there is a FK to a PK. That's the problem. Can someone who understand DB design better than I spot any other alternatives here?
Note: I realize some will see the obvious fix as simply modifying the model to manually add the additional relationship. Unfortunately, we are using a database project and are constantly using automated systems to regenerate the project and model from an updated database. So manual steps are really not practical.
You can't have more than one PK, but you can have more than one unique constraint, and in SQL Server you can create a foreign key constraint that references a unique constraint (one or multiple columns). Here is an example of two tables that roughly look like your model.
CREATE TABLE dbo.Vehicles
(
VehicleID INT PRIMARY KEY,
[Type] VARCHAR(5) NOT NULL UNIQUE,
CONSTRAINT u1 UNIQUE(VehicleID, [Type])
);
CREATE TABLE dbo.Cars
(
CarID INT PRIMARY KEY,
VehicleID INT NOT NULL
FOREIGN KEY REFERENCES dbo.Vehicles(VehicleID),
[Type] VARCHAR(5) NOT NULL
FOREIGN KEY REFERENCES dbo.Vehicles([Type]),
CONSTRAINT fk1 FOREIGN KEY (VehicleID, [Type])
REFERENCES dbo.Vehicles(VehicleID, [Type])
);
Note that Cars has three foreign keys: one points to the PK of vehicles (VehicleID), one points to the unique constraint on Vehicles([Type]), and one points to the multi-column unique constraint on Vehicles(VehicleID, [Type]). I realize this is not equivalent to what you are trying to do but should demonstrate that SQL Server, at least, is capable of doing everything you seem to want to do (I'm having a hard time concluding what you're actually because you keep swapping concepts between what Alex did, what you're trying to do but failing, and what you've done successfully).
Are you saying that EF will not recognize a foreign key that references a unique constraint? If so, does that affect constraints that have more than one column, or all unique constraints? If this is the case, that's a shame, because it is certainly supported in SQL Server. Seems like this would either be a bug or an intentional omission (given that the standard doesn't strictly allow FKs against unique constraints). I wonder if there are any bugs reported on Connect?
I have no idea how to force EF to recognize it, but I do know that just about all the people I know who use database projects end up performing pre- or post-deployment modifications and these can be relatively automated.

Relationships between tables

I have a table called objectives, each objective has zero to many cause-effect relationships with other objectives, these relationships I have to be stored in the database, let me know if there's a way to relate this table records.
There is not a way to relate the records without creating an additional table (you would need N-1 additional columns on your current table to model the N possible effects of a cause).
Creating an additional table like the one below should serve your purpose.
CREATE TABLE cause_effect (
cause integer NOT NULL,
effect integer NOT NULL,
CONSTRAINT cause_effect_pkey PRIMARY KEY (cause, effect),
CONSTRAINT cause_effect_cause_fkey FOREIGN KEY (cause)
REFERENCES yourtable (id),
CONSTRAINT cause_effect_effect_fkey FOREIGN KEY (effect)
REFERENCES yourtable (id)
)
Apply FKey behaviour as applies.

constraint on RowParentId within same table?

How do I specify constraint on my field that allows null but if value exists it should be one of values of primary key within existing table?
Take a look at the code:
CREATE TABLE TestTable
(
RowId int IDENTITY NOT NULL PRIMARY KEY,
RowParentId int NULL, -- < how do I specify constraint that RowParentId if not NULL should be RowId (foreign key to existing table?)
RowName nvarchar(30),
RowShortName nvarchar(10)
)
GO
I want to be able to generate parent child view without limiting depth and enforcing constraint on existing parent.
Hope I was able to convey what I'm looking for.
Cheers
Isn't that just a foreign key?
RowParentId int NULL references ParentTable (ParentTableIdColumn),
if it is not null, then it must be a value from the parent table.
ALTER TABLE TestTable
ADD CONSTRAINT fk_testtable_parent
FOREIGN KEY (RowParentId)
REFERENCES TestTable(RowId)
Note that keeping a NULL in the column is a bad idea, as it's not searcheable by indexes.
You better add a surrogate record with id = 0, reference your real root to this record, and exclude it from selects.
You can have a foreign key constraint that references back to the same table. You may want to look into other models for hierarchies though. The linked chain model has a lot of problems in SQL. Do a Google on "Joe Celko hierarchies" and you should be able to find information on other ways to model a hierarchy.
You want to create a foreign key. There are several ways to do this, but the simplest way for a single key like this would be to add the following after the workld "null" for that column:
references table_name (column_name)
You may need to add the words "foreign key" before that, I can't remember. Either way that should take care of it.
ALTER TABLE [dbo].TestTable WITH CHECK ADD CONSTRAINT [FK_TestTable_RowId_TestTable_RowParentId] FOREIGN KEY(RowParentId) REFERENCES TestTable(RowId)

Resources