Specify a Composite Foreign Key - sql

I'm trying to create a Foreign Key out of two values in two different tables, some pointers would be appreciated!
Below is the code I am using for this :
CREATE TABLE [dbo].[EmployeeUnsetZone](
[EmployeeID] [char](2) NULL,
[ZoneOfficeID] [int] NOT NULL,
[ZoneID] [char](4) NULL
CONSTRAINT EmployeeUnsetZone_EmployeeFK FOREIGN KEY([EmployeeID],[ZoneID])
REFERENCES [Employee]([ID]), [ZoneByOffice]([ID])
)

I'd use:
CREATE TABLE [dbo].[EmployeeUnsetZone](
[EmployeeID] [char](2) NULL,
[ZoneOfficeID] [int] NOT NULL,
[ZoneID] [char](4) NULL,
PRIMARY KEY ([EmployeeID], [ZoneID]),
CONSTRAINT fk_employeeid FOREIGN KEY([EmployeeID]) REFERENCES [Employee]([ID]),
CONSTRAINT fk_zoneid FOREIGN KEY([ZoneID]) REFERENCES [ZoneByOffice]([ID])
)
The primary key will stop duplicates, and also setup the clustered key for the two columns to make searching against better.

Related

Add a referential constraint with a constant value

I have a table of locations:
CREATE TABLE [dbo].[loca_location] (
[loca_id] [int] NOT NULL IDENTITY,
[loca_name] [nvarchar](100) NOT NULL,
[loca_address_line_1] [nvarchar](100),
[loca_address_line_2] [nvarchar](100),
[loca_address_line_3] [nvarchar](100),
[loca_address_town] [nvarchar](100),
[loca_address_county] [nvarchar](100),
[loca_post_code] [nvarchar](12),
[loca_active] [bit] NOT NULL,
[loca_created] [datetimeoffset](0) NOT NULL,
[loca_created_by] [nvarchar](50) NOT NULL,
[loca_modified] [datetimeoffset](0) NOT NULL,
[loca_modified_by] [nvarchar](50) NOT NULL,
[loca_deleted] [datetimeoffset](0),
[loca_deleted_by] [nvarchar](50),
[loca_type] [char](1),
CONSTRAINT [PK_dbo.loca_store] PRIMARY KEY ([loca_id])
)
Now some locations may be my own, others may be customers. If customer, then loca_type will be C. If I own it, it will be L. If its a supplier, then it will be S.
Now a contract should always belong to a customer, so I want to make it so the loca_type = C is a constraint as well as the loca_id.
ALTER TABLE [dbo].[cont_contract] ADD CONSTRAINT [FK_dbo.cont_contract_dbo.loca_location_cust_id] FOREIGN KEY ([loca_id_customer]) REFERENCES [dbo].[loca_location] ([loca_id], `C`)
That doesn't work. Is it possible to do what I want?
This may work
1.Declare UNIQUE ([loca_id], [loca_type] ) on [loca_location] table. That is correct as any superset of PK is unique.
2.Add constant column to [cont_contract] table.
3.Create FK.
CREATE TABLE [dbo].[loca_location] (
[loca_id] [int] NOT NULL IDENTITY,
--..
[loca_type] [char](1) CHECK ([loca_type] IN ('A','B','C')), -- change as needed
CONSTRAINT [PK_dbo.loca_store] PRIMARY KEY ([loca_id])
)
ALTER TABLE [dbo].[loca_location] ADD CONSTRAINT [u1] UNIQUE ([loca_id],[loca_type]);
CREATE TABLE [dbo].[cont_contract] (
[id] [int] NOT NULL IDENTITY,
--..
[loca_id_customer] Int,
-- Constant column
[loca_type] [char](1) DEFAULT 'C' CHECK ([loca_type] ='C')
);
ALTER TABLE [dbo].[cont_contract] ADD CONSTRAINT [FK_dbo.cont_contract_dbo.loca_location_cust_id]
FOREIGN KEY ([loca_id_customer],[loca_type]) REFERENCES [dbo].[loca_location] ([loca_id], [loca_type]);

Primary and foreign keys database design

I am creating SQL Server tables for a currency exchange system.
I have one table which is the following:
CREATE TABLE [CurrencyKeys]
(
[Key] [nchar](3) NOT NULL,
[Currency] [nvarchar](30) NOT NULL
CONSTRAINT [PK_Key] PRIMARY KEY ([Key])
);
How would I create the table for the exchange rates itself which references the keys from the CurrencyKeys table?
Currently I have the following:
CREATE TABLE [ExchangeRates]
(
[DateTime] [datetime]NOT NULL,
[FromCurrCode] [nchar](3) NOT NULL,
[ToCurrCode] [nchar] (3) NOT NULL,
[Rate] [money] NOT NULL
)
Would I need to create (FromCurrCode, ToCurrCode) as primary key as well?
If I understand you correctly, you need to define the foreign keys on the exchangeRates table.
You would define a foreign key for EACH column, like so:
alter table [ExchangeRates] add constraint fk_ExchangeRates_01 foreign key (fromCurrCode) references CurrencyKeys([Key]);
alter table [ExchangeRates] add constraint fk_ExchangeRates_02 foreign key (toCurrCode) references CurrencyKeys([Key]);
That will define foreign keys for your exchangeRates table.
Don't you just want to do this?
CREATE TABLE [ExchangeRates]
(
[DateTime] [datetime]NOT NULL,
[FromCurrCode] [nchar](3) NOT NULL,
[ToCurrCode] [nchar] (3) NOT NULL,
[Rate] [money] NOT NULL,
Foreign Key([FromCurrCode]) References [CurrencyKeys]([Key]),
Foreign Key([ToCurrCode]) References [CurrencyKeys]([Key])
)
Or are you trying to ask how to maintain primary key values for ExchangeRates table itself. Can you please make yourself clear?
It depends on whether your table could have several rows referring to the same (From,To) pair (to track different exchange rates on different dates)
If that is the case, then the PK could be (FromCurrCode, ToCurrCode, DateTime)
If you are only saving one/the last exchange rate, then (FromCurrCode, ToCurrCode) should be enough.
It appears that you are using natural keys in lieu of surrogate keys.
In order for the ExchangeRates table to use natural keys, you would need to create a composite primary key using FromCurrCode and ToCurrCode
CREATE TABLE [ExchangeRates]
(
[DateTime] [datetime]NOT NULL,
[FromCurrCode] [nchar](3) NOT NULL,
[ToCurrCode] [nchar] (3) NOT NULL,
[Rate] [money] NOT NULL
CONSTRAINT [PK_ExchangeRates] PRIMARY KEY ([FromCurrCode], [ToCurrCode])
)

Error Defining Foreign Key

I have the following two database tables defined:
CREATE TABLE [dbo].[Classrooms] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[SystemAccount_ID] INT NOT NULL,
[ClassroomName] VARCHAR (30) NOT NULL,
CONSTRAINT [PK_Table] PRIMARY KEY CLUSTERED ([ID]),
CONSTRAINT [FK_Classrooms_SystemAccount] FOREIGN KEY ([SystemAccount_ID]) REFERENCES [dbo].[SystemAccounts] ([ID])
);
CREATE TABLE [dbo].[Students] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[SystemAccount_ID] INT NOT NULL,
[Classroom_ID] INT NULL,
[PhotoID] INT NULL,
[FirstName] VARCHAR (20) NOT NULL,
[LastName] VARCHAR (40) NOT NULL,
[NewsTemplate] TINYINT NOT NULL,
CONSTRAINT [PK_Students] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_Students_Classrooms] FOREIGN KEY ([Classroom_ID]) REFERENCES [dbo].[Classrooms] ([ID]),
CONSTRAINT [FK_Students_SystemAccounts] FOREIGN KEY ([SystemAccount_ID]) REFERENCES [dbo].[SystemAccounts] ([ID])
);
Data model details:
Students belong to zero or one classroom via Classroom_ID FK
Students belong to one System Account via SystemAccount_ID FK
Classrooms belong to one System Account via SystemAccount_ID FK (implying a system account can have zero or more Classrooms)
What I'm attempting to do is enforce when students are added to a classroom (by setting the Classroom_ID key in the Students table) that the classroom belongs to the same system account as the student. I could easily enforce this at the business logic layer but then I'd be requiring every programmer to remember to do this. So ideally, I'd be able to do this at the data layer as a constraint.
I tried adding a FK constraint to the Students table:
CONSTRAINT [FK_Students_ToTable] FOREIGN KEY ([SystemAccount_ID]) REFERENCES [Classrooms]([SystemAccount_ID])
Which results in the following error compliments of SQL Server:
Update cannot proceed due to validation errors.
Please correct the following errors and try again.
SQL71516 :: The referenced table '[dbo].[Classrooms]' contains no primary or candidate keys that match the referencing column list in the foreign key. If the referenced column is a computed column, it should be persisted.
I've tried a few different things but my SQL mojo isn't powerful enough to hack past this one. Any help would be greatly appreciated.
Add a UNIQUE constraint on the combination of the two columns in Classrooms:
CREATE TABLE [dbo].[Classrooms] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[SystemAccount_ID] INT NOT NULL,
[ClassroomName] VARCHAR (30) NOT NULL,
CONSTRAINT [PK_Table]
PRIMARY KEY CLUSTERED ([ID]),
CONSTRAINT [FK_Classrooms_SystemAccount]
FOREIGN KEY ([SystemAccount_ID])
REFERENCES [dbo].[SystemAccounts] ([ID]),
CONSTRAINT [UQ_Classrooms_ID_SystemAccount_ID]
UNIQUE ([SystemAccount_ID], [ID])
);
Then, in the Students table, combine the two FOREIGN KEY constraints into one, or in your case (because Classroom_ID isnullable), change the FK to Classroom to use the combination of the two columns:
CREATE TABLE [dbo].[Students] (
[ID] INT IDENTITY (1, 1) NOT NULL,
[SystemAccount_ID] INT NOT NULL,
[Classroom_ID] INT NULL,
[PhotoID] INT NULL,
[FirstName] VARCHAR (20) NOT NULL,
[LastName] VARCHAR (40) NOT NULL,
[NewsTemplate] TINYINT NOT NULL,
CONSTRAINT [PK_Students]
PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [FK_Students_Classrooms]
FOREIGN KEY ([SystemAccount_ID], [Classroom_ID])
REFERENCES [dbo].[Classrooms] ([SystemAccount_ID], [ID]),
CONSTRAINT [FK_Students_SystemAccounts] -- this wouldn't be needed if
FOREIGN KEY ([SystemAccount_ID]) -- Classrooms_ID was NOT NULL
REFERENCES [dbo].[SystemAccounts] ([ID])
);

Complex foreign key

I have a table similar to this:
CREATE TABLE [dbo].[Table1](
[Option_PK] [bigint] IDENTITY(1,1) NOT NULL,
[Option_1] [varchar](10) NULL,
[Option_2] [varchar](10) NULL,
[Option_3] [varchar](10) NULL)
What I am attempting to do, is add a table driven constraint which can effectively restrict valid entries on a per-column basis. For example, if I made a second table:
CREATE TABLE [dbo].[Table2](
[FK_Name] [varchar](10) NOT NULL,
[FK_Value] [varchar](10) NOT NULL)
I would then want to check that the value stored in Table1, column "Option_1", existed in Table2, column "FK_Value", where the value of "FK_Name" was "Option_1".
Is this possible with either a check or an FK?
** Edit to make the column datatypes match; I hand typed the example table declarations and typo'd, this wasn't relevant to the problem. I know how to do a FK, I don't know how to do an FK like what I'm describing.
Could you not just have 3 tables and three FK?
A FK needs to match types.
CREATE TABLE [dbo].[Option1]([FK_Value] [nchar](10) NOT NULL)
CONSTRAINT [PK_Option1] PRIMARY KEY CLUSTERED ([FK_Value] ASC)
ALTER TABLE [dbo].[Table1] WITH CHECK ADD CONSTRAINT [FK_Table1_Option1] FOREIGN KEY([Option_1])
REFERENCES [dbo].[Table2] ([FK_Value])
GO
Or you could have a column Option1 that defaults to a value of option1
I tried hard coding in a value for Option1 but would not go.
ALTER TABLE [dbo].[FKtest1] WITH CHECK ADD CONSTRAINT [FK_FKtest1_FKtest1] FOREIGN KEY([Option1],[ValueFK])
REFERENCES [dbo].[FKtest1FK] ([PKoption],[PKvalue])
GO
CREATE TABLE [dbo].[Table1](
[Option_PK] [bigint] IDENTITY(1,1) NOT NULL,
[Option_1] [varchar](10) NULL,
[Option_1_FK] [varchar](8) NOT NULL DEFAULT 'OPTION_1',
[Option_2] [varchar](10) NULL,
[Option_2_FK] [varchar](8) NOT NULL DEFAULT 'OPTION_2',
[Option_3] [varchar](10) NULL,
[Option_3_FK] [varchar](8) NOT NULL DEFAULT 'OPTION_3'
)
CREATE TABLE [dbo].[Options](
[FK_Name] [nchar](8) NOT NULL,
[FK_Value] [nchar](10) NOT NULL,
CONSTRAINT [PK_Option1] PRIMARY KEY CLUSTERED ([FK_Name], [FK_Value] ASC)
)
ALTER TABLE [dbo].[Table1] WITH CHECK ADD
CONSTRAINT [FK_Table1_Option1] FOREIGN KEY([Option_1], [Option_1_FK) REFERENCES [dbo].[Options] ([[FK_Value], [FK_Name])
CONSTRAINT [FK_Table1_Option2] FOREIGN KEY([Option_2], [Option_2_FK) REFERENCES [dbo].[Options] ([[FK_Value], [FK_Name])
CONSTRAINT [FK_Table1_Option3] FOREIGN KEY([Option_3], [Option_3_FK) REFERENCES [dbo].[Options] ([[FK_Value], [FK_Name])
GO
Which is untested, unnormalized and ugly. You should probably add constraints to ensure the value of Option_X_FK does not change. Actually, this being T-SQL, you might be able to use computed columns for that, but I'm not sure if including them in a foreign key is allowed.

Composite Primary Key + Foreign Key

I have a table which contains a list of Surveys (PK is ID)
CREATE TABLE [dbo].[SurveyMaster](
[ID] [nvarchar](4) NOT NULL,
[Title] [nvarchar](200) NULL,
[IsActive] [bit] NOT NULL,
And a table which contains a list of Variable Mappings.
CREATE TABLE [dbo].[VariableMappings](
[ParentSurvey_ID] [nvarchar](4) NOT NULL,
[ReportingMonth] [nvarchar](6) NOT NULL,
[VariableName] [nvarchar](400) NOT NULL,
[Value] [int] NOT NULL
My hope was to create a primary key for VariableMappings on ParentSurvey_ID, ReportingMonth and Variable Name to ensure a unique record.
At the same time though, I'd like a foreign key relationship between VariableMappings.ParentSurvey_ID and SurveyMaster.ID to ensure VariableMappings only contains relevant SurveyID's.
I've tried a couple of approaches in SQL Server but I don't believe I can create the FK due to the composite key being made up of 3 columns.
How can I go about achieving this?
Yes, you can:
CREATE TABLE [dbo].[VariableMappings](
[ParentSurvey_ID] [nvarchar](4) NOT NULL,
[ReportingMonth] [nvarchar](6) NOT NULL,
[VariableName] [nvarchar](400) NOT NULL,
[Value] [int] NOT NULL,
PRIMARY KEY (ParentSurvey_ID, ReportingMonth, VariableName),
FOREIGN KEY (ParentSurvey_ID)
REFERENCES dbo.SurveyMaster (ID)
) ;
Mostly irrelevant to your problem, but having a PRIMARY KEY that is so wide (410 nvarchars) is not the best idea.
Your primary key definition and foreign key definitions are orthogonal. You can declare your composite primary key and your single column foreign key without issue. I would suggest to you, though, that a surrogate primary key on VariableMappings is better choice vs. the composite key - specifically for foreign keys pointing at VariableMappings.
There is one primary key that does matter and it is that of SurveyMaster. As long as ID is the primary key there, you can reference it from any number of other tables, regardless of those tables' primary keys.
A foreign key requires that the referenced combination of columns is unique, and the uniqueness must be guaranteed, for example by a primary key constraint.