Why does postgreSQL check foreign key constraints twice? - database

I have a postgresql database with two tables, one which has a foreign key constraint which references the other. The constraint is set to cascade on update and delete, so an update on the referenced table should cascade to the referencing table. When I analyzed an update on the referenced table, using explain analyze the I got the following output:
...
Trigger for constraint foreign_key_constraint on referencedtable: time=33.840 calls=1
Trigger for constraint foreign_key_constraint on referencingtable: time=2.394 calls=40
...
I assume the second trigger is called 40 times as this is the amount of times the updated value appeared in this relation.
My question is why does the constraint need to be checked for both tables? Also, why does the trigger for checking the referenced table take so much longer then the referencing table, as it is called only once compared to the referencing table trigger which is called 40 times. If anyone can shed some light as to what exactly is happening or why it takes so long that would be greatly appreciated.
Here is the schema:
CREATE TABLE course
(
course_id BIGINT CONSTRAINT course_pk PRIMARY KEY,
title CHAR(40)
);
CREATE TABLE lecturer
(
lecturer_id BIGINT CONSTRAINT lecturer_pk PRIMARY KEY,
lec_name CHAR(40)
);
CREATE TABLE teaches
(
lecturer_id BIGINT CONSTRAINT teaches_lecturer_fk1 REFERENCES lecturer (lecturer_id),
course_id BIGINT CONSTRAINT teaches_course_fk1 REFERENCES course (course_id),
desc_teaches CHAR(40),
CONSTRAINT teaches_pk PRIMARY KEY (lecturer_id, course_id)
);
Query and output:
explain analyze UPDATE lecturer SET lecturer_id = 301 WHERE lecturer_id = 57;
Trigger for constraint teaches_lecturer_fk1 on lecturer: time=33.840 calls=1
Trigger for constraint teaches_lecturer_fk1 on teaches: time=2.394 calls=40
Thanks in advance.

(Worked out by reproducing your example, using PostgreSQL 8.4.4, and examining the pg_trigger and pg_proc tables, as well as the latest source.)
Triggers are run twice: once when lecturer is updated in the main statement; then again when each teaches row is updated to refer to the new value in lecturer.
The constraints are implemented as PostgreSQL triggers. When the UPDATE statement is executed, PG recognises that there a trigger on UPDATE of lecturer. It then executes the corresponding trigger function, the PostgreSQL internal function RI_FKey_cascade_upd. This amounts to the first Trigger event in the explain output:
Trigger for constraint teaches_lecturer_fk1 on lecturer: time=33.840 calls=1
After performing a few checks, RI_FKey_cascade_upd generates an UPDATE statement for teaches, to update the foreign key for the new value in lecturer. As there is also an UPDATE trigger on this table, using the internal function RI_FKey_check_upd. This function checks that the new value matches a corresponding row in the PK table. The trigger is called for each row FK is changing as a result of the cascaded update. This explains the second event in the explain output:
Trigger for constraint teaches_lecturer_fk1 on teaches: time=2.394 calls=40
Presumably there were 40 rows in teaches that were subject to the cascaded update.
I'm not sure where the costs for each trigger come from. I thought at first that the cascaded trigger's costs would be included in the main one, but a test with 10000 affected rows in teaches doesn't support this:
Trigger for constraint teaches_lecturer_fk1 on lecturer: time=540.886 calls=1
Trigger for constraint teaches_lecturer_fk1 on teaches: time=808.930 calls=10000
Total runtime: 1377.017 ms
But then the version I'm running is not the same as the newest code, so perhaps there's been a change since 8.4.4 that optimises RI_FKey_cascade_upd. Or, just as likely, I'm not reading the code properly...

Every row has it's own check, 40 updates, 40 checks. It's a row based trigger. You see two triggers in two different tables, that must be because of different checks. Could you show us the database schema involved in your update? And your update query as well, that might help to understand what is going on in your database.
your_database <> my_database
Take a look at pg_trigger, it will show you the FK-triggers.

Setting a foreign key does not also set an index to make the check fast, you have to do that separately. Without the index, a delete on the master table will cause a table-scan on the foreign table.
Also, it is possible to inadvertently create the same constraint multiple times which causes postgres to perform the check multiple times.

Related

Resetting the primary key to 1

I have a script for microsoft sql server database which has hundreds of tables and tables contains data as well. This is the database of a web application.what I want to do is to delete the previous records and reset the primary key to 1 or 0.
I have tried
`DBCC CHECKIDENT ('dbo.tbl',RESEED,0); `
but it does not work for me as in most of the tables the primary key is not identity.
I can not truncate the table as its primary key is being used as FK in many other tables.
I have also tried to add the identity specification in the primary key of the table and run the checkident query and then changing it back to non-identity spec, but after adding the record again it starts from where it left.
Making changes in the code is not an option for me.
please help.
According with your question I am not sure about the main objective, Why? If you need truncate a lot of tables and change their structures to have an Identity property why you can't disabled the FK? . In the past I have used an standard process for rebuild a table and migrate all the information, this represent a group of steps, I would try to help you but you should follow the next steps.
Steps:
1) Disable FK for alter the structure of your tables. You can get the solution for this task in the next link:
Temporarily disable all foreign key constraints
2) Alter the table with the new property Identity, this is a classic process of ALTER TABLE xxxxxx.
3) Execute the syntax that previously posted :
DBCC CHECKIDENT ('dbo.tbl',RESEED,0);
Try to follow this path and if you have any problem only ask us.
You can not truncate table that have relation. You shoud remove relation firstly.
My understanding of this question:
You have a database with tables that you want to empty and next have them use primary key values starting at 0 or 1.
Some of these tables use an identity value and you already have a solution for those (you know you can find out which columns have an identity by using the sys.columns view? Look for the is_identity column).
Some tables do not use an identity but get their pk values from an unknown source, which we can't modify.
The only solution I see, is creating an after insert trigger (or modifying) on those tables that subtracts from the new pk value.
E.g.: your "hidden generator" will generate a next value 5254, but you want the next pk value to become one:
CREATE TRIGGER trg_sometable_ai
ON sometable
AFTER INSERT
AS
BEGIN
UPDATE st
SET st.pk_col = st.pk_col - 5253
FROM sometable AS st
INNER JOIN INSERTED AS i
ON i.pk_col = th.pk_col
END
You'll have to determine the next value and thus the "subtract value" for each table.
If the code also inserts child records into tables with a foreign key to this table, and uses the previously generated value, you have to modify those triggers as well...
This is a "last resort" solution and something I would recommend against in any scenario that has other options. Manipulating primary key values is generally not a good idea.

unable to truncate a parent table even after disabling a FK constraint in sql server

I am unable to truncate from a parent table by only disabling the FK constraint in the referring table . I had to actually drop the constraint in the referring table to be able to truncate the parent table.
I did the above step after first truncating the child table.....so I did follow the right order....do i really need to delete the constraint and then bulk insert the data and then re-create the FK constraint?
If you wanna use TRUNCATE then yes, dropping and recreating the constraint is the only way.
Confirmation in doc (with more explanations)
You cannot use TRUNCATE TABLE on a table referenced by a FOREIGN KEY
constraint; instead, use
DELETE statement without a WHERE clause. Because TRUNCATE TABLE is not
logged, it cannot activate a trigger.
If the child table is empty (or all of the relevant FK values are set to NULL), you should be able to DELETE from the parent table without disabling or dropping the constraint.
However you won't be able to use TRUNCATE even if you disable the constraint and even if the child table is empty. In this case SQL Server checks at the DDL level that the constraint exists and bails out long before it will ever bother checking the data for constraint violations (so it doesn't matter that the table is empty).
So, just use DELETE in your case. Unless you are doing this 50,000 times a day, trying to get this to work with TRUNCATE seems to be a lot of trouble for very little gain.

Enable foreign key with Check existing data

I love foreign keys, but I'm running into one problem with them. I have a conversion program where I am disabling foreign keys to tables. The reason I'm doing this is so that I can reconvert all records in the main table, but leave the other tables dependent on that one untouched without having to reconvert them every time because they are HUGE.
I'm using these commands to disable and re-enable the foreign keys:
ALTER TABLE MyTable NOCHECK CONSTRAINT MyConstraint
ALTER TABLE MyTable CHECK CONSTRAINT MyConstraint
However, after I re-enable the constraint "Check Existing Data on Creation or Re-Enabling" is still set to No. I understand that it is set to No because I disabled the constraint, but by doing this it altered my database schema, which I don't like. I thought this would be considered re-enabling the constraint and would check the existing data, but apparently not.
Is there no way to change this with the ALTER TABLE command? I know I can if I drop the constraint and recreate it, but I'm not about to write the script to recreate every foreign key I have and maintain that.
I'm using SQL Server 2008 R2.
To re-enable a constraint:
-- Enable the constraint
ALTER TABLE MyTable
WITH CHECK CHECK CONSTRAINT MyConstraint
GO
Note: you have to specify CHECK twice to force a check that all foreign key values are valid.
FOREIGN KEY and CHECK constraints that are disabled are marked
is_not_trusted.These are viewable in the sys.check_constraints and
sys.foreign_keys catalog views. This means that the constraint is no
longer being verified by the system for all rows of the table. Even
when you re-enable the constraint, it will not reverify the existing
rows against the table unless you specify the WITH CHECK option of
ALTER TABLE. Specifying WITH CHECK marks the constraint as trusted
again.
Ref.: Guidelines for Disabling Indexes and Constraints
As noted in comments (for search engines), this corresponds to
sys.foreign_keys.is_not_trusted
in the catalog view

SQL Server update primary key that's also a foreign key in two tables

I need to update the primary key for a record but it's also the foreign key in two other tables. And I need the updated primary key to be reflected in the child tables as well.
Here is my query and the error:
begin tran
update question set questionparent = 10000, questionid= 10005 where questionid = 11000;
Error 9/4/2009 10:04:49 AM 0:00:00.000 SQL Server Database Error: The UPDATE statement conflicted with the REFERENCE constraint "FK_GoalRequirement_Question". The conflict occurred in database "numgmttest", table "dbo.GoalRequirement", column 'QuestionID'. 14 0
I don't remember how to go about doing this so that's why I'm here. Any help?
Are your relationships using
ON UPDATE CASCADE
If they are then changing the key in the primary table will update the foreign keys.
e.g.
ALTER TABLE Books
ADD CONSTRAINT fk_author
FOREIGN KEY (AuthorID)
REFERENCES Authors (AuthorID) ON UPDATE CASCADE
You may:
disable enforcing FK constraints temporarily (see here or here)
update your PK
update your FKs
enable back enforcing FK constraints
do it all within a transaction and make sure that if transaction fails, you roll it back properly and still enforce the FK constraints back.
But... why do you need to change a PK? I hope this is an action that is executed rarely (legacy data import or something like that).
If you would like to set the Cascade rule graphically then Set Cascade Rule on SQL Management Studio
Open table in design mode
Click Relationship button from top toolbar
Select the required FK relations (one by one)
Right Side - Expand INSERT or UPDATE Specification
Change the UPDATE Rule to - Cascade
Close and Save, Done!
(Tried on SQL 2008)
As I'm not too confident disabling FK constraints, I prefer too :
Duplicate the row with the old PK with one with the new PK
Update the FKs
Delete the row with the old PK
Advantage : No constraint violated during the process.
Go to foreign Key Relations of each child tables and on Insert and Update specification change delete and update rules to cascade.
create a New row with the same data and a different primary key.
update all the children tables.
remove the row that you repeated its data
And its done.

Introducing FOREIGN KEY constraint 'c_name' on table 't_name' may cause cycles or multiple cascade paths

I have a database table called Lesson:
columns: [LessonID, LessonNumber, Description] ...plus some other columns
I have another table called Lesson_ScoreBasedSelection:
columns: [LessonID,NextLessonID_1,NextLessonID_2,NextLessonID_3]
When a lesson is completed, its LessonID is looked up in the Lesson_ScoreBasedSelection table to get the three possible next lessons, each of which are associated with a particular range of scores. If the score was 0-33, the LessonID stored in NextLessonID_1 would be used. If the score was 34-66, the LessonID stored in NextLessonID_2 would be used, and so on.
I want to constrain all the columns in the Lesson_ScoreBasedSelection table with foreign keys referencing the LessonID column in the lesson table, since every value in the Lesson_ScoreBasedSelection table must have an entry in the LessonID column of the Lesson table. I also want cascade updates turned on, so that if a LessonID changes in the Lesson table, all references to it in the Lesson_ScoreBasedSelection table get updated.
This particular cascade update seems like a very straightforward, one-way update, but when I try to apply a foreign key constraint to each field in the Lesson_ScoreBasedSelection table referencing the LessonID field in the Lesson table, I get the error:
Introducing FOREIGN KEY constraint 'c_name' on table 'Lesson_ScoreBasedSelection' may cause cycles or multiple cascade paths.
Can anyone explain why I'm getting this error or how I can achieve the constraints and cascading updating I described?
You can't have more than one cascading RI link to a single table in any given linked table. Microsoft explains this:
You receive this error message because
in SQL Server, 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. For example, the
tree of cascading referential actions
must only have one path to a
particular table on the cascading
referential actions tree.
Given the SQL Server constraint on this, why don't you solve this problem by creating a table with SelectionID (PK), LessonID, Next_LessonID, QualifyingScore as the columns. Use a constraint to ensure LessonID and QualifyingScore are unique.
In the QualifyingScore column, I'd use a tinyint, and make it 0, 1, or 2. That, or you could do a QualifyingMinScore and QualifyingMaxScore column so you could say,
SELECT * FROM NextLesson
WHERE LessonID = #MyLesson
AND QualifyingMinScore <= #MyScore
AND #MyScore <= QualifyingMaxScore
Cheers,
Eric

Resources