Can't Create Foreign Key: May Cause Cycles - sql-server

I've got a very simple relationship between two tables that are used to manage custom UI branding:
ui_portal_branding
CREATE TABLE ui_portal_branding
(
id VARBINARY(16) NOT NULL,
branding_type VARCHAR(128) NOT NULL,
portal_name NVARCHAR(128) NOT NULL,
theme_id VARBINARY(16) NOT NULL,
portal_logo VARBINARY(16) NULL,
portal_favicon VARBINARY(16) NULL,
background_color VARCHAR(50) NULL,
organization_id VARBINARY(16) NULL,
CONSTRAINT pk_ui_port_bran_id PRIMARY KEY (id)
)
ui_portal_resource
CREATE TABLE ui_portal_resource
(
id VARBINARY(16) NOT NULL,
mime_type NVARCHAR(128) NOT NULL,
binary_data VARBINARY(MAX) NOT NULL,
CONSTRAINT pk_ui_port_reso_id PRIMARY KEY (id)
)
Branding is the main table, Resources is a BLOB store for binary data. Both portal_logo and portal_favicon in the branding table are optional binary data from the resource table.
I'd like to define this as a foreign key constraint with the following general logic: neither logo or favicon are required to be defined. If they are defined, they point to a record in the resource table by ui_portal_resource.id. If the data is deleted from the resource table, I want to set the corresponding column in the branding table to null. I don't want to disallow the resource deletion, I don't want to cascade the delete to the branding table.
So I define the following:
ALTER TABLE ui_portal_branding
ADD CONSTRAINT fk_ui_port_bran2ui_port_reso
FOREIGN KEY (portal_logo) REFERENCES ui_portal_resource (id)
ON DELETE SET NULL
So far so good. Now I define:
ALTER TABLE ui_portal_branding
ADD CONSTRAINT fk_ui_port_bran2ui_port_reso2
FOREIGN KEY (portal_favicon) REFERENCES ui_portal_resource (id)
ON DELETE SET NULL
and all of a sudden we have a problem:
Introducing FOREIGN KEY constraint 'fk_ui_port_bran2ui_port_reso2' on table 'ui_portal_branding' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
This to me seems wrong. I'm not introducing a cycle. It's two tables with the foreign keys defined in a single direction. I guess it can technically be multiple cascade paths–if the same resource is the favicon and the logo it has to set 2 things null. But really? This is the deal breaker for the SQL Server engine? Oracle and Postgres both find this situation to be acceptable.
Is there a sensible workaround for this issue? I'm not interested in a solution involving triggers. Is there a better way to model the data? I was hoping that the resources table could service more than just the branding table, which led to the current FK placement. But maybe that is just not possible?

Related

SQL Server error: introducing foreign key constraint on table may cause cycles or multiple cascade paths

I have two tables, User and Data. For audit purposes, I have two columns added in the Datatable:AddedByandLastModifiedBy`.
Both columns reference the User table and its UserId column. I have a On delete set null constraint set on both the foreign keys. SQL Server does not let me create the table Data and throws the error:
Introducing FOREIGN KEY constraint 'FK__Data__LastModifi__0A9D95DB' on table 'Data' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
These are the SQL scripts:
CREATE TABLE [User]
(
UserId int PRIMARY KEY IDENTITY(1,1),
Name varchar(100) not null
)
CREATE TABLE [Data]
(
Id int PRIMARY KEY IDENTITY(1,1),
A int,
B varchar(10),
AddedBy int
FOREIGN KEY REFERENCES [User](UserId)
ON DELETE SET NULL,
LastModifiedBy int
FOREIGN KEY REFERENCES [User](UserId)
ON DELETE SET NULL
)
Removing the ON DELETE constraint solves the problem, but the question why can't I have two ON DELETE SET NULL conditions?
why can't I have two ON DELETE SET NULL conditions?
Because that would cause multiple cascade paths, and SQL Server simply doesn't support that.
There's an outstanding feedback request for this and to allow cascading in parent-child relationships here.
As a workaround you can either make one of the relationships ON DELETE NO ACTION, or use an INSTEAD OF DELETE trigger on USER that handles the cascading behavior.

fake relationships in DataGrip

I'm trying to create an ER diagram with DataGrip 2019.3 however I get duplication of relationships. Once a "false" relay is only based on keys (t1_id:t1 id) without any foreign keys being set at all, and after creating a foreign key the relation is already duplicated (id:t2_id_fkey id).
I'm using PostgreSQL 12
CREATE TABLE public.t1
(
id integer NOT NULL,
name character varying COLLATE pg_catalog."default",
CONSTRAINT t1_pkey PRIMARY KEY (id)
)
CREATE TABLE public.t2
(
id integer NOT NULL,
t1_id integer NOT NULL,
namne character varying COLLATE pg_catalog."default",
CONSTRAINT t2_pkey PRIMARY KEY (id),
CONSTRAINT t2_id_fkey FOREIGN KEY (id)
REFERENCES public.t1 (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
In DataGrip 2019.3 the 'fake keys' were introduced. More information is here: https://www.jetbrains.com/help/datagrip/columns.html#foreign-keys
In your case I see two problems.
Fake keys shouldn't be displayed on the diagram by default (but they can be useful). So we will introduce the setting.
Fake keys should disappear from the diagram when the real one is created. We will fix this as well.
So, expect all this in the nearest update. Thanks!

Entity Framework appears to skip mapping a table with a multi-column primary key set through a constraint

I've created an ADO.NET model via the database first approach.
One of my tables which is listed when creating the model doesn't actually get added to it.
The table has a multi-column primary key, composed of two foreign keys.
CREATE TABLE ForumAccess
(
UserID INT FOREIGN KEY REFERENCES Users(UserID) NOT NULL,
ForumID INT FOREIGN KEY REFERENCES Forums(ForumID) NOT NULL,
CONSTRAINT ForumAccessID PRIMARY KEY (UserID, ForumID),
);
It does show up when I have to select which tables to add, but then it seems to be skipped. No class is generated for it, and it's not shown in the .edmx file.
Part of my application depends on the existence of this table. I have another table which has a multi-column primary key, and another DateTime type column. That table does get added.
That table is:
CREATE TABLE Moderators
(
UserID INT FOREIGN KEY REFERENCES Users(UserID) NOT NULL,
ForumID INT FOREIGN KEY REFERENCES Forums(ForumID) NOT NULL,
TimeOfAddition DateTime NOT NULL, -- When the mod was added as a mod.
CONSTRAINT ModeratorID PRIMARY KEY (UserID, ForumID),
);
Why does the Moderators table get added, but the ForumAccess table doesn't?
There is no error, or any warning that I can see.
What am I missing?

SQL Server - Two columns in child table referencing same column in parent table

As reported by the title I have a child table (Contracts) in which there are two columns, each of them referencing the same key column in the parent table (Clients).
The reason why I have made this design choice is that the parent table contains the clients but at the same time a client could be a provider company for another client.
The problem which I am encountering is that: the table are created correctly but the foreign keys (related only to these two tables) seems to be duplicated in both the child table (Contracts - the place where they should be stay) and in Clients (don't know why they are shown also here). I can see this duplicate when I open the SSMS relationship designer and I see in both tables the presence of the two foreign keys.
Here below the code which generate me this trouble (will be present other tables not mentioned here because them do not create issues):
Table Clients:
CREATE TABLE tblClients
(
VAT_Number NVARCHAR(30) NOT NULL PRIMARY KEY,
Country_EID NVARCHAR(5) NOT NULL,
User_EID NVARCHAR(50) NOT NULL,
Is_Company_Group BIT NOT NULL,
Recording_Date SMALLDATETIME NOT NULL
CONSTRAINT DF_tblClients_Recording_Date DEFAULT CONVERT(SMALLDATETIME, GETDATE()),
General_Notes NVARCHAR(300),
CONSTRAINT tblClients_Country_EID_FK
FOREIGN KEY (Country_EID) REFERENCES tblCountryLkp(Country_ID),
CONSTRAINT tblClients_User_EID_FK
FOREIGN KEY (User_EID) REFERENCES tblUsers(User_ID)
);
Table Contracts:
CREATE TABLE tblContracts
(
Contract_ID_Old NVARCHAR(30) NOT NULL,
Contract_ID_New NVARCHAR(15) NOT NULL,
EVAT_Client NVARCHAR(30) NOT NULL,
Billing_Type_EID INT NOT NULL,
User_EID NVARCHAR(50) NOT NULL,
Type_EID INT NOT NULL,
EVAT_Company NVARCHAR(30) NOT NULL,
Status_EID INT NOT NULL,
Recording_Date SMALLDATETIME NOT NULL
CONSTRAINT DF_tblContracts_Recording_Date DEFAULT CONVERT(SMALLDATETIME, GETDATE()),
PRIMARY KEY (Contract_ID_Old, Contract_ID_New),
CONSTRAINT tblContracts_Billing_Type_EID_FK
FOREIGN KEY (Billing_Type_EID) REFERENCES tblBillingTypeLkp(Billing_Type_ID),
CONSTRAINT tblContracts_User_EID_FK
FOREIGN KEY (User_EID) REFERENCES tblUsers(User_ID),
CONSTRAINT tblContracts_Type_EID_FK
FOREIGN KEY (Type_EID) REFERENCES tblContractTypeLkp(Contract_Type_ID),
CONSTRAINT tblContracts_Status_EID_FK
FOREIGN KEY (Status_EID) REFERENCES tblStatusLkp(Status_ID),
CONSTRAINT tblContracts_EVAT_Client_Company_FK
FOREIGN KEY (EVAT_Client) REFERENCES tblClients(VAT_Number),
CONSTRAINT tblContracts_EVAT_Company_FK
FOREIGN KEY (EVAT_Company) REFERENCES tblClients(VAT_Number)
);
Could someone help me to find the issue and to avoid that the duplication of the foreign keys related to EVAT_Client and EVAT_Company fields are created?
Thank you.
I am replying here to the question of KumarHarsh (since in the comments I didn't get to post images).
In the image will be present the screenshot of the table tblContracts where it could be seen the two foreign keys which, correctly, are defined for this table.
And the screenshot of the table tblClients where it could be seen the two same foreign keys which, in my opinion, are present incorrectly for the table tblClients.
I hope this helps to find the issue.
Thank you.

INSTEAD OF UPDATE Trigger and Updating the Primary Key

I am making changes to an existing database while developing new software. There is also quite a lot of legacy software that uses the database that needs to continue working, i.e. I would like to maintain the existing database tables, procs, etc.
Currently I have the table
CREATE TABLE dbo.t_station (
tx_station_id VARCHAR(4) NOT NULL,
tx_description NVARCHAR(max) NOT NULL,
tx_station_type CHAR(1) NOT NULL,
tx_current_order_num VARCHAR(20) NOT NULL,
PRIMARY KEY (tx_station_id)
)
I need to include a new field in this table that refers to a Plant (production facility) and move the tx_current_order_num to another table because it is not required for all rows. So I've created new tables:-
CREATE TABLE Private.Plant (
PlantCode INT NOT NULL,
Description NVARCHAR(max) NOT NULL,
PRIMARY KEY (PlantCode)
)
CREATE TABLE Private.Station (
StationId VARCHAR(4) NOT NULL,
Description NVARCHAR(max) NOT NULL,
StationType CHAR(1) NOT NULL,
PlantCode INT NOT NULL,
PRIMARY KEY (StationId),
FOREIGN KEY (PlantCode) REFERENCES Private.Plant (PlantCode)
)
CREATE TABLE Private.StationOrder (
StationId VARCHAR(4) NOT NULL,
OrderNumber VARCHAR(20) NOT NULL,
PRIMARY KEY (StationId)
)
Now, I don't want to have the same data in two places so I decided to change the dbo.t_station table into a view and provide instead of triggers to do the DELETE, INSERT and UPDATE. No problem I have [most of] them working.
My question regards the INSTEAD OF UPDATE trigger, updating the Primary Key column (tx_station_id) and updates to multiple rows.
Inside the trigger block, is there any way to join the inserted and deleted [psuedo] tables so that I know the 'before update primary key' and the 'after update primary key'? Something like this...
UPDATE sta
SET sta.StationId = ins.tx_station_id
FROM Private.Station AS sta
INNER JOIN deleted AS del
INNER JOIN inserted AS ins
ON ROW_IDENTITY_OF(del) = ROW_IDENTITY_OF(ins)
ON del.tx_station_id = sta.StationId
At this stage I've put a check in the trigger block that rollbacks the update if the primary key column is updated and there is more than one row in the inserted, or deleted, table.
The short answer is no.
You could put a surrogate key on Private.Station, and expose that through the view, and use that to identify before and after values. You wouldn't need to change the primary key or foreign key relationship, but you would have to expose some non-updateable cruft through the view, so that it showed up in the pseudo-tables. eg:
alter table Private.Station add StationSk int identity(1,1) not null
Note, this may break the legacy application if it uses SELECT *. INSERT statements without explicit insert column lists should be ok, though.
Short of that, there may be some undocumented & consistent ordering between INSERTED and DELETED, such that ROW_NUMBER() OVER (ORDER BY NULLIF(StationId,StationId)) would let you join the two, but I'd be very hesitant to take the route. Very, very hesitant.
Have you intentionally not enabled cascade updates? They're useful when primary key values can be updated. eg:
CREATE TABLE Private.Station (
StationId VARCHAR(4) NOT NULL,
Description NVARCHAR(max) NOT NULL,
StationType CHAR(1) NOT NULL,
PlantCode INT NOT NULL,
PRIMARY KEY (StationId),
FOREIGN KEY (PlantCode) REFERENCES Private.Plant (PlantCode)
ON UPDATE CASCADE
-- maybe this too:
-- ON DELETE CASCADE
)
Someone might have a better trick. Wait and watch!

Resources