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

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

Related

SQL Server foreign key contsraint preventing insert, where no conflict exits

I have 2 tables, and I have just done an insert of about 1,000,000 rows. I turned off foreign key constraints , but I have an error when I try and reinstate them with
ALTER TABLE ForexRebatesNow.dbo.Transactions WITH CHECK CHECK CONSTRAINT ALL
I get the following error:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_Transactions_RebateAccounts". The conflict occurred in database "ForexRebatesNow", table "dbo.RebateAccounts", column 'Id'.
So I go to look for RebateAccountId that does not have a corresponding Id in the RebateAccounts table
SELECT Id
FROM ForexRebatesNow.dbo.RebateAccounts
WHERE Id NOT IN (SELECT RebateAccountId
FROM ForexRebatesNow.dbo.Transactions)
But this returns zero rows, so in my mind that conflict does not exist.
Am I missing something here?
EDIT
The relationship between RebateAccounts and Transactions is One to Many. RebateAccountId is a Nullable int on Transactions table as not allTransactions will have an associated RebateAccount, but any RebateAccount can have many Transactions
As you have not provided the relations between the two tables, I assume that there is a One-to-Many relation between RebateAccounts and Transactions Table. If I'm right then I guess you are checking the conflict in an opposite way. Your foreign key in in the dbo.Transactions table but you are checking it with the dbo.RebateAccounts table.
Try the following query to find the conflict.
SELECT * FROM Transactions WHERE RebateAccountId NOT IN (SELECT Id FROM RebateAccounts)
If any row shows up then that row has an account id that does not belong to any account. That's where your conflict is.
Hope it helps.

Postgres INSERT INTO... SELECT violates foreign key constraint

I'm having a really, really strange issue with postgres. I'm trying to generate GUIDs for business objects in my database, and I'm using a new schema for this. I've done this with several business objects already; the code I'm using here has been tested and has worked in other scenarios.
Here's the definition for the new table:
CREATE TABLE guid.public_obj
(
guid uuid NOT NULL DEFAULT uuid_generate_v4(),
id integer NOT NULL,
CONSTRAINT obj_guid_pkey PRIMARY KEY (guid),
CONSTRAINT obj_id_fkey FOREIGN KEY (id)
REFERENCES obj (obj_id)
ON UPDATE CASCADE ON DELETE CASCADE
)
However when I try to backfill this using the following code, I get a SQL state 23503 claiming that I'm violating the foreign key constraint.
INSERT INTO guid.public_obj (guid, id)
SELECT uuid_generate_v4(), o.obj_id
FROM obj o;
ERROR: insert or update on table "public_obj" violates foreign key constraint "obj_id_fkey"
SQL state: 23503
Detail: Key (id)=(-2) is not present in table "obj".
However, if I do a SELECT on the source table, the value is definitely present:
SELECT uuid_generate_v4(), o.obj_id
FROM obj o
WHERE obj_id = -2;
"0f218286-5b55-4836-8d70-54cfb117d836";-2
I'm baffled as to why postgres might think I'm violating the fkey constraint when I'm pulling the value directly out of the corresponding table. The only constraint on obj_id in the source table definition is that it's the primary key. It's defined as a serial; the select returns it as an integer. Please help!
Okay, apparently the reason this is failing is because unbeknownst to me the table (which, I stress, does not contain many elements) is partitioned. If I do a SELECT COUNT(*) FROM obj; it returns 348, but if I do a SELECT COUNT(*) FROM ONLY obj; it returns 44. Thus, there are two problems: first, some of the data in the table has not been partitioned correctly (there exists unpartitioned data in the parent table), and second, the data I'm interested in is split out across multiple child tables and the fkey constraint on the parent table fails because the data isn't actually in the parent table. (As a note, this is not my architecture; I'm having to work with something that's been around for quite some time.)
The partitioning is by implicit type (there are three partitions, each of which contains rows relating to a specific subtype of obj) and I think the eventual solution is going to be creating GUID tables for each of the subtypes. I'm going to have to handle the stuff that's actually in the obj table probably by selecting it into a temp table, dropping the rows from the obj table, then reinserting them so that they can be partitioned properly.

Deleting related records in another table with "Where"-like considerations

I have a data table with a primary key called OptDefID. When a record in this table is deleted I need to go and delete all records from the Permissions table that have that OptDefID in the defID field (in Permissions). The tricky part for me is that the Permissions table does not have a primary key and holds lots of different kinds of permissions, and has a permissiontype field. I need to delete rows that have the OptDefID AND a permissiontype of OptDef.
Because I need to consider the permissiontype, I don't believe a Foreign Key Constraint is appropriate here (or is it?).
I've also considered creating a trigger, but am unsure how to get the OptDefID passed into the trigger.
I can do this via the application itself, but I feel like this should be a database level solution.
What's the best solution?
Say I want to delete from Permissions where defID is 20 and permissiontype is 'OptDef'. There may be another row in Permissions that has a defID of 20, but has a permissiontype of 'Member'. That show should not be deleted because it pertains to Members and not Opt data.
Storing table names in fields prevents foreign keys from working properly, as you have discovered.
I recommend you fix the root problem and separate these two foreign keys, so each of them can be individually enforced. For example:
CREATE TABLE Permissions (
...
OptDefId int,
MemberId int,
FOREIGN KEY (OptDefId) REFERENCES OptDef ON DELETE CASCADE,
FOREIGN KEY (MemberId) REFERENCES Members ON DELETE CASCADE,
CHECK (
(OptDefId IS NOT NULL AND MemberId IS NULL)
OR (OptDefId IS NULL AND MemberId IS NOT NULL)
)
)
The CHECK makes sure only one of the FKs is non-NULL and only non-NULL FKs are enforced.
Alternatively, you could avoid changing your current design and enforce this "special" FK through a trigger, but declarative constraints should be preferred to triggers when possible - declarative constraints tend to leave less room for error and be more "self-documenting".
In case the OptDefId column is only filled when the record in question references the Permissions table, a foreign key should be appropriate. I.e. you have another column MemberId, which in turn could be a foreign key on a Members table.
It is only when you have a single column - let's call it ObjectId - which takes on other meanings as the contents of the type column change, that you cannot use foreign keys.
In that case, a trigger would probably be the best approach, as you already guessed. I only know about triggers in Oracle PL/SQL, where they are passed in as separate, complete rows representing the old and new state. I guess it will be analogous in MS-SQL-Server.
In addition to using join with SELECT statements, you can also join multiple tables in DELETE & UPDATE statements as well.
As I understand the issue, you should be able to join the Permissions table to the table with the OptDefID column & add a WHERE clause similar to the this:
DELETE MyTable
...
WHERE [Permissions].permissiontype = 'OptDef'
Also, these links may be of interest too:
SQL DELETE with JOIN another table for WHERE condition (for MySQL, but still relevant)
Using A SQL JOIN In A SQL DELETE Statement
Using A SQL JOIN In A SQL UPDATE Statement

Creating New Foreign Key (SQL Server)

I am having a bit of trouble creating a foreign key in my DB. Here is a paraphrased model of what my tables look like:
NOTE
* (PK) NOTE_ID BIGINT
* TITLE VARCHAR(200)
* DATE DATETIME
* SERIES_ID BIGINT
SERIES
* (PK) SERIES_ID BIGINT
* TITLE VARCHAR(200)
* DESCR VARCHAR(1000)
I am trying to create a "has a" relationship between NOTE and SERIES by SERIES_ID. I thought that setting up a foreign key between the two tables by SERIES_ID would be the solution, but when I attempt to create it I get the following error:
ERROR: There are no primary or candidate keys in the referenced table 'dbo.SERIES' that match the referencing column list in the
foreign key 'FK_SERIES_NOTE'. Could not create constraint
I'm using the web database manager that comes with the GoDaddy SQL Server I set up, so I'm not sure what the underlying query it's trying to use or I would post it.
At the end of the day, this is all to create a relationship so that the NHibernate mappings for my Note object will contain a one-to-one relationship to a Series object. I may not even be trying to tackle this the correct way with the foreign key, though.
Am I going about this the correct way?
EDIT:
In an attempt to pair down the tables to a more simple example, I removed what I thought to be several non-critical columns. However, I ended up leaving a field that was actually a part of the composite primary key on the series table. So, because I was trying to assign the foreign key to only one part of the composite key, it was not allowing me to do so.
In the end, I have taken another look at the structure of my table and found that I don't actually need the other piece of the composite key - and after removing, the assignment of the foreign key works great now.
If you can, you may try running the following statement in a query analyzer and see the resulting error message (I guess #Damien_The_Unbeliever is right ) :
ALTER TABLE NOTE ADD CONSTRAINT FK_SERIES_NOTE
FOREIGN KEY (SERIES_ID) REFERENCES SERIES(SERIES_ID)
--ON DELETE CASCADE
-- uncomment the preceding line if you want a delete on a serie
-- to automatically delete all notes on this serie
Hope this will help

Why does postgreSQL check foreign key constraints twice?

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.

Resources