I have a database where I'm trying to ensure referential integrity using foreign keys but I'm unsure how to proceed:
There are four tables: Company, Employee, Order and BillingCode.
A company has multiple employees, and each of those employees can place orders.
Each order must be allocated a billing code - which is taken from a lookup table.
Each company can have multiple billing codes.
An employee can only use a billing code thats allocated to their company.
Basic Diagram: http://i61.tinypic.com/21bm937.png
CREATE TABLE [dbo].[BillingCodes](
[BillingId] [int] NOT NULL,
[CompanyId] [int] NOT NULL,
[BillingCode] [varchar](10) NOT NULL,
CONSTRAINT [PK_BillingCodes] PRIMARY KEY CLUSTERED
(
[BillingId] 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
CREATE TABLE [dbo].[Company](
[CompanyId] [int] NOT NULL,
[CompanyName] [varchar](50) NOT NULL,
CONSTRAINT [PK_Company] PRIMARY KEY CLUSTERED
(
[CompanyId] 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
CREATE TABLE [dbo].[Employees](
[EmployeeId] [int] NOT NULL,
[CompanyId] [int] NOT NULL,
[Name] [varchar](50) NOT NULL
CONSTRAINT [PK_Employees] PRIMARY KEY CLUSTERED
(
[EmployeeId] 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
CREATE TABLE [dbo].[Orders](
[OrderId] [int] NOT NULL,
[EmployeeId] [int] NOT NULL,
[Price] [money] NOT NULL,
[Qty] [int] NOT NULL,
[BillingId] [int] NOT NULL,
CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED
(
[OrderId] 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].[BillingCodes] WITH CHECK ADD CONSTRAINT [FK_BillingCodes_Company] FOREIGN KEY([CompanyId])
REFERENCES [dbo].[Company] ([CompanyId])
GO
ALTER TABLE [dbo].[Employees] WITH CHECK ADD CONSTRAINT [FK_Employees_Company] FOREIGN KEY([CompanyId])
REFERENCES [dbo].[Company] ([CompanyId])
GO
ALTER TABLE [dbo].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Employees] FOREIGN KEY([EmployeeId])
REFERENCES [dbo].[Employees] ([EmployeeId])
GO
How do I ensure that [BillingId] in [Orders] is for the same company as the Employee?
Normally I'd just create a Foreign key between [BillingCodes] and [Orders] on [BillingId] and enforce the [CompanyId] reference in the business layer. Unfortunately in this instance I have a boss who likes to go into the database and correct data manually. So I need to enforce this in the database.
Is my only option to carry the CompanyID over to the orders table, and use a composite foreign key to [BillingCode]? This is a simplified version - there are another 3 tables between [Company] and [Employee], so I'd rather not have [CompanyId] in every table if I don't need to.
Is there an easier way to accomplish this?
An alternative is to add a trigger for insert and update on Orders. This would validate that the BillingId is linked to the same Company that the EnployeeId is. If they end up at different companies then raise an error. This is probably the way I'd do this.
You can do this using just referential integrity constraints, it just takes some redundancy -- which is not altogether a bad thing to have.
First, you have to bring the CompanyID along with the EmployeeID to the Orders table and make it referable to the Employee table.
alter table Employees add unique constraint UQ_Employee_Co( EmployeeID, CompanyID );
alter table BillingCodes add unique constraint UQ_BillingCodes_Company_Code( BillingID, CompanyID );
-- Or use indexes - no matter.
CREATE TABLE Orders(
OrderId int NOT NULL,
EmployeeId int NOT NULL,
CompanyID int not null,
Price money NOT NULL,
Qty int NOT NULL,
BillingId int NOT NULL,
CONSTRAINT PK_Orders PRIMARY KEY( OrderID ),
constraint FK_Orders_Employee foreign key( EmployeeID, CompanyID )
references Employees( EmployeeID, CompanyID ),
constraint FK_Orders_BillingCode foreign key( BillingID, CompanyID )
references BillingCodes( BillingID, CompanyID ),
);
Now you cannot have an order with an EmployeeID that associates with one company and a BillingID that associates with a different company because you cannot have an order with EmployeeID and CompanyID that don't match what's in the Employee table nor a CompanyID and BillingID that don't match what's in the BillingCodes table.
And all it costs you is one additional column.
Related
I've been trying to implement a table in TSQL that references another table twice in 2 different columns, and applying cascade UPDATE/DELETE to those references:
CREATE TABLE [dbo].[Discussion](
[id] [bigint] IDENTITY(100000,1) NOT NULL,
[name] [nvarchar](255) NOT NULL,
[originated_user_id] [bigint] NOT NULL,
CONSTRAINT [PK_Discussion] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[User_Badge_Count](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[discussion_id] [bigint] NULL,
[chat_id] [bigint] NULL,
[count] [int] NOT NULL,
CONSTRAINT [PK_User_Badge_Count] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[User_Badge_Count] ADD CONSTRAINT [DF_User_Badge_Count_count] DEFAULT ((0)) FOR [count]
GO
ALTER TABLE [dbo].[User_Badge_Count] WITH CHECK ADD CONSTRAINT [FK_User_Badge_Count_Chat] FOREIGN KEY([chat_id])
REFERENCES [dbo].[Discussion] ([id]) ON DELETE CASCADE ON UPDATE CASCADE
GO
ALTER TABLE [dbo].[User_Badge_Count] CHECK CONSTRAINT [FK_User_Badge_Count_Chat]
GO
ALTER TABLE [dbo].[User_Badge_Count] WITH CHECK ADD CONSTRAINT [FK_User_Badge_Count_Discussion] FOREIGN KEY([discussion_id])
REFERENCES [dbo].[Discussion] ([id]) ON DELETE CASCADE ON UPDATE CASCADE
GO
ALTER TABLE [dbo].[User_Badge_Count] CHECK CONSTRAINT [FK_User_Badge_Count_Discussion]
GO
It's done this way because we can consider a discussion to be a "discussion" or a "chat", and we want to reference as such in the badge table. However, trying to create these 2 cascades to the same table results in the TSQL error:
Introducing FOREIGN KEY constraint 'FK_User_Badge_Count_Discussion' on table 'User_Badge_Count' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Is there a technical reason for this restriction? It seems a bit arbitrary; the DBMS could just update/delete the row if either foreign key referenced is updated/deleted?
CREATE TABLE [dbo].[WKF_EXEC_HTR](
[MODULE_ID] [int] NULL,
[SESSION_ID] [int] NULL,
[Request_id] [int] NULL,
[wkf_exec_htry_seq] [int] IDENTITY(1,1) NOT NULL,
CONSTRAINT [pk_wkf_exec_htr] PRIMARY KEY CLUSTERED
(
[wkf_exec_htry_seq] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = OFF, ALLOW_PAGE_LOCKS = OFF)
ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF GO
ALTER TABLE [dbo].[WKF_EXEC_HTR] WITH CHECK ADD CONSTRAINT [FK_WKF_EXEC_HTR_SESSION_ID] FOREIGN KEY([SESSION_ID])
REFERENCES [dbo].[SESN] ([SESSION_ID])
GO
ALTER TABLE [dbo].[WKF_EXEC_HTR] CHECK CONSTRAINT [FK_WKF_EXEC_HTR_SESSION_ID]
I have a Primary Key and Clustered Index on [wkf_exec_htry_seq]. When multiple processes try to insert in this table it causes deadlock any idea how to avoid this deadlock.
I am trying to execute the following but its giving an error of, I tried doing it with designer, same error saying data type, precision and length must be the same, even though they are
ALTER TABLE CustomerUsers
ADD CONSTRAINT fk_CustomerUsers_Users
FOREIGN KEY (CustomerID)
REFERENCES Customers(UniqueID)
Error thrown:
Msg 1769, Level 16, State 1, Line 1
Foreign key 'fk_CustomerUsers_Users' references invalid column 'UniqueID' in referencing table 'CustomerUsers'.
Msg 1750, Level 16, State 0, Line 1
Could not create constraint. See previous errors.
Here is My Table Structure.
CREATE TABLE [dbo].[Customers](
[CustomerID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](500) NOT NULL,
[ContentLocation] [nvarchar](500) NOT NULL,
[UniqueID] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED
(
[CustomerID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[CustomerUsers](
[CustomerUserID] [int] IDENTITY(1,1) NOT NULL,
[CustomerUniqueID] [uniqueidentifier] NOT NULL,
[UserID] [nvarchar](128) NOT NULL,
CONSTRAINT [PK_CustomerUsers] PRIMARY KEY CLUSTERED
(
[CustomerUserID] 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].[CustomerUsers] WITH CHECK ADD CONSTRAINT [FK_CustomerUsers_AspNetUsers] FOREIGN KEY([UserID])
REFERENCES [dbo].[AspNetUsers] ([Id])
GO
ALTER TABLE [dbo].[CustomerUsers] CHECK CONSTRAINT [FK_CustomerUsers_AspNetUsers]
GO
There is no column with the name CustomerID in CustomerUsers just CustomerUniqueID
modify your query as below
ALTER TABLE CustomerUsers
ADD CONSTRAINT fk_CustomerUsers_Users
FOREIGN KEY (CustomerUniqueID)
REFERENCES Customers(UniqueID)
Query same as #Drew
I do believe the following should do the trick (in the case that you're wanting to set up CustomerUsers.CustomerUniqueID as a FK)
ALTER TABLE customerusers
ADD CONSTRAINT fk_customerusers_users FOREIGN KEY (customeruniqueid)
REFERENCES customers(uniqueid)
what I changed: inferred that error was related to column not existing, noticed that you had already created a column which looked like what you might have been trying to shoot for, added that
I have two table in sql server:
First table Message_Child has a composite primary key (MessageId, ChildId)
Message_Child (MessageId, ChildId, Date)
Second table should contain a foreign key to Message_Child table, so I created two columns: MessageId and ChildId.
Request (RequestId, MessageId, ChildId, type)
And I created the constraint as follow:
Alter table Request
ADD FOREIGN KEY (MessageId, ChildId) REFERENCES Message_Child(MessageId, ChildId);
But I'm getting the following error:
There are no primary or candidate keys in the referenced table 'Message_Child' that match the referencing column list in the foreign key 'FK_Request_534D60F1'.
Edit
Adding the code:
Message_Child table:
CREATE TABLE [dbo].[Message_Child](
[ChildId] [int] NOT NULL,
[MessageId] [int] NOT NULL,
[Date] [datetime] NULL,
CONSTRAINT [PK_Message_Child] PRIMARY KEY CLUSTERED
(
[ChildId] ASC,
[MessageId] 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].[Message_Child] WITH CHECK ADD CONSTRAINT [FK_Message_Child_Child_ChildId] FOREIGN KEY([ChildId])
REFERENCES [dbo].[Child] ([ChildId])
GO
ALTER TABLE [dbo].[Message_Child] CHECK CONSTRAINT [FK_Message_Child_Child_ChildId]
GO
ALTER TABLE [dbo].[Message_Child] WITH CHECK ADD CONSTRAINT [FK_Message_Child_Message_MessageId] FOREIGN KEY([MessageId])
REFERENCES [dbo].[Message] ([MessageId])
GO
ALTER TABLE [dbo].[Message_Child] CHECK CONSTRAINT [FK_Message_Child_Message_MessageId]
GO
RequestQueue table:
CREATE TABLE [dbo].[RequestQueue](
[RequestQueueId] [int] IDENTITY(1,1) NOT NULL,
[PIN] [nvarchar](max) NULL,
[MessageId] [int] NULL,
[ChildId] [int] NULL,
CONSTRAINT [PK_RequestQueue] PRIMARY KEY CLUSTERED
(
[RequestQueueId] 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].[RequestQueue] WITH CHECK ADD CONSTRAINT [FK_RequestQueue_MessageChildId] FOREIGN KEY([RequestQueueId])
REFERENCES [dbo].[RequestQueue] ([RequestQueueId])
GO
ALTER TABLE [dbo].[RequestQueue] CHECK CONSTRAINT [FK_RequestQueue_MessageChildId]
GO
And then I added this:
Alter table [DayCareDB].[dbo].[RequestQueue]
ADD FOREIGN KEY (MessageId, ChildId) REFERENCES [DayCareDB].[dbo].[Message_Child](MessageId, ChildId);
Key order matters here. You need to use (ChildID, MessageID) IN THAT ORDER since that is the key order in your primary key definition.
Alter table [DayCareDB].[dbo].[RequestQueue]
ADD FOREIGN KEY (ChildId, MessageId)
REFERENCES [DayCareDB].[dbo].[Message_Child](ChildId, MessageId);
Hello I've a simple table
CREATE TABLE [dbo].[PRICING](
[ID_PRICE] [int] NOT NULL,
[DATA_START] [datetime] NOT NULL,
[DATA_END] [datetime] NOT NULL,
CONSTRAINT [PK_PRICING] PRIMARY KEY CLUSTERED
(
[ID_PRICE] ASC,
[DATA_START] ASC,
[DATA_END] 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
with primary key consisting of 3 columns (I need this triple key)
Now I need to related it to another table like
CREATE TABLE [dbo].[MOV](
[ID_MOV] [int] IDENTITY(1,1) NOT NULL,
[SALDO] [float] NOT NULL,
[ID_PRICING_BUY] [int] NOT NULL,
[ID_PRICING_SELL] [int] NOT NULL,
CONSTRAINT [PK_MOV] PRIMARY KEY CLUSTERED
(
[ID_MOV] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
When I try to add foreign key to the second table adding relation between ID_PRICING_BUY in table MOV and ID_PRICE in table PRICING I get the error
The column do not match an existing primary key or unique constraint
Now what is the correct way to relate these 2 tables?
Thank you.
If you have a compound primary key made up of 3 columns in your PRICING table, then all foreign keys that reference that must also include all three columns.
ALTER TABLE dbo.MOV
ADD CONSTRAINT FK_MOV_PRICING
FOREIGN KEY(ID_PRICING_BUY, -new-column-for-start-date-, -new-column-for-end-date-)
REFERENCES dbo.PRICING(ID_PRICE, DATA_START, DATA_END)
A foreign key cannot reference only part of a primary key.
The only other option would be to create a unique index on just dbo.Pricing(ID_Price) - then you could reference that unique constraint from a foreign key.
But if you can create a unique index on just dbo.Pricing(ID_Price), the real question becomes why isn't just ID_Price alone the primary key? If it's already unique -> then why add two more columns to your primary key???