SQL delete orphan - sql

Assuming that all foreign keys have the appropriate constraint, is there a simple SQL statement to delete rows not referenced anywhere in the DB?
Something as simple as delete from the_table that simply skip any rows with child record?
I'm trying to avoid manually looping through the table or adding something like where the_SK not in (a,b,c,d).

You might be able to use the extended DELETE statement in 10g that includes error logging.
First use DBMS_ERRLOG to create a logging table (which is just a copy of the original table with some additional prefixing columns: ORA_ERR_MESG$, ..., ORA_ERR_TAG$)
execute dbms_errlog.create_error_log('parent', 'parent_errlog');
Now, you can use the LOG ERRORS clause of the delete statement to capture all rows that have existing integrity constraints:
delete from parent
log errors into parent_errlog ('holding-breath')
reject limit unlimited;
In this case the "holding-breath" comment will go into the ORA_ERR_TAG$ column.
You can read the full documentation here.
If the parent table is huge and you're only looking to delete a few stray rows, you'll end up with a parent_errlog table that is essentially a duplicate of your parent table. If this isn't ok, you'll have to do it the long way:
Directly reference the child tables (following Tony's solution), or,
Loop through the table in PL/SQL and catch any exceptions (following Confusion's and Bob's solutions).

The easiest way may be to write an application or stored procedure that attempts to delete the rows in the table one-by-one and simply ignores the failures due to foreign key constraints. Afterwards, all rows not under a foreign key constraint should be removed. Depending on the required/possible performance, this may be an option.

No. Obviously you can do this (but I realise you would rather not):
delete parent
where not exists (select null from child1 where child1.parent_id = parent.parent_id)
and not exists (select null from child2 where child2.parent_id = parent.parent_id)
...
and not exists (select null from childn where childn.parent_id = parent.parent_id);

One way to do this is to write something like the following:
eForeign_key_violation EXCEPTION;
PRAGMA EXCEPTION_INIT(eForeign_key_violation, -2292);
FOR aRow IN (SELECT primary_key_field FROM A_TABLE) LOOP
BEGIN
DELETE FROM A_TABLE A
WHERE A.PRIMARY_KEY_FIELD = aRow.PRIMARY_KEY_FIELD;
EXCEPTION
WHEN eForeign_key_violation THEN
NULL; -- ignore the error
END;
END LOOP;
If a child row exists the DELETE will fail and no rows will be deleted, and you can proceed to your next key.
Note that if your table is large this may take quite a while.

Related

Copy a table data from one database to another database SQL

I have had a look at similar problems, however none of the answers helped in my case.
Just a little bit of background. I have Two databases, both have the same table with the same fields and structure. Data already exists in both tables. I want to overwrite and add to the data in db1.table from db2.table the primary ID is causing a problem with the update.
When I use the query:
USE db1;
INSERT INTO db2.table(field_id,field1,field2)
SELECT table.field_id,table.field1,table.field2
FROM table;
It works to a blank table, because none of the primary keys exist. As soon as the primary key exists it fails.
Would it be easier for me to overwrite the primary keys? or find the primary key and update the fields related to the field_id? Im really not sure how to go ahead from here. The data needs to be migrated every 5min, so possibly a stored procedure is required?
first you should try to add new records then update all records.you can create a procedure like below code
PROCEDURE sync_Data(a IN NUMBER ) IS
BEGIN
insert into db2.table
select *
from db1.table t
where t.field_id not in (select tt.field_id from db2.table tt);
begin
for t in (select * from db1.table) loop
update db2.table aa
set aa.field1 = t.field1,
aa.field2 = t.field2
where aa.field_id = t.field_id;
end loop;
end;
END sync_Data
Set IsIdentity to No in Identity Specification on the table in which you want to move data, and after executing your script, set it to Yes again
I ended up just removing the data in the new database and sending it again.
DELETE FROM db2.table WHERE db2.table.field_id != 0;
USE db1;
INSERT INTO db2.table(field_id,field1,field2)
SELECT table.field_id,table.field1,table.field2
FROM table;
Its not very efficient, but gets the job done. I couldnt figure out the syntax to correctly do an UPDATE or to change the IsIdentity field within MariaDB, so im not sure if they would work or not.
The overhead of deleting and replacing non-trivial amounts of data for an entire table will be prohibitive. That said I'd prefer to update in place (merge) over delete /replace.
USE db1;
INSERT INTO db2.table(field_id,field1,field2)
SELECT t.field_id,t.field1,t.field2
FROM table t
ON DUPLICATE KEY UPDATE field1 = t.field1, field2 = t.field2
This can be used inside a procedure and called every 5 minutes (not recommended) or you could build a trigger that fires on INSERT and UPDATE to keep the tables in sync.
INSERT INTO database1.tabledata SELECT * FROM database2.tabledata;
But you have to keep length of varchar length larger or equal to database2 and keep the same column name

SQL Server trigger thinks there's a duplicate in the table when there isn't

I'm a new SQL developer. After recommendations I have altered my trigger (for this task I need to use a trigger so can't avoid it), but I have re-altered my trigger. I want it to prevent a duplication in the Rentals table of the BikeID foreign key contained within it.
This is my code at the moment:
CREATE TRIGGER BikeNotAvailable
ON dbo.SA_Rental
AFTER INSERT
AS
IF EXISTS (SELECT *
FROM SA_Rental
INNER JOIN inserted i ON i.BikeID = dbo.SA_Rental.BikeID)
BEGIN
ROLLBACK
RAISERROR ('This bike is already being hired', 16, 1);
END
go
But when I enter the BikeID in the Rentals table, even though the BikeID is not present inside a row yet, it still raises the error - why? (I have also tested this on an empty table and it still raises the error)
Just some context on my data, the BikeID is a primary key from the 'Bike' table that is shared as a foreign key to the Rentals table, not sure if this has anything to do with the error.
Can someone please help me fix this trigger so it works.
Thanks.
Well, as it's an AFTER trigger, the trigger is run after the new record is added to the table (at least visible for your trigger).
Supposing that your table has an automatically generated ID column, you should exclude the inserted row from your check like this:
CREATE TRIGGER BikeNotAvailable ON dbo.SA_Rental
AFTER INSERT
AS
if exists ( select * from SA_Rental
inner join inserted i on i.BikeID=dbo.SA_Rental.BikeID
where SA_Rental.RentalID <> i.RentalID)
begin
rollback
RAISERROR ('This bike is already being hired', 16, 1);
end
go
A far simpler way to achieve what you are after is to create a unique index:
CREATE UNIQUE INDEX BikeRented ON SA_Rental (BikeID);
This, of course, assumes that you delete the row from your table when the bike is no longer rented (as this is the implied logic in your post). If this is not the case, then we need more detail; what specifies on your table that the rental has completed?
If we assume you have a return date, and the return date is NULL when the bike is yet to be returned, then you would use a filtered index like so:
CREATE UNIQUE INDEX BikeRented ON SA_Rental (BikeID)
WHERE ReturnedDate IS NULL;

IF EXIST(To check row exist in table) condition not working in SQL Procedure(AS400)

I have one table and two different SQL procedures(AS400) to Insert/Update records to that same table. Both the SQL procedures having IF EXISTS condition to handle the data.
IF EXIST (SELECT 1 FROM TABLE WHERE FIELD001 = 'test') THEN
Update table....
ELSE
INSERT INTO TABLE VALUES ('test')...
ENDIF;
But still am getting duplicate records in my table with mili seconds difference.
Ex.1st record is --> 2017-07-24-04.21.47.485832
2nd record is --> 2017-07-24-04.21.47.487468
These tables could be Inserted/Updated interactively as well as batch. Anyway How come this is possible for duplicate records..?. Please experts give some possibilities where/when/how duplicate records will be inserted.
And also i don't want to fix this with UNIQUE INDEX, PRIMARY KEY etc...
Sorry I didn't attach any coding with this.
Thanks,
Loganathan.
Adding codes here,,,
The table which I mentioned earlier will insert/update from various ways, but we confirmed these records were inserted interactively from a single session using below single procedure.
Original Records in table.
9243548 CUSTYPE 2017-07-10-16.53.09.825860 2017-07-10-16.53.09.825860
9243548 ROYALTY 2017-07-10-16.53.09.485832 2017-07-10-16.53.09.485832
9243548 ROYALTY 2017-07-10-16.53.09.487468 2017-07-10-16.53.09.487468
Calling program:
if v_res_spec_sts <> '' then
if (v_res_spec_sts <> v_current_res_spec_sts
or v_current_res_spec_sts IS NULL) then
call SPCASPECSV (p_resvnum, c_Royalty, v_res_spec_sts,
p_updateUser, p_updateProgram) ;
end if;
end if;
Procedure:
IF EXISTS (SELECT 1 FROM CASPECLPF WHERE RSRES# = P_RSRES#
AND RSCOND = P_RSCOND) THEN
UPDATE CASPECLPF SET
RSSSTS = COALESCE(P_RSSSTS, RSSSTS)
,RSSLCM = TODAYMONTH
,RSSLCD = TODAYDAY
,RSSLCY = TODAYYEAR
,RSSLCU = COALESCE(P_RSSLCU, RSSLCU)
,RSSLCP = COALESCE(P_RSSLCP, RSSLCP)
WHERE RSRES# = P_RSRES# AND RSCOND = P_RSCOND;
ELSE
INSERT INTO CASPECLPF
(
RSRES#
,RSCOND
,RSSSTS
,RSSLCM
,RSSLCD
,RSSLCY
,RSSLCU
,RSSLCP
)
VALUES
(
COALESCE(P_RSRES#, 0)
,COALESCE(P_RSCOND, ' ')
,COALESCE(P_RSSSTS, ' ')
,TODAYMONTH
,TODAYDAY
,TODAYYEAR
,COALESCE(P_RSSLCU, ' ')
,COALESCE(P_RSSLCP, ' ')
);
END IF;
Make sure they are not in different sessions i.e. inserting in one session and not doing the commit, then second session would obviously not find the record inserted in 1st session.
Also if that is not the case, please provide the code.
Because it's not a duplicate.
If you had a primary or unique key defined, the system would have prevented the second process from writing a record at 2017-07-24-04.21.47.487468.
As it is when the second process checked for a record at 2017-07-24-04.21.47.485500, one didn't exist. But by the time the second process inserted a record the first process had also inserted a record.
Even with a primary key, the existence check and insert are two separate operations. You'd still have to monitor for a duplicate key on the insert and handle appropriately.
The MERGE statement is usually preferred for such "upsert" (UPDATE or INSERT) operations. However, even with a atomic merge, a second process could insert a record between existence check & insert. You have to use a locking level of *RR (repeatable read) which basically locks the entire table to ensure that no process can add a record between the existence check and the insert.
With processes inserting microseconds apart, locking the entire table is going to hurt.
You really need to define a primary key, or at least a unique one.

Do databases always lock non-existent rows after a query or update?

Given:
customer[id BIGINT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(30), count INT]
I'd like to execute the following atomically: Update the customer if he already exists; otherwise, insert a new customer.
In theory this sounds like a perfect fit for SQL-MERGE but the database I am using doesn't support MERGE with AUTO_INCREMENT columns.
https://stackoverflow.com/a/1727788/14731 seems to indicate that if you execute a query or update statement against a non-existent row, the database will lock the index thereby preventing concurrent inserts.
Is this behavior guaranteed by the SQL standard? Are there any databases that do not behave this way?
UPDATE: Sorry, I should have mentioned this earlier: the solution must use READ_COMMITTED transaction isolation unless that is impossible in which case I will accept the use of SERIALIZABLE.
This question is asked about once a week on SO, and the answers are almost invariably wrong.
Here's the right one.
insert customer (email, count)
select 'foo#example.com', 0
where not exists (
select 1 from customer
where email = 'foo#example.com'
)
update customer set count = count + 1
where email = 'foo#example.com'
If you like, you can insert a count of 1 and skip the update if the inserted rowcount -- however expressed in your DBMS -- returns 1.
The above syntax is absolutely standard and makes no assumption about locking mechanisms or isolation levels. If it doesn't work, your DBMS is broken.
Many people are under the mistaken impression that the select executes "first" and thus introduces a race condition. No: that select is part of the insert. The insert is atomic. There is no race.
Use Russell Fox's code but use SERIALIZABLE isolation. This will take a range lock so that the non-existing row is logically locked (together with all other non-existing rows in the surrounding key range).
So it looks like this:
BEGIN TRAN
IF EXISTS (SELECT 1 FROM foo WITH (UPDLOCK, HOLDLOCK) WHERE [email] = 'thisemail')
BEGIN
UPDATE foo...
END
ELSE
BEGIN
INSERT INTO foo...
END
COMMIT
Most code taken from his answer, but fixed to provided mutual exclusion semantics.
Answering my own question since there seems to be a lot of confusion around the topic. It seems that:
-- BAD! DO NOT DO THIS! --
insert customer (email, count)
select 'foo#example.com', 0
where not exists (
select 1 from customer
where email = 'foo#example.com'
)
is open to race-conditions (see Only inserting a row if it's not already there). From what I've been able to gather, the only portable solution to this problem:
Pick a key to merge against. This could be the primary key, or another unique key, but it must have a unique constraint.
Try to insert a new row. You must catch the error that will occur if the row already exists.
The hard part is over. At this point, the row is guaranteed to exist and you are protected from race-conditions by the fact that you are holding a write-lock on it (due to the insert from the previous step).
Go ahead and update if needed or select its primary key.
IF EXISTS (SELECT 1 FROM foo WHERE [email] = 'thisemail')
BEGIN
UPDATE foo...
END
ELSE
BEGIN
INSERT INTO foo...
END

INSTEAD OF UPDATE TRIGGER for a table with foreign key

I get this error:
Cannot create INSTEAD OF DELETE or INSTEAD OF UPDATE TRIGGER 'trig_Income_Updater' on table 'MYBUDGET.tbl_Income'. This is because the table has a FOREIGN KEY with cascading DELETE or UPDATE.
I can use 'FOR UPDATE'. but how to make it to ignore the original update ?
To ignore the origional update you will need to utilize a RAISERROR and then ROLLBACK.
Here is an example under the "Using a DML AFTER trigger to enforce a business rule between the PurchaseOrderHeader and Vendor tables" section
Just update the fields you want and then "undo" the original update using the "inserted" and "deleted" temporary tables that are provided to the trigger.
For example (untested):
--Do the stuff you want
UPDATE table SET fields = values WHERE some condition
--Undo the original update (minus anything you WANT changed above)
UPDATE table SET unchangingfield = deleted.unchangingfield WHERE ID = deleted.ID
The "inserted" table will contain the new values, and the "deleted" table contains the values that are being changed. You can join, query and otherwise treat them as though they were actual tables.