SQL Server foreign key with several cascading paths - sql-server

I have three tables in SQL Server 2014.
First one is a Product table (productid, ...)
Second one contains ProductVersions of that product (ProductVersionID, ProductID, ...)
Third one contains licenses for the products (LicenseID, ProductID)
These tables have foreign keys on the product ID with on delete cascade.
Now I want to add another table mapping the licenses to specific ProductVersions. This can be a n:m relationship, so I create an mapping table LicenseVersion (LicenseID, ProductVersionID)
When I try to add an foreign key to that relations I get an error saying can't add foreign key because there are loops or serveral cascading paths. Use on delete no action or change the foreign key.
I think, I get why this happens (deleting the product will cause the LicenseVersion row to be deleted from both ways in one transaction) but what is the best practice to solve this?
The database should be consistent anytime, so I don't want to solve this in the software application logic.
I could use a trigger (I think) and an foreign key with no action, but is this the best way?
CREATE TABLE [dbo].[Products]
(
[ProductID] [int] IDENTITY(1,1) NOT NULL,
[ProductName] [nvarchar](255) NOT NULL,
CONSTRAINT [PK_dbo.Products]
PRIMARY KEY CLUSTERED([ProductID] ASC)
)
CREATE TABLE [dbo].[ProductVersions]
(
[ProductVersionID] [int] IDENTITY(1,1) NOT NULL,
[ProductID] [int] NOT NULL,
[Name] [nvarchar](255) NOT NULL,
CONSTRAINT [PK_dbo.ProductVersions]
PRIMARY KEY CLUSTERED ([ProductVersionID] ASC)
)
CREATE TABLE [dbo].[License]
(
[LicenseId] [int] IDENTITY(1,1) NOT NULL,
[LicenseName] [nvarchar](255) NOT NULL,
[ProductId] [int] NOT NULL,
CONSTRAINT [PK_dbo.License]
PRIMARY KEY CLUSTERED ([LicenseId] ASC)
)
CREATE NONCLUSTERED INDEX [IX_ProductId]
ON [dbo].[License] ([ProductId] ASC)
CREATE NONCLUSTERED INDEX [IX_ProductID]
ON [dbo].[ProductVersions] ([ProductID] ASC)
ALTER TABLE [dbo].[License] WITH CHECK
ADD CONSTRAINT [FK_dbo.License_dbo.Products_ProductId]
FOREIGN KEY([ProductId]) REFERENCES [dbo].[Products] ([ProductID])
ON UPDATE CASCADE
ON DELETE CASCADE
ALTER TABLE [dbo].[License] CHECK CONSTRAINT [FK_dbo.License_dbo.Products_ProductId]
ALTER TABLE [dbo].[ProductVersions] WITH CHECK
ADD CONSTRAINT [FK_dbo.ProductVersions_dbo.Products_ProductID]
FOREIGN KEY([ProductID]) REFERENCES [dbo].[Products] ([ProductID])
ON UPDATE CASCADE
ON DELETE CASCADE
ALTER TABLE [dbo].[ProductVersions] CHECK CONSTRAINT [FK_dbo.ProductVersions_dbo.Products_ProductID]
--add new table
CREATE TABLE [dbo].[LicenseVersion]
(
[LicenseID] [int] NOT NULL,
[ProductVersionID] [int] NOT NULL,
CONSTRAINT [PK_LicenseVersion]
PRIMARY KEY CLUSTERED ([LicenseID] ASC, [ProductVersionID] ASC)
)
ALTER TABLE [dbo].[LicenseVersion] WITH CHECK
ADD CONSTRAINT [FK_LicenseVersion_ProductVersions]
FOREIGN KEY([ProductVersionID]) REFERENCES [dbo].[ProductVersions] ([ProductVersionID])
ON UPDATE CASCADE
ON DELETE CASCADE
ALTER TABLE [dbo].[LicenseVersion] CHECK CONSTRAINT [FK_LicenseVersion_ProductVersions]
--error here:
ALTER TABLE [dbo].[LicenseVersion] WITH CHECK
ADD CONSTRAINT [FK_LicenseVersion_Licenses]
FOREIGN KEY([LicenseID]) REFERENCES [dbo].[License] ([LicenseID])
ON UPDATE CASCADE
ON DELETE CASCADE
ALTER TABLE [dbo].[LicenseVersion] CHECK CONSTRAINT [FK_LicenseVersion_Licenses]

Related

Creating Primary Keys

I would like to know which column should I put in the PK first, [ID_CHECK] or [ID_CONTROL].
CREATE TABLE QUALITY_CONTROL
(
[ID_CHECK] [int] NULL,
[ID_CONTROL] [int] NOT NULL
)
ALTER TABLE QUALITY_CONTROL ADD CONSTRAINT QUALITY_CONTROL_P01 PRIMARY KEY ([ID_CHECK], [ID_CONTROL])
or
ALTER TABLE QUALITY_CONTROL
ADD CONSTRAINT QUALITY_CONTROL_P01 PRIMARY KEY ([ID_CONTROL], [ID_CHECK])
The [ID_CHECK] column has more repeated data, I don't know if that means anything.
Thanks.

Can SQL Server table have a foreign key to a table that resolves to many records?

Consider the following table...
CREATE TABLE [dbo].[Alerts]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[I18NMessageKey] [uniqueidentifier] NOT NULL
PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO
and the following table...
CREATE TABLE [dbo].[I18NMessages]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[Key] [uniqueidentifier] NOT NULL,
[Culture] [nvarchar](200) NOT NULL,
[Message] [nvarchar](max) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
GO
I would like to add a foreign key constraint to table [Alerts] on the column [I18NMessageKey] to refer to many records in table [I18NMessages].
Is this possible without a third table?
The [I18NMessages] table holds the same message for the [Key] but in different languages depending on [Culture]. The relationship between [Alerts] and [I18NMessages] doesn't care about the culture. The resolution of [Culture] depends on the user at runtime.
In SQL Server, the uniqueness of the referenced key column(s) must be enforced by a primary key, unique constraint, or unique index. You need a third table with a unique I18NMessageKey column key to enforce referential integrity.
You can create a trigger and implement custom business logic

How can I delete child rows when I delete a parent row in SQL Server?

I have two tables:
CREATE TABLE [dbo].[AdminTest] (
[AdminTestId] INT IDENTITY (1, 1) NOT NULL,
[Title] NVARCHAR (100) NOT NULL,
CONSTRAINT [PK_AdminTest] PRIMARY KEY CLUSTERED ([AdminTestId] ASC)
);
GO
CREATE NONCLUSTERED INDEX [Test_ExamId_IX]
ON [dbo].[AdminTest]([ExamId] ASC);
CREATE TABLE [dbo].[AdminTestQuestion] (
[AdminTestQuestionId] INT IDENTITY (1, 1) NOT NULL,
[AdminTestId] INT NOT NULL,
[QuestionUId] UNIQUEIDENTIFIER NOT NULL,
CONSTRAINT [PK_AdminTestQuestion] PRIMARY KEY CLUSTERED ([AdminTestQuestionId] ASC),
CONSTRAINT [FK_AdminTestQuestionAdminTestId] FOREIGN KEY ([AdminTestId]) REFERENCES [dbo].[AdminTest] ([AdminTestId])
);
GO
CREATE NONCLUSTERED INDEX [AdminTestQuestion_AdminTestId_IX]
ON [dbo].[AdminTestQuestion]([AdminTestId] ASC);
Is there some way that I can change the definition of the tables so that when I delete a row from AdminTest then all the child rows in AdminTestQuestions for that test are deleted?
You can add ON DELETE CASCADE to your foreign key constraint;
CONSTRAINT [FK_AdminTestQuestionAdminTestId]
FOREIGN KEY ([AdminTestId]) REFERENCES [dbo].[AdminTest] ([AdminTestId])
ON DELETE CASCADE
An SQLfiddle to test with.
I may not need to point out that this is not necessarily a good idea in the long run, since it will make implicit changes to your data that other users may not know about. Use with caution.

How can I make it so that I can do a delete even when there's a foreign key and row in another table?

I have these tables:
CREATE TABLE [dbo].[ObjectiveDetail] (
[ObjectiveDetailId] INT IDENTITY (1, 1) NOT NULL,
[ObjectiveId] INT NOT NULL,
[Number] INT NOT NULL,
[Text] NVARCHAR (MAX) NOT NULL,
CONSTRAINT [PK_ObjectiveDetail] PRIMARY KEY CLUSTERED ([ObjectiveDetailId] ASC),
CONSTRAINT [FK_ObjectiveDetailObjective] FOREIGN KEY ([ObjectiveId]) REFERENCES [dbo].[Objective] ([ObjectiveId])
);
CREATE TABLE [dbo].[ObjectiveTopic] (
[ObjectiveDetailId] INT NOT NULL,
[SubTopicId] INT NOT NULL,
CONSTRAINT [PK_ObjectiveTopicObjectiveDetail] PRIMARY KEY CLUSTERED ([ObjectiveDetailId] ASC, [SubTopicId] ASC),
CONSTRAINT [FK_ObjectiveTopicObjectiveDetail] FOREIGN KEY ([ObjectiveDetailId]) REFERENCES [dbo].[ObjectiveDetail] ([ObjectiveDetailId]),
CONSTRAINT [FK_ObjectiveTopicSubTopic] FOREIGN KEY ([SubTopicId]) REFERENCES [dbo].[SubTopic] ([SubTopicId])
);
GO
CREATE UNIQUE NONCLUSTERED INDEX [NobjectiveDetail_Number_IX]
ON [dbo].[ObjectiveDetail]([ObjectiveId] ASC, [Number] ASC);
GO
CREATE NONCLUSTERED INDEX [Objective_ObjectiveDetailId_IX]
ON [dbo].[ObjectiveDetail]([ObjectiveDetailId] ASC);
What I was trying to do was to delete an ObjectiveDetail. But if there is a row in the ObjectiveTopic table then the delete fails.
Is there some way that I can also delete of the other row in the ObjectiveTopic table when the ObjectiveDetail row gets deleted?
Change the constraint so that it has ON DELETE CASCADE enabled.
ALTER TABLE dbo.LocalData
DROP CONSTRAINT FK_ReferenceDataLocaldata
ALTER TABLE dbo.LocalData
ADD CONSTRAINT FK_ReferenceDataLocaldata_Cascade
FOREIGN KEY (ForeignID) REFERENCES dbo.ReferenceData(ForeignPK) ON DELETE CASCADE
Essentially all you do is add ON DELETE CASCADE to the end of the add constraint command
To apply this to your question, we need to change
ALTER TABLE ObjectiveTopic
DROP CONSTRAINT [FK_ObjectiveTopicObjectiveDetail]
to
ALTER TABLE ObjectiveTopic
CREATE CONSTRAINT [FK_ObjectiveTopicObjectiveDetail] FOREIGN KEY ([ObjectiveDetailId]) REFERENCES [dbo].[ObjectiveDetail] ([ObjectiveDetailId]) ON DELETE CASCASE
ALTER TABLE dbo.ReferenceData DROP CONSTRAINT FK_MyTable
ALTER TABLE dbo.LocalData
ADD CONSTRAINT FK_ReferenceDataLocaldata_Cascade
FOREIGN KEY (ForeignID) REFERENCES dbo.ReferenceData(ForeignPK)
ON DELETE CASCADE

How can I force one to one relationship on SQL Server 2008 or 2008 R2

Here is my scenario on SQL Server 2008 R2:
This is my first table:
CREATE TABLE [dbo].[Foos](
[FooId] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_Foos] PRIMARY KEY CLUSTERED
(
[FooId] ASC
)
) ON [PRIMARY]
This is the second table which has a relationship to Foos table:
CREATE TABLE [dbo].[Bars](
[BarId] [int] IDENTITY(1,1) NOT NULL,
[FooId] [int] NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_Bars] PRIMARY KEY CLUSTERED
(
[BarId] ASC
)
) ON [PRIMARY]
Go
ALTER TABLE [dbo].[Bars] WITH CHECK ADD CONSTRAINT [FK_Bars_Foos] FOREIGN KEY([FooId])
REFERENCES [dbo].[Foos] ([FooId])
ON DELETE CASCADE
GO
But it is not one to one. What should I do to force this to be one to one relationship? Should I use check constraints?
Add a unique constraint to FooId in Bars.
However, you don't need BarID then because they have the same key. So it looks like this
CREATE TABLE [dbo].[Bars] (
[FooId] [int] NOT NULL,
[Name] [nvarchar](50) NULL,
CONSTRAINT [PK_Bars] PRIMARY KEY CLUSTERED (FooId),
CONSTRAINT [FK_Bars_Foos] FOREIGN KEY([FooId])
REFERENCES [dbo].[Foos] ([FooId])
ON DELETE CASCADE
)
GO
However again, you don't need Bars at all: it is one table...
You can keep Identity column(BarID) also. Then Unique key will help you out from this problem.
IF NOT EXISTS(SELECT OBJECT_ID from sys.objects WHERE name ='foo_bars')
alter table bars add constraint foo_bars unique(fooid)

Resources