Foreign Key Referencing a Technical Key - sql-server

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.

Related

T-SQL Clustered Foreign Key

The "Create Table" grammar rather clearly does not allow me to specify a clustered foreign key constraint. In other words, this is illegal:
--keyword CLUSTERED must be removed before this will execute...
CREATE TABLE [Content](
[ID] [int] NOT NULL CONSTRAINT PK_Content_ID PRIMARY KEY,
ContentDefID int NOT NULL CONSTRAINT FK_Plugin_ContentDef FOREIGN KEY CLUSTERED REFERENCES ContentDef(ID)
)
GO
But I don't understand why it is illegal. ISTM that clustering a foreign-key would facilitate performance of paged-lookups. In other words, "give me child items 80 through 140 of parent ID 20".
Is there a rationale for this?
Update
Based on Oded and Tvanfosson feedback, I've found that the following works:
CREATE TABLE [Content](
[ID] [int] NOT NULL CONSTRAINT PK_Content_ID PRIMARY KEY,
ContentDefID int NOT NULL UNIQUE CLUSTERED CONSTRAINT FK_ContentDefContent FOREIGN KEY REFERENCES ContentDef(ID)
)
GO
But the above causes more problems than it solves. First, a "UNIQUE" foreign key forces my relationship to be one-to-one which I don't want. Second, this only works because it represents the creation of two separate constraints, rather than a single CLUSTERED FOREIGN KEY.
But this investigation is getting me closer to my answer. Evidently clustered indexes MUST be unique, as stated here on SO. Quoting:
If the clustered index is not a unique index, SQL Server makes any duplicate keys unique by adding an internally generated value called a uniqueifier
In particular, I think this answer covers it.
As others have explained, the clustered index does not have to be the primary key but it either has to be unique or SQL-Server adds a (not shown) UNIQUIFIER column to it.
To avoid this, you can make the clustered index unique by explicitly adding the primary key column to the clustered index, like below. The index will then be avaialbel to be used by the foreign key constraints (and for queries, like joining the two tables).
Notice, that as #Martin Smith has explained, the concepts of CONSTRAINT and INDEX are different. And the various DBMSs implement these in different ways. SQL-Server automatically creates an index for some constraints, while it doesn't for foreign key constraints. It's advised though to have an index that the constraint can use (when deleting or updating in the referenced table):
CREATE TABLE Content(
ID int NOT NULL,
ContentDefID int NOT NULL,
CONSTRAINT PK_Content_ID
PRIMARY KEY NONCLUSTERED (ID),
CONSTRAINT CI_Content
UNIQUE CLUSTERED (ContentDefID, ID),
CONSTRAINT FK_Plugin_ContentDef
FOREIGN KEY (ContentDefID) REFERENCES ContentDef(ID)
) ;
Is there a rationale for this?
You might as well ask why you can't create a CLUSTERED check constraint or a CLUSTERED default constraint.
A foreign key simply defines a logical constraint and has no indexes automatically created for it in SQL Server (this only happens for UNIQUE or PRIMARY KEY constraints). It is always the case in SQL Server that if you want the FK columns indexed you need to run a CREATE INDEX on the relevant column(s) yourself.
Therefore the concept of a CLUSTERED FOREIGN KEY doesn't make any sense. You can of course create a CLUSTERED INDEX on the columns making up the FK though as you indicate in your question.
You can only have one clustered index on a table. By default this will be the primary key column.
There are ways to change this - you will need to use PRIMARY KEY NONCLUSTERED and UNIQUE CLUSTERED FOREIGN KEY.
It seems you're conflating the ideas of the clustered index with keys (either primary or foreign). Why not just make the table and then specify its clustered index afterwards? (code copied from your first example and changed as little as possible)
CREATE TABLE [Content](
[ID] [int] NOT NULL CONSTRAINT PK_Content_ID PRIMARY KEY NONCLUSTERED,
ContentDefID int NOT NULL CONSTRAINT FK_Plugin_ContentDef FOREIGN KEY REFERENCES ContentDef(ID)
)
GO
CREATE CLUSTERED INDEX IX_Content_Clustered on Content(ContentDefID)
There's no need for you to make the clustered index unique

Need Tips on Foreign Keys to Alternate Tables

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));

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.

Are foreign keys indexed automatically in SQL Server?

Would the following SQL statement automatically create an index on Table1.Table1Column, or must one be explicitly created?
Database engine is SQL Server 2000
CREATE TABLE [Table1] (
. . .
CONSTRAINT [FK_Table1_Table2] FOREIGN KEY
(
[Table1Column]
) REFERENCES [Table2] (
[Table2ID]
)
)
SQL Server will not automatically create an index on a foreign key. Also from MSDN:
A FOREIGN KEY constraint does not have
to be linked only to a PRIMARY KEY
constraint in another table; it can
also be defined to reference the
columns of a UNIQUE constraint in
another table. A FOREIGN KEY
constraint can contain null values;
however, if any column of a composite
FOREIGN KEY constraint contains null
values, verification of all values
that make up the FOREIGN KEY
constraint is skipped. To make sure
that all values of a composite FOREIGN
KEY constraint are verified, specify
NOT NULL on all the participating
columns.
As I read Mike's question, He is asking whether the FK Constraint will create an index on the FK column in the Table the FK is in (Table1). The answer is no, and generally. (for the purposes of the constraint), there is no need to do this The column(s) defined as the "TARGET" of the constraint, on the other hand, must be a unique index in the referenced table, either a Primary Key or an alternate key. (unique index) or the Create Constraint statment will fail.
(EDIT: Added to explicitly deal with comment below -)
Specifically, when providing the data consistency that a Foreign Key Constraint is there for. an index can affect performance of a DRI Constraint only for deletes of a Row or rows on the FK side. When using the constraint, during a insert or update the processor knows the FK value, and must check for the existence of a row in the referenced table on the PK Side. There is already an index there. When deleting a row on the PK side, it must verify that there are no rows on the FK side. An index can be marginally helpful in this case. But this is not a common scenario.
Other than that, in certain types of queries, however, where the query processor needs to find the records on the many side of a join which uses that foreign key column. join performance is increased when an index exists on that foreign key. But this condition is peculiar to the use of the FK column in a join query, not to existence of the foreign Key constraint... It doesn't matter whether the other side of the join is a PK or just some other arbitrary column. Also, if you need to filter, or order the results of a query based on that FK column, an index will help... Again, this has nothing to do with the Foreign Key constraint on that column.
No, creating a foreign key on a column does not automatically create an index on that column. Failing to index a foreign key column will cause a table scan in each of the following situations:
Each time a record is deleted from the referenced (parent) table.
Each time the two tables are joined on the foreign key.
Each time the FK column is updated.
In this example schema:
CREATE TABLE MasterOrder (
MasterOrderID INT PRIMARY KEY)
CREATE TABLE OrderDetail(
OrderDetailID INT,
MasterOrderID INT FOREIGN KEY REFERENCES MasterOrder(MasterOrderID)
)
OrderDetail will be scanned each time a record is deleted in the MasterOrder table. The entire OrderDetail table will also be scanned each time you join OrderMaster and OrderDetail.
SELECT ..
FROM
MasterOrder ord
LEFT JOIN OrderDetail det
ON det.MasterOrderID = ord.MasterOrderID
WHERE ord.OrderMasterID = #OrderMasterID
In general not indexing a foreign key is much more the exception than the rule.
A case for not indexing a foreign key is where it would never be utilized. This would make the server's overhead of maintaining it unnecessary. Type tables may fall into this category from time to time, an example might be:
CREATE TABLE CarType (
CarTypeID INT PRIMARY KEY,
CarTypeName VARCHAR(25)
)
INSERT CarType .. VALUES(1,'SEDAN')
INSERT CarType .. VALUES(2,'COUP')
INSERT CarType .. VALUES(3,'CONVERTABLE')
CREATE TABLE CarInventory (
CarInventoryID INT,
CarTypeID INT FOREIGN KEY REFERENCES CarType(CarTypeID)
)
Making the general assumption that the CarType.CarTypeID field is never going to be updated and deleting records would be almost never, the server overhead of maintaing an index on CarInventory.CarTypeID would be unnecessary if CarInventory was never searched by CarTypeID.
According to: https://learn.microsoft.com/en-us/sql/relational-databases/tables/primary-and-foreign-key-constraints?view=sql-server-ver16#indexes-on-foreign-key-constraints
Unlike primary key constraints, creating a foreign key constraint does not automatically create a corresponding index

Resources