Why `not deferrable` constraint is deferred when using `with`? - sql

create table T1 (
id bigint NOT NULL primary key,
a bigint unique not null
);
create table T2 (
id int not null primary key,
b bigint,
foreign KEY(id) references T1(id) not deferrable
);
alter table T1 add constraint fk_id foreign key (id) references T2(id) not deferrable;
--Statement 1
with ins as (
insert into T1(id, a) values(15, 4) returning id
)
insert into T2(id, b) values(15, 3);
--Statement 2
with ins as (
insert into T2(id, b) values(14, 4) returning id
)
insert into T1(id, a) values(14, 3);
--Statement 3 (gives error)
with upd as (
update T1 set a = 4 where id = 14 returning id
)
update T1 set a = 3 where id = 15;
When this script is run in PostgreSQL, why does Statement 1 and 2 work and only statement 3 gives an error even when both the foreign key constraint are explicitly not deferrable?

According to the docs non-deferrable unique constraints are checked for each row, contrary to the standards specification that they are checked only at the end of a statement.
When a UNIQUE or PRIMARY KEY constraint is not deferrable, PostgreSQL checks for uniqueness immediately whenever a row is inserted or modified. The SQL standard says that uniqueness should be enforced only at the end of the statement...
But this exception to the standards spec is only for uniqueness, not for foreign key. Foreign key constraints are checked at the end of the statement if they are either not deferrable or if they are deferrable but not deferred. Since any problems have been cured by the end of the statement in your first two examples, there is no error.

Related

Check constraint with multiple conditions and other table column referring

I have two table called tbl_1 and tbl_2 with below schema
CREATE TABLE tbl_1(id int, disabled bit, qtype int)
and
CREATE TABLE tbl_2 (qtype int, qname nvarchar(MAX))
i want to add a constraint to the tbl_1 so that if (disabled=0 or disabled is null) qtype must be a number that exists in the tbl_2.qtype column?
I tried creating a function and added a check constraint
CREATE FUNCTION fn_Check_qtype(#qtype INT)
RETURNS int
AS
BEGIN
IF EXISTS(SELECT qtype from tbl_1 where (disabled=0 or disabled is null) and qtype=#qtype)
IF EXISTS (SELECT qtype FROM tbl_2 WHERE qtype is not null and qtype = #qtype)
return 1
return 0
END
Constraint
alter table tbl_1
add constraint ck_qtyppe
check (dbo.fn_Check_qtype(qtype) =1)
But even no non-matching records it is throwing error
The ALTER TABLE statement conflicted with the CHECK constraint
"ck_qtyppe". The conflict occurred in database "TestDB", table
"dbo.tbl_1", column 'qtype'.
But if i am deleting qtype null value from tbl_1 it is working no matter disabled=0, disabled=, disabled = null.
First, a foreign key references should be to a primary key, so let me assume tbl_2 is defined as:
CREATE TABLE tbl_2 (
qtype int primary key,
qname nvarchar(MAX)
);
Then, you can do this without a user-defined function. All you need is a persisted computed column:
CREATE TABLE tbl_1 (
id int,
disabled bit,
qtype int,
qtype_enabled as (case when disabled = 1 then qtype end) persisted,
foreign key (qtype_enabled) references tbl_2 (qtype)
);
Here is a db<>fiddle.

ON DELETE CASCADE for a many-to-many relationship with the same table in MS SQL Server

Note: this question appears to have been asked before at Cascade delete on many-to-many between same table but didn't receive a satisfactory answer.
I have a table, Friendship( MemberId, FriendId ), which allows a Member to add another Member as a friend.
CREATE TABLE dbo.FRIENDSHIP(
MemberId INT NOT NULL
, FriendId INT NOT NULL
, DateCreated DATE NOT NULL DEFAULT CURRENT_TIMESTAMP
CONSTRAINT pk_friendship PRIMARY KEY( MemberId, FriendId ),
CONSTRAINT fk_friendship_member FOREIGN KEY( MemberId ) REFERENCES Member( MemberId ) ON DELETE CASCADE,
CONSTRAINT fk_friendship_friend FOREIGN KEY( FriendId ) REFERENCES Member( MemberId ) ON DELETE CASCADE
);
When I run this script, I see the following error:
Introducing FOREIGN KEY constraint 'fk_friendship_friend' on table
'FRIENDSHIP' may cause cycles or multiple cascade paths. Specify ON
DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints.
Obviously, I want records in Friendship to be deleted whenever either member is deleted; I also don't want either field to be nullable.
After some research, I saw people suggesting the use of Triggers. So I created one:
CREATE OR ALTER TRIGGER friendship_cascade_delete
ON Member
FOR DELETE
AS
DELETE FROM Friendship
WHERE MemberId IN( SELECT MemberId
FROM deleted )
OR FriendId IN( SELECT MemberId
FROM deleted );
but when I go to delete a Member, I still get the error:
The DELETE statement conflicted with the REFERENCE constraint
"fk_friendship_member". The conflict occurred in database "CVGS",
table "dbo.FRIENDSHIP", column 'MemberId'.
So it's not even getting to the point where it can fire the trigger. If I remove fk_friendship_member and fk_friendship_friend, the trigger works properly - but I'm not sure I want to remove those constraints.
Is there any way to get the behaviour I want, without risking invalid rows in the table or unnecessary SQL errors? Am I missing something obvious here?
Use an INSTEAD OF trigger, instead of an AFTER trigger. EG
--DROP TABLE IF EXISTS FRIENDSHIP
--DROP TABLE IF EXISTS MEMBER
GO
CREATE TABLE MEMBER(MemberID int primary key);
CREATE TABLE dbo.FRIENDSHIP(
MemberId INT NOT NULL
, FriendId INT NOT NULL
, DateCreated DATE NOT NULL DEFAULT CURRENT_TIMESTAMP
CONSTRAINT pk_friendship PRIMARY KEY( MemberId, FriendId ),
CONSTRAINT ak_friendship UNIQUE( FriendId, MemberId ),
CONSTRAINT fk_friendship_member FOREIGN KEY( MemberId ) REFERENCES Member( MemberId ),
CONSTRAINT fk_friendship_friend FOREIGN KEY( FriendId ) REFERENCES Member( MemberId )
);
GO
CREATE OR ALTER TRIGGER friendship_cascade_delete
ON Member
INSTEAD OF DELETE
AS
BEGIN
SET NOCOUNT ON;
DELETE FROM Friendship
WHERE MemberId IN( SELECT MemberId FROM deleted );
DELETE FROM Friendship
WHERE FriendId IN( SELECT MemberId FROM deleted );
DELETE FROM MEMBER
WHERE MemberId IN( SELECT MemberId FROM deleted );
END
GO
INSERT INTO MEMBER(MemberID) values (1),(2),(3)
INSERT INTO FRIENDSHIP(MemberID, FriendId) values (1,2), (1,3), (3,2)
go
select *
from FRIENDSHIP
delete from member where MemberID = 1
select *
from FRIENDSHIP

Not expected result of the query

There is a query:
select * from
(
select
p.ID
from
MY_PRODUCT_PARENT_LINK pLink
inner join MY_PRODUCT p on pLink.FK_PARENT=p.ID
order by
pLink.DESCENDANT_LVL desc
)
where rownum <= 1
;
The inner query returns one row with not null value of p.ID.
The outer query - one row with null value of p.ID. Here expected not null value.
The database sever is Oracle 11g.
There is a code to reproduction:
CREATE TABLE MY_PRODUCT
(
FK_PARENT NUMBER(19),
ID NUMBER(19) NOT NULL,
NAME VARCHAR2(4000 BYTE),
CONSTRAINT PK_MY_PRODUCT PRIMARY KEY (ID)
);
INSERT INTO MY_PRODUCT (FK_PARENT, ID,NAME) VALUES (null, 111111,'PRODUCT_NAME_01');
INSERT INTO MY_PRODUCT (FK_PARENT, ID,NAME) VALUES (null, 111112,'PRODUCT_NAME_02');
CREATE TABLE MY_PRODUCT_PARENT_LINK
(
ID NUMBER(19) NOT NULL,
FK_PRODUCT NUMBER(19) NOT NULL,
FK_PARENT NUMBER(19) NOT NULL,
DESCENDANT_LVL NUMBER(19) NOT NULL,
primary key (ID)
);
ALTER TABLE MY_PRODUCT_PARENT_LINK ADD
CONSTRAINT MY_PRD_PARENT_TO_MY_PRODUCT
FOREIGN KEY (FK_PRODUCT)
REFERENCES MY_PRODUCT (ID) ON DELETE CASCADE;
ALTER TABLE MY_PRODUCT_PARENT_LINK ADD
CONSTRAINT MY_PRD_PARENT_TO_PARENT
FOREIGN KEY (FK_PARENT)
REFERENCES MY_PRODUCT (ID) ON DELETE CASCADE;
INSERT INTO MY_PRODUCT_PARENT_LINK (ID, FK_PRODUCT, FK_PARENT, DESCENDANT_LVL) VALUES (211111, 111112, 111111, 1);
Try and select the ID in the outer query and see if that returns the result. so instead of select * in the outer do select ID.

SQLite cascading delete

The parent table is:
CREATE TABLE BHEAD (
ID INTEGER primary key asc,
DESCR TEXT,
LINECTR INT,
UNITCTR INT)
The child table is:
CREATE TABLE BDET (
ID INTEGER primary key asc,
BID INTEGER,
BCODE TEXT,
QTY INTEGER,
FOREIGN KEY (BID) REFERENCES BHEAD(ID) ON DELETE CASCADE
)
I also execute the SQL PRAGMA foreign_keys = ON;.
However, it does not work; when I delete one row from BHEAD, its associated rows in BDET are not gone...
Why was that?
What version of SQLite are you using?
Please see: Foreign Keys.
In order to use foreign key constraints in SQLite, the library must be compiled with neither SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER defined.
However, you can also implement on delete cascade to delete all child
rows when a parent row is deleted.
-- Create the test tables using ON DELETE CASCADE
DROP TABLE t3 PURGE;
--DROP TABLE t2 PURGE;
--DROP TABLE t1 PURGE;
CREATE TABLE t1 (
id NUMBER,
description VARCHAR2(50),
CONSTRAINT t1_pk PRIMARY KEY (id)
);
CREATE TABLE t2 (
id NUMBER,
t1_id NUMBER,
description VARCHAR2(50),
CONSTRAINT t2_pk PRIMARY KEY (id),
CONSTRAINT t2_t1_fk FOREIGN KEY (t1_id) REFERENCES t1 (id) ON DELETE CASCADE
);
CREATE TABLE t3 (
id NUMBER,
t2_id NUMBER,
description VARCHAR2(50),
CONSTRAINT t3_pk PRIMARY KEY (id),
CONSTRAINT t3_t2_fk FOREIGN KEY (t2_id) REFERENCES t2 (id)
);
INSERT INTO t1 VALUES (1, 't1 ONE');
INSERT INTO t2 VALUES (1, 1, 't2 ONE');
INSERT INTO t2 VALUES (2, NULL, 't2 TWO');
INSERT INTO t3 VALUES (1, 1, 't3 ONE');
INSERT INTO t3 VALUES (2, NULL, 't3 TWO');
COMMIT;
SELECT (SELECT COUNT(*) FROM t1) AS t1_count,
(SELECT COUNT(*) FROM t2) AS t2_count,
(SELECT COUNT(*) FROM t3) AS t3_count
FROM dual;
DELETE FROM t3;
rollback;
truncate table t1 ;
rollback;
truncate table t1 CASCADE;

How to insert row in table with foreign key to itself?

I have table that has foreign key for itself. Column parentid is foreign key and it cannot be NULL.
if I doINSERT INTO mytable(name) VALUES('name'), so it says that can't insert NULL to parentid. BUT, what value I can set to it if no row was inserted yet?!
How I can write script that will add row to this table?
Thank you
Remove the NOT NULL constraint, as it is an inappropriate constraint. If you do not have a ParentId then the value is NULL and should be allowed. Creating a dummy row just to have a dummy parentid creates unnecessary dependencies.
A trick: Have a dummy row with a dummy key, say 99999. Insert with this as the FK, and then change the FK to its real value. And do it in a transaction.
Disable the FK in charge.
Then do the insert
Then do an update with the new (generated?) PK-ID into the Self-FK-Field
Then Enable the FK back.
LIke so:
ALTER TABLE [Client] NOCHECK CONSTRAINT [FK_Client_MainClient]
INSERT INTO Client VALUES ...
#ClientID = SCOPE_IDENTITY()
IF #IsMainClient=1
BEGIN
UPDATE [Client]
SET MainClientID = #ClientID
WHERE ClientID = #ClientID
END
ALTER TABLE [Relatie] WITH CHECK CHECK CONSTRAINT [FK_Relatie_Relatie]
How to make a dummy row with both id and parentid equal to -1:
CREATE TABLE mytable(
id int NOT NULL IDENTITY,
parentid int NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (parentid) REFERENCES mytable(id)
) ;
SET IDENTITY_INSERT mytable ON ; <-- this allows you to insert the
INSERT INTO mytable(id, parentid) <-- auto incremented identity field
VALUES (-1, -1);
SET IDENTITY_INSERT mytable OFF ;
SELECT * FROM mytable ;
| id | parentid |
| -1 | -1 |
If you have many data from other tables that you want to transfer into this table, you can set the IDENTITY_INSERT variable to ON, insert the data and then set it to OFF again.
But as others said, it might be better to just remove the NOT NULL constraint from the parentid field.
You can alter the column to allow null then set the fk to the new identity and enable not null again.
This should work, though maybe there is a better way
CREATE TABLE mytable
(
id int IDENTITY(1,1) primary key,
name varchar(50) not null,
parentid int not null
)
go
alter table mytable
add constraint FK_mytable_parentid FOREIGN KEY ( parentid ) references mytable(id)
ALTER TABLE mytable alter column parentid int null;
insert into mytable(name)
select 'test'
update mytable
set parentid = SCOPE_IDENTITY()
where id = SCOPE_IDENTITY()
ALTER TABLE mytable alter column parentid int not null;
select * from mytable
drop table mytable
From what I understood you already have id before inserting and you can't insert it because identity field isn't letting you to.
Like you mentioned in your comment:
in 1 table I have the rows 34 'name1'
34, 35 'name2' 34 (id,name,parentid)
and I want to copy them to other table
First table
create table Table1
(
id int not null primary key,
name varchar(20) not null,
parentId int not null
)
insert Table1
values
(34, 'name1', 34),
(35, 'name2', 34)
Second table:
create table Table2
(
id int identity(1, 1) primary key,
name varchar(20) not null,
parentId int not null foreign key references Table2(id)
)
Copying data from the first table to the second one:
set identity_insert Table2 on
insert Table2(id, name, parentId)
select *
from Table1
set identity_insert Table2 on
[Update]
If the second table already has values then:
alter table Table2
add oldId int null
alter table Table2
alter column parentId int null
go
insert Table2(name, oldId)
select name, id
from Table1
update tt3
set parentId = tt2.id
from Table2 tt3
join Table1 tt1 on
tt1.id = tt3.oldId
join Table2 tt2 on
tt1.parentId = tt2.oldId
alter table Table2
drop column oldId
alter table Table2
alter column parentId int not null
Don't reference an IDENTITY column as a self-referencing foreign key. Use an alternative key of the table instead. I guess you are using IDENTITY as a surrogate key but every table ought to have a natural key as well, so the IDENTITY column shouldn't be the only key of your table.