Why is this a cyclical foreign key constraint? - sql

I came upon this code, marked "error," in an application I'm to update. Running it on a test database gives a cyclical reference error:
The referential relationship will result in a cyclical reference that is not allowed (Constraint name = descriptions_fk_2)
I named the constraints to see which one caused the problem.
CREATE TABLE items (
id INT NOT NULL UNIQUE IDENTITY,
name NCHAR(100) NOT NULL UNIQUE,
PRIMARY KEY (id)
);
CREATE TABLE sources (
id INT NOT NULL UNIQUE IDENTITY,
item_id INT NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (item_id)
REFERENCES items(id) ON UPDATE NO ACTION ON DELETE CASCADE
);
CREATE TABLE descriptions (
id INT NOT NULL UNIQUE IDENTITY,
item_id INT NOT NULL,
source_id INT NOT NULL,
PRIMARY KEY (id),
CONSTRAINT descriptions_fk_1 FOREIGN KEY (item_id)
REFERENCES items(id) ON UPDATE NO ACTION ON DELETE CASCADE,
CONSTRAINT descriptions_fk_2 FOREIGN KEY (source_id)
REFERENCES sources(id) ON UPDATE NO ACTION ON DELETE CASCADE
);
Why is this a cyclical reference? The descriptions table is linked to two separate tables, but none of them link back to descriptions.

It's not strictly cyclical - but there are multiple cascade paths. So you could cascade delete a row in items two ways:
1) description -> item
2) description -> source -> item
And, for that reason, it's disallowed.
I believe it's a performance concern, as PostGres will allow cycles like that and will just work it out, but deletes under those circumstances can be quite slow.
For some further reading about why it's disallowed, please see this answer.

Related

Using queries in check constraints

I'm trying to use check constraint with a row while I only have the private key. I don't know if there is another way to check this kind of stuff, I'm fairly new to SQL, so I'm also open to suggestions.
Here is a minimal example:
I have following tables:
Buildings:
building_id: int GENERATED PRIMARY KEY
Floors:
floor_id: int GENERATED PRIMARY KEY
floor_nr: int
building_id: int FOREIGN KEY REFERENCES Buildings (building_id)
Glasses:
glass_id: int GENERATED PRIMARY KEY
building_id: int FOREIGN KEY REFERENCES Buildings (building_id)
Floors_Glasses:
floor_id: int FOREIGN KEY REFERENCES Floors (floor_id)
glass_id: int FOREIGN KEY REFERENCES Glasses (building_id)
When I want to move a glass to the floor, I need to check if
(SELECT building_id FROM Floors WHERE floor_id = floor.floor_id) == glass.building_id
So I don't have to repeat the query above for each query I write.
Edit:
I end up using constraint triggers as following:
CREATE CONSTRAINT TRIGGER "trigger name"
AFTER UPDATE OF "column name" ON "table name"
FOR EACH ROW EXECUTE PROCEDURE "procedure name"
And raise exceptions as
raise exception 'message' using errcode = 'restrict_violation';
You can always enforce this rule at the app level. However, I'm always distrusful of the apps (since they are full of bugs all the time), so I personally prefer to enforce the rules at the database level, whenever possible.
It's a bit more startup work but saves you a lot of time later on, since it prevents data corruption issues from the start.
You can do:
create table buildings (
building_id int primary key not null
);
create table floors (
building_id int not null references buildings (building_id),
floor_id int not null,
floor_nr int,
primary key (building_id, floor_id)
);
create table glasses (
building_id int not null references buildings (building_id),
glass_id int not null,
glass_price int,
primary key (building_id, glass_id)
);
create table floor_glasses (
building_id int not null,
floor_id int not null,
glass_id int not null,
primary key (building_id, floor_id, glass_id),
foreign key (building_id, floor_id) references floors (building_id, floor_id),
foreign key (building_id, glass_id) references glasses (building_id, glass_id)
);
The key concept is in the last table. There's only one column building_id that is used in two foreign keys references. This reference sharing ensures the floors and glasses referenced in the last table always belong to the same building.
If a glass truly can be on multiple floors at the same time, add building_id to floors_glasses and create compound foreign keys from that table to floors and glasses that include the building_id column. That will guarantee your integrity constraint.
You cannot define a check constraint that references other rows or tables, because it would become invalid as soon as you modify those other objects. For example, restoring a dump might fail.

May cause cycles or multiple cascade paths

I have a task:
If a "shop" is deleted all references to it is set to NULL.
When I try to create a table:
CREATE TABLE TEST
(
id int Primary Key,
shop int FOREIGN KEY REFERENCES TEST(id) ON DELETE SET NULL,
);
I get an error:
Introducing FOREIGN KEY constraint 'FK__TEST__shop__2882FE7D' on table 'TEST' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Msg 1750, Level 16, State 0, Line 1
What am I doing wrong?
You are getting an error because you are creating a table with a reference to itself. This is not a good idea, and also this is not what you want.
For your task, you need to create another table, with a relationship (a foreign key) that references your first table. The foreign key must be defined properly to hold the rule that sets the child to NULL when the parent is deleted, like :
CONSTRAINT fk_name
FOREIGN KEY (child_col)
REFERENCES parent_table (parent_col)
ON DELETE SET NULL
See this link on how to set option ON DELETE in a foreign key
You can do this, but not exactly as you wish. The following works:
CREATE TABLE TEST (
id int Primary Key,
shop int,
FOREIGN KEY (shop) REFERENCES TEST(id) ON DELETE NO ACTION,
);
To be honest, I'm not sure why the in-line definition doesn't work. The more important point is the action. The SET NULL is not allowed, because SQL Server is very cautious about potential cycles. However, NO ACTION is allowed.

How to use two columns in a foreign key constraint

I have two tables:
Article
Subscription
In the Article table I have two columns that make up the primary key: id, sl. In the Subscription table I have a foreign key 'idsl`.
I use this constraint :
constraint FK_idsl
foreign key (idsl) references CSS_SubscriptionGroup(id, sl)
But when I run the query, I getting this error:
Number of referencing columns in foreign key differs from number of referenced columns, table X
In Article Table I have two fields that are the primary key: id,sl. In the Subscription Table I have a foreign key 'idsl`
This design is broken - it is apparent that the composite primary key in Article(id, sl) has been mangled into a single compound foreign key in table Subscription. This isn't a good idea.
Instead, you will need to change the design of table Subscription to include separate columns for both id and sl, of the same type as the Article Table, and then create a composite foreign key consisting of both columns, referencing Article in the same order as the primary key, e.g:
CREATE TABLE Article
(
id INT NOT NULL,
sl VARCHAR(50) NOT NULL,
-- Other Columns
CONSTRAINT PK_Article PRIMARY KEY(id, sl) -- composite primary key
);
CREATE TABLE Subscription
(
-- Other columns
id INT NOT NULL, -- Same type as Article.id
sl VARCHAR(50) NOT NULL, -- Same type as Article.sl
CONSTRAINT FK_Subscription_Article FOREIGN KEY(id, sl)
REFERENCES Article(id, sl) -- Same order as Article PK
);
Edit
One thing to consider here is that by convention a column named table.id or table.tableid should be unique, and is the primary key for the table. However, since table Article requires an additional column sl in the primary key, it implies that id isn't unique.
correct syntax for relation:
CONSTRAINT FK_OtherTable_ParentTable
FOREIGN KEY(OrderId, CompanyId) REFERENCES dbo.ParentTable(OrderId, CompanyId)
You must try like this:
constraint FK_idsl foreign key (id,sl) references CSS_SubscriptionGroup(id,sl)

SQL: Can I write a CHECK constraint that validates data in a foreign table using a foreign key?

I am designing a test database in SQL Server 2008 R2 and/or SQL Azure. (All of my code will run on both, so far.)
I have a table with a foreign key, and I need to add a constraint that references a field in the foreign table.
Normally I would have the foreign table manage it's own validation checks, but there are cases where that is impossible (or illogical). I've provided some sample-code that displays what I am trying to accomplish.
CREATE TABLE CustomerOrder
(
ID INT NOT NULL IDENTITY PRIMARY KEY,
CustomerID INT NOT NULL UNIQUE
FOREIGN KEY REFERENCES Customer(ID)
ON DELETE NO ACTION ON UPDATE CASCADE,
ProductID INT NOT NULL UNIQUE
FOREIGN KEY REFERENCES Product(ID)
ON DELETE NO ACTION ON UPDATE CASCADE,
Quantity INT NOT NULL DEFAULT 1,
IsPaid BIT NOT NULL DEFAULT 0
)
GO
CREATE TABLE RMA
(
ID INT NOT NULL IDENTITY PRIMARY KEY,
CustomerOrderID INT NOT NULL UNIQUE
FOREIGN KEY REFERENCES CustomerOrder(ID)
ON DELETE NO ACTION ON UPDATE CASCADE,
-- Add constraint to prevent RMAs from being
-- created for orders that have not been paid.
-- This could be a column constraint, or a table constraint.
CHECK ( CustomerOrderID.IsPaid = 1 )
-- ERROR: 'The multi-part identifier "CustomerOrderID.IsPaid"
-- could not be bound.'
)
GO
In this example, it doesn't make sense to put the CHECK constraint in the CustomerOrder table, because a row in the CustomerOrder table is perfectly happy being unpaid as long as there are no RMAs for the order. Furthermore, a constraint in the CustomerOrder table would still need to reference the RMA table to confirm whether there is an RMA, so the same issue remains.
I've also tried:
CHECK (EXISTS(SELECT co.ID FROM CustomerOrder co
WHERE co.ID=CustomerOrderID AND
co.IsPaid=1))
-- ERROR: 'Subqueries are not allowed in this context.
-- Only scalar expressions are allowed.'
Since this is a static constraint for basic data validation, and will never be referenced by any other object, I'd like to avoid making this into a scalar function or stored procedure.
However, in order to avoid using a scalar function, I will need to define the constraint within SQL (preferrably at the same time my database is deployed and the table is created).
What SQL syntax could I use here to define this type of constraint?
Another option (since you asked for a solution without adding triggers or enlarging the foreign key), is removing the IsPaid column and adding another table for paid orders:
CREATE TABLE CustomerOrder
(
ID INT NOT NULL IDENTITY PRIMARY KEY,
CustomerID INT NOT NULL UNIQUE
FOREIGN KEY REFERENCES Customer(ID)
ON DELETE NO ACTION ON UPDATE CASCADE,
ProductID INT NOT NULL UNIQUE
FOREIGN KEY REFERENCES Product(ID)
ON DELETE NO ACTION ON UPDATE CASCADE,
Quantity INT NOT NULL DEFAULT 1
)
GO
CREATE TABLE CustomerOrderPaid
(
ID INT NOT NULL PRIMARY KEY
FOREIGN KEY REFERENCES CustomerOrder(ID)
ON DELETE NO ACTION ON UPDATE CASCADE
)
GO
CREATE TABLE RMA
(
ID INT NOT NULL IDENTITY PRIMARY KEY,
CustomerOrderID INT NOT NULL UNIQUE
FOREIGN KEY REFERENCES CustomerOrderPaid(ID)
ON DELETE NO ACTION ON UPDATE CASCADE,
)
GO

Enforcing unique rows in a many-to-many junction table

I have a junction table for a many-to-many relationship that just links two foreign keys together. However I've found that this will not prevent duplicate identical row entries. What's the proper way of handling that? I thought adding PRIMARY KEY to the two foreign keys would do it, but it seems like I'm not understanding that correctly.
CREATE TABLE ab_link (
a_id bigint REFERENCES a(a_id) PRIMARY KEY,
b_id bigint REFERENCES b(b_id) PRIMARY KEY
);
I found on another question this example:
CREATE TABLE bill_product (
bill_id int REFERENCES bill (bill_id) ON UPDATE CASCADE ON DELETE CASCADE
, product_id int REFERENCES product (product_id) ON UPDATE CASCADE
, amount numeric NOT NULL DEFAULT 1
, CONSTRAINT bill_product_pkey PRIMARY KEY (bill_id, product_id) -- explicit pk
);
Is that constraint the best way of enforcing uniqueness? I would think there would be some way of doing it without having a third row.
For a compound primary key, you need a separate declaration:
CREATE TABLE ab_link (
a_id bigint REFERENCES a(a_id),
b_id bigint REFERENCES b(b_id),
PRIMARY KEY (a_id, b_id)
);