Delete empty rows in one-to-many relationship (postgresql) - database

Table book (book_id, title) is the main table.
Table book_version (version_id, book_id,publisher) is another table
book_id is primary key to the book and foreign key (on update cascade on delete cascade) to book_version. They have a one-to-many relationship, since there may be several versions of the same book
Obviously, to delete all versions of a book: DELETE FROM book WHERE book_id = $some_id
However, if I want to delete a specific version we have two cases:
There are several versions of this book, so just delete entry based on version_id: DELETE FROM book_version WHERE version_id = $some_id
There is one single version so far. Not only the row in the book_version has to be deleted, but also the row in the book table
I know it can be done using count (I count the number of rows in book_version. If it is more than one, go to case 1. Else go to case 2). But I'd like to make this happen in a single query, without including two different queries or (if possible) an if statement. Maybe I could do something in the database structure - it doesn't have to be because of the query. Any ideas are much appreciated.

It can be done in a single plain SQL query. SQL Fiddle
with dv as (
delete from book_version
where version_id = 2
returning book_id, version_id
)
delete from book b
using dv
where
b.book_id = dv.book_id
and
not exists (
select version_id
from book_version
where
book_id = dv.book_id
and
version_id != dv.version_id
)

Maybe a trigger could be used here.
Something like:
CREATE TRIGGER deleteIfLastVersionIsDeleted AFTER DELETE ON book_version ...

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

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!

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.

Relation between Inserted and Deleted table's in a trigger

Let's say I have this table :
Car
----------------------
Name|Date|Color
The primary key is a combination of Name and Date.
On the update, if the initial Color of the updated row is Blue and the new one is Red, I want to keep a trace of this update.
This is what I did :
ALTER TRIGGER TraceTrigger
ON Car
FOR UPDATE
AS
BEGIN
INSERT INTO TraceTable
SELECT
del.Name,
del.Date,
del.Color,
ins.Name,
ins.Date,
ins.Color
FROM deleted as del
INNER JOIN inserted as ins
ON del.Name = ins.Name AND del.Date = ins.Date
WHERE del.color = 'Blue' AND ins.Color = 'Red'
END
This example is pretty simple. It show that I need to keep a trace of X old value and X new value from the updated row.
But imagine if the Name can be modified (I know we should not modify PK, but in this situation, it is possible). Given that the primary key can change, sometimes, the relation between the INSERTED and DELETED table's will just not work.
So, it is possible to keep the relation between the deleted row and the inserted row when the PK can be updated to a different value ?
You needn't bother recording both INSERTED and DELETED. Just INSERTED is what I usually do, otherwise you'd end up with 2 of every bit of information. You'll record it when its inserted, then you'll record the identical data when its deleted.
Say you've got a table that just has an ID and a Name field, the trace for that recording both INSERTED and DELETED would look like:
OldID OldName NewID NewName
1 Harry 1 Henry
1 Henry 1 James
1 James 1 Thomas
As you can see, you're doubling up data. The left 2 columns are identical to the right columns except shifted up a row.
In terms of the primary key, if you know you might have to change the PK whilst wanting to maintain a history, I'd strongly recommend adding a surrogate key to the table (e.g ID) that you NEVER change, that way you are free to alter the name column as you wish.
You never really change a primary key; logically, you actually create a new entity (record / row ). It is, in effect, a completely new thing.
There are a number of ways to keep track of this change, but here are two:
Create a row identifier like an IDENTITY column. It's not really a surrogate key, because a surrogate key should always be 1-1 with the proper natural key. Use this if name + date is not really the primary key and you can't create one (yuck - you have a database design issue).
Update the data in your trace table to match the new value anytime a value in the PK changes. This is the proper solution if your database design is correct. You may be able to implement this with an ON UPDATE CASCADE foreign key constraint.

Table design for related products

I’m new to SQL Server and I’ve been assigned to work on implementing “related products” functionality in our existing database. We have a table products that looks like this
ProductID int, PK
Name nvarchar(100)
Price decimal
CoverImageURL nvarchar(400)
-- couple more columns exist
I’m thinking about adding another column to existing database like RelatedProductID but not sure if this is good design.
Your solution will work only if each product can have exactly one related product. Otherwise, if one product can have more than one related products you need a referencing table that will hold all relations between products.
Try adding a new table like this:
CREATE TABLE dbo.RelatedProducts
(
FirstProductId int not null,
SecondProductId int not null
)
So when you need all related products for some product you can retrieve these with following query.
SELECT SecondProductId
FROM RelatedProducts
WHERE FirstProductId = #ProductID
Depends on what kind of relationship you need.
I suspect what you actually need is a "many to many" relationship, in which case you'll need an additional "junction" table:
CREATE TABLE ProductRelation (
ProductID1 int REFERENCES Product (ProductID),
ProductID2 int REFERENCES Product (ProductID),
PRIMARY KEY (ProductID1, ProductID2)
)

Resources