Delete rows with foreign key in PostgreSQL - sql

I would like to delete rows which contain a foreign key, but when I try something like this:
DELETE FROM osoby WHERE id_osoby='1'
I get this statement:
ERROR: update or delete on table "osoby" violates foreign key constraint "kontakty_ibfk_1" on table "kontakty"
DETAIL: Key (id_osoby)=(1) is still referenced from table "kontakty".
How can I delete these rows?

To automate this, you could define the foreign key constraint with ON DELETE CASCADE.
I quote the the manual for foreign key constraints:
CASCADE specifies that when a referenced row is deleted, row(s)
referencing it should be automatically deleted as well.
Look up the current FK definition like this:
SELECT pg_get_constraintdef(oid) AS constraint_def
FROM pg_constraint
WHERE conrelid = 'public.kontakty'::regclass -- assuming public schema
AND conname = 'kontakty_ibfk_1';
Then add or modify the ON DELETE ... part to ON DELETE CASCADE (preserving everything else as is) in a statement like:
ALTER TABLE kontakty
DROP CONSTRAINT kontakty_ibfk_1
, ADD CONSTRAINT kontakty_ibfk_1
FOREIGN KEY (id_osoby) REFERENCES osoby (id_osoby) ON DELETE CASCADE;
There is no ALTER CONSTRAINT command. Drop and recreate the constraint in a single ALTER TABLE statement to avoid possible race conditions with concurrent write access.
You need the privileges to do so, obviously. The operation takes an ACCESS EXCLUSIVE lock on table kontakty and a SHARE ROW EXCLUSIVE lock on table osoby.
If you can't ALTER the table, then deleting by hand (once) or by trigger BEFORE DELETE (every time) are the remaining options.

One should not recommend this as a general solution, but for one-off deletion of rows in a database that is not in production or in active use, you may be able to temporarily disable triggers on the tables in question.
In my case, I'm in development mode and have a couple of tables that reference one another via foreign keys. Thus, deleting their contents isn't quite as simple as removing all of the rows from one table before the other. So, for me, it worked fine to delete their contents as follows:
ALTER TABLE table1 DISABLE TRIGGER ALL;
ALTER TABLE table2 DISABLE TRIGGER ALL;
DELETE FROM table1;
DELETE FROM table2;
ALTER TABLE table1 ENABLE TRIGGER ALL;
ALTER TABLE table2 ENABLE TRIGGER ALL;
You should be able to add WHERE clauses as desired, of course with care to avoid undermining the integrity of the database.
There's some good, related discussion at http://www.openscope.net/2012/08/23/subverting-foreign-key-constraints-in-postgres-or-mysql/

You can't delete a foreign key if it still references another table.
First delete the reference
delete from kontakty
where id_osoby = 1;
DELETE FROM osoby
WHERE id_osoby = 1;

It's been a while since this question was asked, hope can help.
Because you can not change or alter the db structure, you can do this. according the postgresql docs.
TRUNCATE -- empty a table or set of tables.
TRUNCATE [ TABLE ] [ ONLY ] name [ * ] [, ... ]
[ RESTART IDENTITY | CONTINUE IDENTITY ] [ CASCADE | RESTRICT ]
Description
TRUNCATE quickly removes all rows from a set of tables. It has the same effect as an unqualified DELETE on each table, but since it does not actually scan the tables it is faster. Furthermore, it reclaims disk space immediately, rather than requiring a subsequent VACUUM operation. This is most useful on large tables.
Truncate the table othertable, and cascade to any tables that reference othertable via foreign-key constraints:
TRUNCATE othertable CASCADE;
The same, and also reset any associated sequence generators:
TRUNCATE bigtable, fattable RESTART IDENTITY;
Truncate and reset any associated sequence generators:
TRUNCATE revinfo RESTART IDENTITY CASCADE ;

It means that in table kontakty you have a row referencing the row in osoby you want to delete. You have do delete that row first or set a cascade delete on the relation between tables.
Powodzenia!

One can achieve this by issueing an extra SQL script that deletes the records related via the FK.
For this, using subselect in WHERE clause of a DELETE command can simply do what is needed.
Something similar to:
DELETE FROM kontakty
WHERE fk_column_from_kontakty_matching_id_osoby IN (
SELECT id_osoby FROM osoby WHERE id_osoby = '1'
);
DELETE FROM osoby WHERE id_osoby = '1';
the example assumes the FK column from kontakty matching osoby.id_osoby is called fk_column_from_kontakty_matching_id_osoby as it cannot be interpolated from the error message provided
For the case of OP's the query could by simplified a lot, but I have decided to leave it like this for it demonstrates accomplishing more complex scenarios
This approach is quite useful for SQL migrations, where one cannot depend on an assigned id and where ids cannot be easily fetched first and acted upon later.

Related

If I delete a record from a table it is deleted at many other places

I am working on a database built by the previous team. I have to delete certain records from a table (Example shown below).
DELETE FROM table WHERE id = 5541
While doing this process, some records from the other tables with the same id is getting deleted. Could someone help how to overcome this problem?
In SQL Server, there is statement called ON DELETE CASCADE which deletes the record from the child table if the record is deleted from the parent table. This can be set using the ALTER STATEMENT as shown below. To remove the cascading, try altering the child table back to default. The default is NO CASCADING.
ALTER TABLE ChildTable
ADD CONSTRAINT FKey
FOREIGN KEY (col1, col2, ... coln)
REFERENCES ParentTable (Pcol1, Pcol2, ... Pcoln)
ON DELETE CASCADE
There is UPDATE CASCADE as well if the data in the child table should be updated when the parent table is updated.
You database most certainly contains foreign key constraints with cascading deletes see docs.
You may be able to remove these foreign keys, but of course, then deleting some rows will leave you with inconsistent data.
Another possibilty is to just remove the cascading deletes. But then of course, you won't be able to delete any rows which are referenced by records from other tables, as SQL server will ensure the consistency of your data.

How to update 2 columns in 2 tables that have foreign key

I know the question of how to update multiple tables in SQL has been asked before and the common answer seems to be do them separately in a transaction.
However, the 2 columns I need to update have a foreign key so cannot be updated separately.
e.g.
Table1.a is a foreign key to Table2.a
One of the entries in the tables is wrong, e.g. both columns are 'xxx' and should be 'yyy'
How do I update Table1.a and Table2.a to be 'yyy'?
I know I could temp remove the key and replace but surely there's another way.
Thanks
You can't do the update simultaneously, however you can force SQL to do the update. You need to make sure your foreign keys have the referential triggered action ON UPDATE CASCADE
e.g.
ALTER TABLE YourTable
ADD CONSTRAINT FK_YourForeignKey
FOREIGN KEY (YourForeignKeyColumn)
REFERENCES YourPrimaryTable (YourPrimaryKeyColumn) ON UPDATE CASCADE
Not being a fan of on update cascade, I would suggest a different route.
First you do not update the Parent table, you add a new record with the value you want (and the same data as the other record for all other fields). Then you have no difficulty updating the child tables to use this value instead of that value. Further you now have the ability to to do the work in batches to avoid locking the system up while the change promulgates through it. Once all the child tables have been updated, you can delete the original bad record.
my answer is based on the following link: http://msdn.microsoft.com/en-us/library/ms174123%28v=SQL.90%29.aspx
You need to make sure that your table_constraint will be defined as ON UPDATE CASCADE
CREATE TABLE works_on1
(emp_no INTEGER NOT NULL,
project_no CHAR(4) NOT NULL,
job CHAR (15) NULL,
enter_date DATETIME NULL,
CONSTRAINT prim_works1 PRIMARY KEY(emp_no, project_no),
CONSTRAINT foreign1_works1 FOREIGN KEY(emp_no) REFERENCES employee(emp_no) ON DELETE CASCADE,
CONSTRAINT foreign2_works1 FOREIGN KEY(project_no) REFERENCES project(project_no) ON UPDATE CASCADE)
and then when you will change the value of your primary key
see the following quote:
For ON DELETE or ON UPDATE, if the CASCADE option is specified, the
row is updated in the referencing table if the corresponding
referenced row is updated in the parent table. If NO ACTION is
specified, SQL Server Compact Edition returns an error, and the update
action on the referenced row in the parent table is rolled back.
For example, you might have two tables, A and B, in a database. Table
A has a referential relationship with table B: the A.ItemID foreign
key references the B.ItemID primary key.
If an UPDATE statement is executed on a row in table B and an ON
UPDATE CASCADE action is specified for A.ItemID, SQL Server Compact
Edition checks for one or more dependent rows in table A. If any
exist, the dependent rows in table A are updated, as is the row
referenced in table B.
Alternatively, if NO ACTION is specified, SQL Server Compact Edition
returns an error and rolls back the update action on the referenced
row in table B when there is at least one row in table A that
references it.

How to delete rows in associated SQL tables?

I want to delete rows in GlassesColor table that associated with GlassesID in Glasses table.
I want to implement this in stored procedure.The stored procedure gets only one parameter from client side , a CollectionID.
Here are the following tables:
Here is example of tables content:
Any idea how can i implement this?
Thank you in advance!
Manually deleting would be something like this:
delete
from GlassesColor
where GlassesID in (select GlassesID from Glasses where CollectionID = 3)
However, unless this is a one time cleanup, you should start specifying foreign key cascading rules, like the other answers already suggested.
The easiest way is to define FOREIGN KEYS with CASCADE options for DELETE.
http://msdn.microsoft.com/en-us/library/aa933119%28v=sql.80%29.aspx
I see you already have Foreign Keys defined, so you just need to make sure they have CASCADE option for DELETE.
Without stored procedure, use Constrains -> http://www.mssqlcity.com/Articles/General/using_constraints.htm OnDelete Cascade, so when you delete row in Glasses, it will deleted in Glasses color also.
In your foreign key constraint for the GlassesColor join table, have on delete cascade. What that means is when the primary key referenced record is deleted, the corresponding foreign key reference row will also be deleted.
So your GlassesColor table definition would look like this:
create table GlassesColor
(
GlassesId int foreign key references Glasses(GlassesID) on delete cascade,
.....
)
go
I answered something similar for using the INFORMATION_SCHEMA in MSSQL
SQL Server: drop table cascade equivalent?
Here's the easiest way to do it in Microsoft SQL Server: when you create the foreign key constraint, add ON UPDATE CASCADE ON DELETE CASCADE to its definition. I'm assuming you want changes to GlassesID to also propogate. ;-) If not, then you don't need the "ON UPDATE CASCADE".
Example:
ALTER TABLE
[GlassesColor]
WITH CHECK ADD CONSTRAINT
[FK_GlassesColor_GlassesID]
FOREIGN KEY
([glassesID]) REFERENCES [Glasses] ([glassesID])
ON UPDATE CASCADE ON DELETE CASCADE

SQL Delete if the row is not affected by constraints

First note that I have seen this question:TSQL delete with an inner join
I have a large table and several foreign key relations, each of which have data of a given age. We need to remove data older than a given data on a regular basis to stop the DB from growing without bound.
I'm writing a query that will delete from each point on the star if you will by the given parameters (unfortunately these are configurable and different between the tables).
After this first deletion, I have a central table that I'm worried that I'm doing twice the work attempting to delete, as on delete the database checks the conditionals. I have a set of:
AND NOT EXISTS
(SELECT key
FROM table
WHERE table.key = centretable.key)
which TSQL is making into a right anti semi join and doing it nicely on the indexes. The problem is it creates a list of stuff to delete and then does the same checks again as it performs the delete.
I guess my question is whether there is a try delete by row, (I'm not going to do that in a cursor as I know how slow it would be), but you would think that such a keyword would exist, I haven't had any luck finding it though.
In terms of a single command that only checks the relationships once (rather than twice in your example - once for the NOT EXISTS, once for the DELETE), then I expect the answer is a big fat no, sorry.
(off the wall idea):
If this is a major problem, you could try some kind of reference-counting implementation, using triggers to update the counter - but in reality I expect this will be a lot more overhead to maintain than simply checking the keys like you are already.
You could also investigate NOCHECK during the delete (since you are checking it yourself); but you can only do this at the table level (so probably OK for admin scripts, but not for production code) - i.e.:
-- disable
alter table ChildTableName nocheck constraint ForeignKeyName
-- enable
alter table ChildTableName check constraint ForeignKeyName
A quick test shows that with it enabled it does an extra Clustered Index Scan on the foreign key; with it disabled, this is omitted.
Here's a full example; you can look at the query plan of the two DELETE operations... (ideally in isolation from the rest of the code):
create table parent (id int primary key)
create table child (id int primary key, pid int)
alter table child add constraint fk_parent foreign key (pid)
references parent (id)
insert parent values (1)
insert parent values (2)
insert child values (1,1)
insert child values (2,1)
-- ******************* THIS ONE CHECKS THE FOREIGN KEY
delete from parent
where not exists (select 1 from child where pid = parent.id)
-- reset
delete from child
delete from parent
insert parent values (1)
insert parent values (2)
insert child values (1,1)
insert child values (2,1)
-- re-run with check disabled
alter table child nocheck constraint fk_parent
-- ******************* THIS ONE DOESN'T CHECK THE FOREIGN KEY
delete from parent
where not exists (select 1 from child where pid = parent.id)
-- re-enable
alter table child check constraint fk_parent
Again - I stress this should only be run from things like admin scripts.
You could create an Indexed view of your select sentence:
SELECT key FROM table WHERE table.key = centretable.key
The indexed view is a physical copy of the data it would therefore be very fast to check.
You do have the overhead of updating the view, so you would need to test this against your usage pattern.
If you're reusing the same list of stuff to delete then you could consider inserting the keys to delete into a temp table and then using this in the second query.
SELECT Key, ...
INTO #ToDelete
FROM Table T
WHERE ...
Then something like this
...
LEFT OUTER JOIN #ToDelete D
ON T.Key=D.Key
WHERE D.Key IS NULL
DROP #ToDelete
If you specified the foreign key as a constraint when creating the table in the database you can tell the database what to do in case of a delete, by setting the delete rule. This rule specifies what happens if a user tries to delete a row with data that is involved in a foreign key relationship. The "No action" setting tells the user that the deletion is not allowed and the DELETE is rolled back. Implementing it like that would keep you from checking it yourself before deleting it, and thus could be seen as some kind of try.
Well, at least it works like that in MS SQL. http://msdn.microsoft.com/en-us/library/ms177288.aspx
I did find one article that discusses using an outer join in a delete:
http://www.bennadel.com/blog/939-Using-A-SQL-JOIN-In-A-SQL-DELETE-Statement-Thanks-Pinal-Dave-.htm
I hope this works for you!
The short answer to your question is no, there is no standard RDBMS keyword for deleting a master record when all foreign key references to it go away (and certainly none that would account for foreign keys in multiple tables).
Your most efficient option is a second query that is run on an as-needed basis to delete from "centre" based on a series of NOT EXISTS() clauses for each of the tables with foreign keys.
This is based on two statements I believe are both true for your situation:
You will delete more "related" records than "centre" (parent) records. Thus, any operation that attempts to adjust "centre" every time you delete from one of the other tables will result in an instantaneous update to "centre", but will require much wasted querying to delete a "centre" record only occasionally.
Given that there are multiple points on the star from "centre," any "wasted effort" checking for a foreign key in one of them is minimal compared to the whole. For instance, if there are four foreign keys to check before deleting from "centre", you can only save, at best, 25% of the time.

How to delete FK one to many?

I have a table (aspnet_Membership) specifically that has spam records in it that I need to delete. There are some foreign keys and I'm trying to write a simple SQL statement to remove the FK's so I can delete the primary record.
So theres a table called 'aspnet_UsersInRoles' that has UserID and RoleID, and in my 'aspnet_Membership' table is the UserID. I can't delete the User without orphaning a record(wouldn't let me do that anyways due to contraints).
How can I essentially run the following:
'delete from 'aspnet_UsersInRoles' where UserID in 'aspnet_Membership' and 'aspnet_Membership.CreateDate >= '03/15/2009'?
Thank you for any suggestions.
Well, you probably could have set up the foreign keys to do a cascading delete, so that you didn't need to worry about it, but your try at the query was pretty close to one that would work, just use a subquery:
DELETE FROM aspnet_UsersInRoles WHERE UserID IN (SELECT UserID FROM aspnet_Membership WHERE aspnet_Membership.CreateDate >= '03/15/2009')
You can alter the constraints to do a cascade all upon deletion:
link
Otherwise you can look in the sys.foreign_keys table to get all the foreign keys using the primary key and auto generate sql to do deletes there first.