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

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

Related

Change dependent records on delete in SQL

I'm adding a new job category to a database. There are something like 20 tables that use jobCategoryID as a foreign key. Is there a way to create a function that would go through those tables and set the jobCategoryID to NULL if the category is ever deleted in the parent table? Inserting the line isn't the issue. It's just for a backout script if the product owners decide at a later date that they don't want to keep the new job category on.
You need some action. First of all update the dirty records to NULL. For each table use:
Update parent_table
Set jobCategoryID = NULL
WHERE jobCategoryID NOT IN (select jobCategoryID FROM Reerenced_tabble)
Then set delete rule of foreign keys to SET NULL.
If you care about performance issue, follow the below instruction too.
When you have foreign key but dirty records it means, that these constraints are not trusted. It means that SQL Optimizer can not use them for creating best plans. So run these code to see which of them are untrusted to optimizer:
Select * from sys.foreign_keys Where is_not_trusted = 1
For each constraint that become in result of above code edit below code to solve this issue:
ALTER TABLE Table_Name WITH CHECK CHECK CONSTRAINT FK_Name

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.

SQL Server Error: Introducing Foreign Key Constraint [duplicate]

I have a problem when I try to add constraints to my tables. I get the error:
Introducing FOREIGN KEY constraint 'FK74988DB24B3C886' on table 'Employee' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
My constraint is between a Code table and an employee table. The Code table contains Id, Name, FriendlyName, Type and a Value. The employee has a number of fields that reference codes, so that there can be a reference for each type of code.
I need for the fields to be set to null if the code that is referenced is deleted.
Any ideas how I can do this?
SQL Server does simple counting of cascade paths and, rather than trying to work out whether any cycles actually exist, it assumes the worst and refuses to create the referential actions (CASCADE): you can and should still create the constraints without the referential actions. If you can't alter your design (or doing so would compromise things) then you should consider using triggers as a last resort.
FWIW resolving cascade paths is a complex problem. Other SQL products will simply ignore the problem and allow you to create cycles, in which case it will be a race to see which will overwrite the value last, probably to the ignorance of the designer (e.g. ACE/Jet does this). I understand some SQL products will attempt to resolve simple cases. Fact remains, SQL Server doesn't even try, plays it ultra safe by disallowing more than one path and at least it tells you so.
Microsoft themselves advises the use of triggers instead of FK constraints.
A typical situation with multiple cascasing paths will be this:
A master table with two details, let's say "Master" and "Detail1" and "Detail2". Both details are cascade delete. So far no problems. But what if both details have a one-to-many-relation with some other table (say "SomeOtherTable"). SomeOtherTable has a Detail1ID-column AND a Detail2ID-column.
Master { ID, masterfields }
Detail1 { ID, MasterID, detail1fields }
Detail2 { ID, MasterID, detail2fields }
SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }
In other words: some of the records in SomeOtherTable are linked with Detail1-records and some of the records in SomeOtherTable are linked with Detail2 records. Even if it is guaranteed that SomeOtherTable-records never belong to both Details, it is now impossible to make SomeOhterTable's records cascade delete for both details, because there are multiple cascading paths from Master to SomeOtherTable (one via Detail1 and one via Detail2).
Now you may already have understood this. Here is a possible solution:
Master { ID, masterfields }
DetailMain { ID, MasterID }
Detail1 { DetailMainID, detail1fields }
Detail2 { DetailMainID, detail2fields }
SomeOtherTable {ID, DetailMainID, someothertablefields }
All ID fields are key-fields and auto-increment. The crux lies in the DetailMainId fields of the Detail tables. These fields are both key and referential contraint. It is now possible to cascade delete everything by only deleting master-records. The downside is that for each detail1-record AND for each detail2 record, there must also be a DetailMain-record (which is actually created first to get the correct and unique id).
I would point out that (functionally) there's a BIG difference between cycles and/or multiple paths in the SCHEMA and the DATA. While cycles and perhaps multipaths in the DATA could certainly complicated processing and cause performance problems (cost of "properly" handling), the cost of these characteristics in the schema should be close to zero.
Since most apparent cycles in RDBs occur in hierarchical structures (org chart, part, subpart, etc.) it is unfortunate that SQL Server assumes the worst; i.e., schema cycle == data cycle. In fact, if you're using RI constraints you can't actually build a cycle in the data!
I suspect the multipath problem is similar; i.e., multiple paths in the schema don't necessarily imply multiple paths in the data, but I have less experience with the multipath problem.
Of course if SQL Server did allow cycles it'd still be subject to a depth of 32, but that's probably adequate for most cases. (Too bad that's not a database setting however!)
"Instead of Delete" triggers don't work either. The second time a table is visited, the trigger is ignored. So, if you really want to simulate a cascade you'll have to use stored procedures in the presence of cycles. The Instead-of-Delete-Trigger would work for multipath cases however.
Celko suggests a "better" way to represent hierarchies that doesn't introduce cycles, but there are tradeoffs.
There is an article available in which explains how to perform multiple deletion paths using triggers. Maybe this is useful for complex scenarios.
http://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger/
By the sounds of it you have an OnDelete/OnUpdate action on one of your existing Foreign Keys, that will modify your codes table.
So by creating this Foreign Key, you'd be creating a cyclic problem,
E.g. Updating Employees, causes Codes to changed by an On Update Action, causes Employees to be changed by an On Update Action... etc...
If you post your Table Definitions for both tables, & your Foreign Key/constraint definitions we should be able to tell you where the problem is...
This is because Emplyee might have Collection of other entity say Qualifications and Qualification might have some other collection Universities
e.g.
public class Employee{
public virtual ICollection<Qualification> Qualifications {get;set;}
}
public class Qualification{
public Employee Employee {get;set;}
public virtual ICollection<University> Universities {get;set;}
}
public class University{
public Qualification Qualification {get;set;}
}
On DataContext it could be like below
protected override void OnModelCreating(DbModelBuilder modelBuilder){
modelBuilder.Entity<Qualification>().HasRequired(x=> x.Employee).WithMany(e => e.Qualifications);
modelBuilder.Entity<University>.HasRequired(x => x.Qualification).WithMany(e => e.Universities);
}
in this case there is chain from Employee to Qualification and From Qualification to Universities. So it was throwing same exception to me.
It worked for me when I changed
modelBuilder.Entity<Qualification>().**HasRequired**(x=> x.Employee).WithMany(e => e.Qualifications);
To
modelBuilder.Entity<Qualification>().**HasOptional**(x=> x.Employee).WithMany(e => e.Qualifications);
Trigger is solution for this problem:
IF OBJECT_ID('dbo.fktest2', 'U') IS NOT NULL
drop table fktest2
IF OBJECT_ID('dbo.fktest1', 'U') IS NOT NULL
drop table fktest1
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'fkTest1Trigger' AND type = 'TR')
DROP TRIGGER dbo.fkTest1Trigger
go
create table fktest1 (id int primary key, anQId int identity)
go
create table fktest2 (id1 int, id2 int, anQId int identity,
FOREIGN KEY (id1) REFERENCES fktest1 (id)
ON DELETE CASCADE
ON UPDATE CASCADE/*,
FOREIGN KEY (id2) REFERENCES fktest1 (id) this causes compile error so we have to use triggers
ON DELETE CASCADE
ON UPDATE CASCADE*/
)
go
CREATE TRIGGER fkTest1Trigger
ON fkTest1
AFTER INSERT, UPDATE, DELETE
AS
if ##ROWCOUNT = 0
return
set nocount on
-- This code is replacement for foreign key cascade (auto update of field in destination table when its referenced primary key in source table changes.
-- Compiler complains only when you use multiple cascased. It throws this compile error:
-- Rrigger Introducing FOREIGN KEY constraint on table may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION,
-- or modify other FOREIGN KEY constraints.
IF ((UPDATE (id) and exists(select 1 from fktest1 A join deleted B on B.anqid = A.anqid where B.id <> A.id)))
begin
update fktest2 set id2 = i.id
from deleted d
join fktest2 on d.id = fktest2.id2
join inserted i on i.anqid = d.anqid
end
if exists (select 1 from deleted)
DELETE one FROM fktest2 one LEFT JOIN fktest1 two ON two.id = one.id2 where two.id is null -- drop all from dest table which are not in source table
GO
insert into fktest1 (id) values (1)
insert into fktest1 (id) values (2)
insert into fktest1 (id) values (3)
insert into fktest2 (id1, id2) values (1,1)
insert into fktest2 (id1, id2) values (2,2)
insert into fktest2 (id1, id2) values (1,3)
select * from fktest1
select * from fktest2
update fktest1 set id=11 where id=1
update fktest1 set id=22 where id=2
update fktest1 set id=33 where id=3
delete from fktest1 where id > 22
select * from fktest1
select * from fktest2
This is an error of type database trigger policies. A trigger is code and can add some intelligences or conditions to a Cascade relation like Cascade Deletion. You may need to specialize the related tables options around this like Turning off CascadeOnDelete:
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
modelBuilder.Entity<TableName>().HasMany(i => i.Member).WithRequired().WillCascadeOnDelete(false);
}
Or Turn off this feature completely:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Some databases, most notably SQL Server, have limitations on the cascade behaviors that form cycles.
There are two ways to handle this situation:
1.Change one or more of the relationships to not cascade delete.
2.Configure the database without one or more of these cascade deletes, then ensure all dependent entities are loaded so that EF Core can perform the cascading behavior.
please refer to this link:
Database cascade limitations
Mass database update to offset PKs: make a copy of the database instead.
Special use case: company A uses a database with the same schema as company B. Because they have merged, they want to use a single database. Hence, many tables from company B's database must have their primary keys offset to avoid collision with company A's records.
One solution could have been to define foreign keys as ON UPDATE CASCADE, and offset the primary keys having the foreign keys follow. But there are many hurdles if you do that (Msg 1785, Msg 8102, ...).
So a better idea that occurs to me is simply to make a copy of the database, DROP and re CREATE the tables that must have their PKs|FKs offset, and copy the data (and while doing so, offset the primary keys and the foreign keys).
Avoiding all the hassle.
My solution to this problem encountered using ASP.NET Core 2.0 and EF Core 2.0 was to perform the following in order:
Run update-database command in Package Management Console (PMC) to create the database (this results in the "Introducing FOREIGN KEY constraint ... may cause cycles or multiple cascade paths." error)
Run script-migration -Idempotent command in PMC to create a script that can be run regardless of the existing tables/constraints
Take the resulting script and find ON DELETE CASCADE and replace with ON DELETE NO ACTION
Execute the modified SQL against the database
Now, your migrations should be up-to-date and the cascading deletes should not occur.
Too bad I was not able to find any way to do this in Entity Framework Core 2.0.
Good luck!

SQL Server : how to enforce Primary Key cannot exist as Primary Key in another table constraint - trigger

I have a table LotTable that has a PK= LotID, Name, rate.
I have another table LotTranslate that has a PK=TranslateLotID and FK=MasterLotID
Before insert into LotTable I need to make sure enforce the PK inserted is NOT already the PK in LotTranslate.
My question is do I do a trigger instead of insert or Delete it after? What is the most clean way, speedy way to check this other table and stop the insert in LotTable if the PK is found there in LotTranslate?
My direction I am not sure if this is the right SQL Server way...
CREATE TRIGGER tr_LotsInsert ON LotTable
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO dbo.LotTable
SELECT *
FROM INSERTED
WHERE INSERTED.LotID not in (select TranslateLotID from LotTranslate)
END
I don't recommend using a trigger to enforce this.
What you are describing is actually inheritance, where different objects share a base type. In this case, you have the base concept of a Lot (called the supertype), and two mutually exclusive subtypes, LotTable and LotTranslate. (And for the record, I think it unfortunate that your database has a table with the name Table in it, unless it actually deals with some kind of tables that aren't database objects).
There is a reasonably well-established database design pattern to deal with subtypes and supertypes: creating a parent table that is used as the "base object" in the inheritance pattern, and making the subtype tables all have an FK relationship to it. To additionally enforce the mutual exclusivity, you can add a Type column to all the tables and involve it in the foreign key.
Then, your base table participates with the two tables in a 1-to-zero-or-one relationship. The most important concept to get here is that the LotID is always the same in all the tables and you do not create separate surrogate keys for any table: the base/supertype table contains the same values that are in the child/subtype tables.
Before I show you how to accomplish this, let me mention that in this case it's possible your two tables should really be combined into one, with a simple Type column indicating which it is which would of course prevent a single Lot from being two types at once. I'm assuming, however, that your two tables have enough columns different between them that it would be a big waste of NULL values to do so (if there are only a few columns different, it may be better to just combine them).
CREATE TABLE dbo.LotBase (
LotID int NOT NULL CONSTRAINT PK_LotBase PRIMARY KEY CLUSTERED,
LotTypeID tinyint NOT NULL
CONSTRAINT FK_LotBase_LotTypeID FOREIGN KEY
REFERENCES dbo.LotType (LotTypeID),
-- A unique constraint needed for FK purposes
CONSTRAINT UQ_LotBase_LotID_LotTypeID
UNIQUE (LotID, LotTypeID)
);
-- Include script here to create a LotType table and populate it with two rows
-- 1 = `Standard Lot` and 2 = `TranslateLot`
INSERT dbo.LotBase (LotID, LotTypeID)
SELECT LotID, 1
FROM dbo.LotTable;
INSERT dbo.LotBase (LotID, LotTypeID)
SELECT TranslateLotID, 2
FROM dbo.LotTranslate;
ALTER TABLE dbo.LotTable ADD LotTypeID tinyint NOT NULL
CONSTRAINT DF_LotTable_LotTypeID DEFAULT (1);
ALTER TABLE dbo.LotTranslate ADD LotTypeID tinyint NOT NULL
CONSTRAINT DF_LotTranslate_LotTypeID DEFAULT (2);
ALTER TABLE dbo.LotTable ADD CONSTRAINT FK_LotTable_LotBase
FOREIGN KEY (LotID, LotTypeID)
REFERENCES dbo.LotBase (LotID, LotTypeID);
ALTER TABLE dbo.LotTable ADD CONSTRAINT FK_LotTable_LotBase
FOREIGN KEY (LotID, LotTypeID)
REFERENCES dbo.LotBase (LotID, LotTypeID);
Note that you might want to do the work to get the new LotTypeID columns in the child tables to be situated immediately after the LotID columns, but it is up to you--just be careful because it will require table recreation and you can harm your database if you are not knowledgeable and careful (take backups first!).
One huge benefit of this pattern to not miss is that anywhere in your database you want an FK to a Lot, you can choose to either use one of the child tables or to use the parent table. This constrains your other tables to allow either both or just one of the subtypes. Another benefit to not miss is that you can put common columns between the two tables into the parent table instead of repeated in the children. Finally, you can create a view for each child that exposes the combined parent + child columns just like the original child table.
Finally, if you persist in going on with the trigger method, you don't have to use an INSTEAD OF trigger. You can just ROLLBACK any transaction that isn't appropriate:
CREATE TRIGGER TR_LotTable_I ON dbo.LotTable FOR INSERT
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
IF EXISTS (
SELECT *
FROM
Inserted I
INNER JOIN dbo.LotTranslate LT
ON I.LotID = LT.TranslateLotID
) ROLLBACK TRAN;
That's a far better way to handle it (for one thing, you won't have to modify it every time you add a column to your LotTable table. Also, I would recommend that you learn to use (and then consistently use) JOIN syntax instead of the IN syntax you showed. While there is some controversy over this recommendation I'm making, in my experience people who use IN instead of JOINs miss some key conceptual learning that goes on in the process of figuring out how to make them into JOINs. There are other practical benefits such as the fact that nested IN queries get abominably hard to understand and maintain, while adding 5 more JOINs doesn't really make a query much harder to understand when formatted well.

foreign key data deletion should display error for primary key

i have two tables Table1 and Table2. Where table1 column primary key is referred to table2 column as foreign key.
Now i have to display a error message of constraint violation when ever i delete records from table2 which is having foreign key column of table1.
If I get it right your column A (say) in table 1 references column B (say) in table 2.
What you can do is set the ON DELETE to NO ACTION which will prevent deletion of records from table 2 if any children of it still exists in table 1.
You can can do this by:
ALTER TABLE TABLE1 ADD FOREIGN KEY (A) REFERENCES TABLE2 (B) ON DELETE NO ACTION;
You don't have a constraint violation if you delete records from the child table and not the parent. It is normal to delete child records. For instance if I have a user table and a photos tables that contains the userid from the users table, why would I want to stop that action and throw an error if I want to delete a photo? Deleting a child record doesn't also delete the parent.
If you really want to do that, then you must do it through a trigger (make sure to handle multiple record deletes) or if the FK is a required field, then simply don't grant permissions to delete to the table. Be aware that this may mean you can never delete any records even when you try to delete. A simple method may be to not have a delete function available in the application.
I suspect what you really need to a to get a better definition of what is needed in the requirements document. In over 30 years of dealing with hundreds of databases, I have never seen anyone need this functionality.

Resources