mass update of primary keys along with foreign keys - sql-server

Here is a sample
CREATE TABLE A(ida int PRIMARY KEY);
CREATE TABLE B(idb int PRIMARY KEY, ida int);
ALTER TABLE B
ADD CONSTRAINT FK_B_ida FOREIGN KEY (ida) REFERENCES A(ida);
INSERT INTO A (ida) VALUES (1);
INSERT INTO B (idb, ida) VALUES (1, 1);
BEGIN TRANSACTION
UPDATE A
SET ida = ida + 1000;
UPDATE B
SET ida = ida + 1000;
COMMIT TRANSACTION;
When running the part between BEGIN TRANSACTION and COMMIT TRANSACTION (all 4 lines), I get an error
Msg 547 - UPDATE instruction conflicts with FOREIGN KEY constraint "FK_B_ida"
In the context of a data migration project (merging 2 dbs with the same schema), I need to mass update primary keys (to avoid collisions) and make sure foreign keys follow.
What is the recommended way to do this?
I was thinking, I could offset keys during copy, but that might make the copy script complex (difficult to avoid listing column names)

Related

Circular delete cascade in Postgres

(For background I'm using Postgres 12.4)
I'm unclear why deletes work when there are circular FKs between two tables and both FKs are set to ON DELETE CASCADE.
CREATE TABLE a (id bigint PRIMARY KEY);
CREATE TABLE b (id bigint PRIMARY KEY, aid bigint references a(id) on delete cascade);
ALTER TABLE a ADD COLUMN bid int REFERENCES b(id) ON DELETE CASCADE ;
insert into a(id) values (5);
insert into b(id, aid) values (10,5);
update a set bid = 10 where id=5;
DELETE from a where id=5;
The way that I am thinking about this, when you delete the row in table 'a' with PK id = 5, postgres looks at tables that have a referential constraint referencing a(id), it finds b, it tries to delete the row in table b with id = 10, but then it has to look at tables referencing b(id), so it goes back to a, and then it should just end up in an infinite loop.
But this does not seem to be the case. The delete completes without error.
It's also not the case, as some sources say online, that you cannot create the circular constraint. The constraints are created successfully, and neither of them is deferrable.
So my question is - why does postgres complete this circular cascade even when neither constraint is set to deferrable, and if it's able to do so, then what is the point of even having a DEFERRABLE option?
Foreign key constraints are implemented as system triggers.
For ON DELETE CASCADE, this trigger will run a query like:
/* ----------
* The query string built is
* DELETE FROM [ONLY] <fktable> WHERE $1 = fkatt1 [AND ...]
* The type id's for the $ parameters are those of the
* corresponding PK attributes.
* ----------
*/
The query runs is a new database snapshot, so it cannot see rows deleted by previous RI triggers:
/*
* In READ COMMITTED mode, we just need to use an up-to-date regular
* snapshot, and we will see all rows that could be interesting. But in
* transaction-snapshot mode, we can't change the transaction snapshot. If
* the caller passes detectNewRows == false then it's okay to do the query
* with the transaction snapshot; otherwise we use a current snapshot, and
* tell the executor to error out if it finds any rows under the current
* snapshot that wouldn't be visible per the transaction snapshot. Note
* that SPI_execute_snapshot will register the snapshots, so we don't need
* to bother here.
*/
This makes sure that no RI trigger will try to delete the same row a second time, and thus circularity is broken.
(All quotations taken from src/backend/utils/adt/ri_triggers.c.)

Postgresql problems with postgres_fdw module

I'm trying, using the PostgreSQL Maestro tool, to reference a foreign key coming from a "local" DB to an other primary key inside an other DB (actually, they're both on the same remote machine).
I've heard about the postgres_fdw module to create a foreign table that act like a copy of the table inside the remote DB, but when I try to execute my query I have this error:
"SQL Error: ERROR: referenced relation "foreign_olo" is not a table".
This is my sql code:
CREATE TABLE edb.olo_config (
primary_key integer NOT NULL PRIMARY KEY,
puntamento varchar,
mail_contatto_to varchar,
mail_contatto_cc varchar,
/* Foreign keys */
CONSTRAINT olo_code
FOREIGN KEY (olo_code)
REFERENCES edb.foreign_olo(codice_operatore)
) WITH (
OIDS = FALSE
);
foreign_olo is my foreign table created with postgres_fdw.
I have tried to commit an INSERT or a simple SELECT on the foreign_olo table, and all went well, so I can't understand why for the foreign key case it can't be recognized as a table.
Thank you to everyone would give me an hand!
There are two parts to enforcing a foreign key constraint:
An INSERT (or UPDATE of the FK field) on the child table must check that a parent record exists.
A DELETE (or UPDATE of the PK field) on the parent table must check that no child record exists.
Foreign tables can't be referenced by FK constraints, because this second condition is impossible to enforce - there's no way for the remote server to automatically check for records in your local table.
You can implement the first check relatively easily with a trigger:
CREATE FUNCTION edb.olo_config_fk_trg() RETURNS TRIGGER AS
$$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM edb.foreign_olo
WHERE codice_operatore = new.olo_code
FOR KEY SHARE
)
THEN
RAISE foreign_key_violation
USING MESSAGE = 'edb.foreign_olo.codice_operatore="' || new.olo_code || '" not found';
END IF;
RETURN NULL;
END
$$
LANGUAGE plpgsql;
CREATE TRIGGER olo_config_fk_trg
AFTER INSERT OR UPDATE OF olo_code ON edb.olo_config
FOR EACH ROW EXECUTE PROCEDURE edb.olo_config_fk_trg();
You can create a similar trigger on the PK table to do the update/delete check, but it will require another foreign table in your other database which points back to your local olo_config.

mssql table multi foreign key cascade

I'm confident that this is possible but for the life of me I can't figure it out.
What I have created is a user history MSSQL table to hold the changes made to a user and by whom. This table contains two foreign keys which reference my other table (User) - one fkey for the affected user and the other fkey for the user making the changes.
What I need is for any changes to the (User) table to cascade and update the corresponding entries in this new table.
The fields in the new table (User_History) are as follows (Each user is identified by two fields):
Affected_User_House_Id - int
Affected_User_Id - int
Modified_By_User_House_Id - int
Modified_By_User_Id – int
Modification_Date - datetime
ModificationMade - ntext
Each field is a primary key except for ‘ModificationMade’. The field ‘Modification_Date’ is accurate down to 1 second.
The problem I am having is creating said cascade.
I have tried running the following T-SQL code:
ALTER TABLE [User_History] WITH CHECK
ADD CONSTRAINT [FK_User_History_User] FOREIGN KEY([Affected_User_House_Id], [Affected_User_Id])
REFERENCES [User] ([User_House_Id], [User_ID])
ON UPDATE CASCADE
GO
ALTER TABLE [User_History] CHECK CONSTRAINT [FK_User_History_User]
GO
ALTER TABLE [User_History] WITH CHECK
ADD CONSTRAINT [FK_User_History_User_ModifiedBy] FOREIGN KEY([Modified_By_User_House_Id], [Modified_By_User_Id])
REFERENCES [User] ([User_House_Id], [User_ID])
ON UPDATE CASCADE
GO
ALTER TABLE [User_History] CHECK CONSTRAINT [FK_User_History_User_ModifiedBy]
GO
This T-SQL gave me the following error:
*'User' table saved successfully
'User_History' table
- Unable to create relationship 'FK_User_History_User_ModifiedBy'.
Introducing FOREIGN KEY constraint 'FK_User_History_User_ModifiedBy' on table 'User_History' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.*
The code works if I remove the second “ON UPDATE CASCADE” the however that will mean the values in the fields “Modified_By_User_House_Id” and “Modified_By_User_Id” will not be updated to match their referenced values in the User table.
I am at a lost as to how to acomplish this goal.
You can only specify a single cascade. Here's an attempt to simulate multiple cascades with two triggers:
create table TabA (
ID1 int not null,
ID2 int not null,
_RowID int IDENTITY(1,1) not null,
constraint PK_TabA PRIMARY KEY (ID1,ID2),
constraint UQ_TabA__RowID UNIQUE (_RowID)
)
go
create table TabB (
ID1a int not null,
ID2a int not null,
ID1b int not null,
ID2b int not null,
constraint PK_TabB PRIMARY KEY (ID1a,ID2a,ID1b,ID2b)
)
They're simpler than your tables, but hopefully close enough. We need an immutable identifier in TabA, and obviously the IDs aren't it, since the whole point is to cascade changes to them. So I've added _RowID.
It would be nice to implement at least a real foreign key and just simulate the cascade behaviour on top of that, but some simple reflection will demonstrate that there's always a point where the FK would be broken. So we simulate it:
create trigger FK_TabB_TabA on TabB
after insert,update
as
set nocount on
if exists (
select
*
from
inserted i
left join
TabA a
on
i.ID1a = a.ID1 and
i.ID2a = a.ID2
left join
TabA b
on
i.ID1b = b.ID1 and
i.ID2b = b.ID2
where
a._RowID is null or
b._RowID is null)
begin
declare #Error varchar(max)
set #Error = 'The INSERT statement conflicted with the Foreign Key constraint "FK_TabB_TabA". The conflict occurred in database "'+DB_NAME()+'", table "dbo.TabB".'
RAISERROR(#Error,16,0)
rollback
end
And then the cascading update:
create trigger FK_TabB_TabA_Cascade on TabA
after update
as
set nocount on
;with Updates as (
select
d.ID1 as OldID1,
d.ID2 as OldID2,
i.ID1 as NewID1,
i.ID2 as NewID2
from
inserted i
inner join
deleted d
on
i._RowID = d._RowID
)
update b
set
ID1a = COALESCE(u1.NewID1,ID1a),
ID2a = COALESCE(u1.NewID2,ID2a),
ID1b = COALESCE(u2.NewID1,ID1b),
ID2b = COALESCE(u2.NewID2,ID2b)
from
TabB b
left join
Updates u1
on
b.ID1a = u1.OldID1 and
b.ID2a = u1.OldID2
left join
Updates u2
on
b.ID1b = u2.OldID1 and
b.ID2b = u2.OldID2
where
u1.OldID1 is not null or
u2.OldID1 is not null
go
Some simple inserts:
insert into TabA (ID1,ID2)
values (1,1),(1,2),(2,1),(2,2)
go
insert into TabB (ID1a,ID2a,ID1b,ID2b)
values (1,1,2,2)
Then the following gets an error. Not quite like a built in FK violation, but close enough:
insert into TabB (ID1a,ID2a,ID1b,ID2b)
values (1,1,2,3)
--Msg 50000, Level 16, State 0, Procedure FK_TabB_TabA, Line 28
--The INSERT statement conflicted with the Foreign Key constraint "FK_TabB_TabA". The conflict occurred in database "Flange", table "dbo.TabB".
--Msg 3609, Level 16, State 1, Line 1
--The transaction ended in the trigger. The batch has been aborted.
This is the update that we wanted to be able to perform:
update TabA set ID2 = ID2 + 1
And we query the FK table:
select * from TabB
Result:
ID1a ID2a ID1b ID2b
----------- ----------- ----------- -----------
1 2 2 3
So the update cascaded.
Why you can't use real FKs:
You want to have cascading updates. That means that the ID values in TabA are going to change to a new value that doesn't currently exist (caveat - we're excluding situations where 2n rows swap their identity values) - otherwise, the primary key constraint will be broken by this update.
As such, we know that the new key value will not yet exist. If we were to attempt cascading updates using an INSTEAD OF trigger (to update the child table before the parent) then the new values we attempt to update to in TabB do not yet exist. Alternately, if we attempt to do cascading updates using an AFTER trigger - well, we're too late. The FK constraint has already prevented the update.
I suppose you could implement an INSTEAD OF trigger that inserts the new rows as "duplicates", updates the children, then deletes the old rows. In such a circumstance, I think you could have real FKs. But I don't want to try writing that trigger to be right in all circumstances (e.g where you have three rows being updated. Two swap their ID values and the other creates a new ID)
According to this knowledge base article, this error message occurs when "a table cannot appear more than one time in a list of all the cascading referential actions that are started by either a DELETE or an UPDATE statement."
Since you have two paths coming from the same table, a possible workaround may involve creating a new key on the parent table and creating a single foreign key on the child ([Affected_User_House_Id], [Affected_User_Id], [Modified_By_User_House_Id], [Modified_By_User_Id]). This however, will likely create a lot of overhead. As a last resort, you can use triggers to enforce the relational integrity.

No foreign key restraints on a temporary table? SQL Server 2008

I know that a a temporary table will only exist for as long as a session of SQL Server is open, but why can't you have foreign key restraints on them?
Imagine this scenario: You create a foreign key relationship from your temp table to a concrete table's key. One of the restrictions on a foreign key relationship is that you cannot delete a row from a key table that is depended upon by your temp table. Now, generally when you create foreign key relationships you know to delete the dependent table rows before deleting the related rows in the key table, but how does a stored procedure or any other call into the database know to delete rows from your temp table? Not only is it impossible to discover spurious foreign key dependencies, other sessions could not reach your temp table even if it could discover the relationship. This leads to spurious failures in delete statements as foreign key constraints restrict the key table for dependent rows.
You can create foreign keys between tables in tempdb. For example, try this:
use tempdb
create table parent
(
parent_key int primary key clustered
)
create table child
(
child_key int primary key clustered,
child_parent_key int
)
alter table child add constraint fk_child_parent foreign key (child_parent_key) references parent(parent_key)
insert into parent(parent_key) select 1
insert into child(child_key, child_parent_key) select 1, 1
insert into child(child_key, child_parent_key) select 2, 2 -- this fails because of the FK constraint
drop table child
drop table parent
Could be because you can't have cross-database foreign key constraints and temp tables technically are created in the TempDB database.
Unless you mean between a temp table and another temp table... but really there's lots of issues you get into when you talk about those kind of constraints on a temp table.

Do Foreign Key constraints get checked on an SQL update statement that doesn't update the columns with the Constraint?

Do Foreign Key constraints get checked on an SQL update statement that doesn't update the columns with the Constraint? (In MS SQL Server)
Say I have a couple of tables with the following columns:
OrderItems
- OrderItemID
- OrderItemTypeID (FK to a OrderItemTypeID column on another table called OrderItemTypes)
- ItemName
If I just update
update [dbo].[OrderItems]
set [ItemName] = 'Product 3'
where [OrderItemID] = 2508
Will the FK constraint do it's lookup/check with the update statement above? (even thought the update is not change the value of that column?)
No, the foreign key is not checked. This is pretty easy to see by examining the execution plans of two different updates.
create table a (
id int primary key
)
create table b (
id int,
fkid int
)
alter table b add foreign key (fkid) references a(id)
insert into a values (1)
insert into a values (2)
insert into b values (5,1) -- Seek on table a's PK
update b set id = 6 where id = 5 -- No seek on table a's PK
update b set fkid = 2 where id = 6 -- Seek on table a's PK
drop table b
drop table a
No. Since the SQL update isn't updating a column containing a constraint, what exactly would SQL Server be checking in this case? This is similar to asking, "does an insert trigger get fired if I only do an update?" Answer is no.
There is a case when the FK not existing will prevent updates to other columns even though the FK is not changed and that is when the FK is created WITH NOCHECK and thus not checked at the time of creation. Per Books Online:
If you do not want to verify new CHECK or FOREIGN KEY constraints
against existing data, use WITH NOCHECK. We do not recommend doing
this, except in rare cases. The new constraint will be evaluated in
all later data updates. Any constraint violations that are suppressed
by WITH NOCHECK when the constraint is added may cause future updates
to fail if they update rows with data that does not comply with the
constraint.

Resources