SQL Update with inner join - mysql

I have a table of records, which has a self-relationship.
Additionally - to make searching easier - I have a flag which determines that a record has been referenced and hence that row is now "obsolete" and is only there for audit purposes:
CREATE TABLE Records
(
RecordID INT(5) NOT NULL,
Replaces INT(5) NULL,
Obsolete INT(1) NOT NULL
)
RecordID is the PK, Replaces links to a previous RecordID which has now been replaced, and Obsolete is redundant information which just says that another record has replaced this one. It just makes searching a lot easier. The table is very large. These are just 3 of the columns.
The only problem is: there was a typo in one of the queries in the system so for a small set of rows, the Obsolete value was not set to 1 (true).
This query will show all the records with Obsolete equal to 0 which should be equal to 1:
SELECT *
FROM Records AS rec1
LEFT JOIN Records AS rec2
ON rec1.Replaces = rec2.RecordID
WHERE rec2.RecordID IS NOT NULL
AND rec2.Obsolete = 0;
Now I need to run an UPDATE to change all those req2.Obsolete from 0 to 1, but I'm not sure how to write a query with an INNER JOIN.

You don't need an inner join. Since your query already returns the records that need to be updated, just do this:
Update Records
set Obsolete=1 where
RecordID in (
SELECT rec2.RecordID
FROM Records AS rec1
LEFT JOIN Records AS rec2
ON rec1.Replaces = rec2.RecordID
WHERE rec2.RecordID IS NOT NULL
AND rec2.Obsolete = 0
)

UPDATE Records
SET obsolete = 1
WHERE recordID in (
SELECT rec1.recordid
FROM Records AS rec1
LEFT JOIN Records AS rec2
ON rec1.Replaces = rec2.RecordID
WHERE rec2.RecordID IS NOT NULL
AND rec2.Obsolete = 0
)

I would suggest doing this in two steps using a temporary table:
-- Create temporary table for holding RecordIDs to be marked as obsolete
CREATE TEMPORARY TABLE `mark_obsolete` (`RecordID` INT NOT NULL);
-- Insert RecordIDs to mark as obsolete into temp table
INSERT INTO `mark_obsolete` (`RecordID`)
SELECT `rec2`.`RecordID`
FROM
`Records` AS `rec1`
INNER JOIN `Records` AS `rec2`
ON `rec1`.`Replaces` = `rec2`.`RecordID`
WHERE `rec2`.`Obsolete` = 0;
-- Update records using inner join to temp table
UPDATE
`Records` AS `r`
INNER JOIN `mark_obsolete` AS `o`
ON `r`.`RecordID` = `o`.`RecordID`
SET `r`.`Obsolete` = 1;
DROP TEMPORARY TABLE `mark_obsolete`;
Note that using a LEFT JOIN with WHERE rec2.RecordID IS NOT NULL is the same as an INNER JOIN.
The reason for using a temporary table is to avoid locking issues when updating the same table used in the sub-query. And it might also give you better performance than using the IN clause.

Related

Multiple conditions on a temporary table - MySQL

I have a temporary table called 'tempaction'. I wanted to select rows where 'ActionID' matches that of another table. I got the safe update mode error, I think as ActionID is part of a compound primary key. However, when I try
UPDATE action
SET Status = 'Sent'
WHERE ActionID IN( select ActionID from tempaction)
AND DeviceID IN( select DeviceID from tempaction);
I get temporary table cannot be reopened error.
Checking both parts of primary key has worked for the safe update error in the past. I also understand that I cannot reference a temporary table twice in the same statement.
How can I select rows with matching ActionID's or matching ActionID's AND DeviceID's from this temporary table?
Tempory Table
CREATE TEMPORARY TABLE tempaction (ActionID BIGINT)
SELECT *
FROM action
WHERE DeviceID = '1234'
AND Status = 'Pending'
You can try Update using Join with sub-query.
UPDATE action a
JOIN
tempaction t ON a.ActionID = t.ActionID
SET
a.Status = 'Sent';

Filling in table values from neighbour rows

I have a MySQL table that has a integer timestamp field from when the row was inserted but a bug in the inserting software left some of them as 0.
Only roughly 80% of the rows in the table has a valid timestamp.
It is not essential data so "faking" a timestamp is good enough to fix the table.
Since all rows are inserted in sequence it would make sense to set the timestamp of the row to the last row with a value of != 0
What kind of query can "fill the gaps" for 100.000 rows without taking forever?
I am assuming you have an ID that has AUTO_INCREMENT on it. In that case you could use the following type of code.
First create a temporary table, because you cannot insert into a table, with data querying that same table
Insert data into the temporary table
Update data in the first table
delete temporary table
I am not sure about you structure, but try something like the following
CREATE TABLE `temp_table` (id int NOT NULL PRIMARY_KEY, `timestamp` int NOT NULL);
INSERT INTO `temp_table` (`id`, `timestamp`)
SELECT a.id, MAX(b.`timestamp`)
FROM `your_table` a
LEFT JOIN `your_table` b
ON b.`id` < a.`id`
WHERE a.`timestamp` = 0 AND b.`timestamp` != 0
GROUP BY a.`id`;
UPDATE `your_table` a
INNER JOIN `temp_table` b
ON b.`id` = a.`id`
SET a.`timestamp` = b.timestamp;
DROP TABLE `temp_table` ;
Also don't know how fast it will be, I assume it will run in decent time

Update Mysql Query Optimisation

I must create a mysql query with a large number of queries (about 150,000)
For the moment the query is:
UPDATE table SET activated=NULL
WHERE (
id=XXXX
OR id=YYYY
OR id=ZZZZ
OR id=...
...
)
AND activated IS NOT NULL
Do you know a best way for to do that please?
If you're talking about thousands of items, an IN clause probably isn't going to work. In that case you would want to insert the items into a temporary table, then join with it for the update, like so:
UPDATE table tb
JOIN temptable ids ON ids.id = tb.id
SET tb.activated = NULL
UPDATE table
SET activated = NULL
WHERE id in ('XXXX', 'YYYY', 'zzzz')
AND activated IS NOT NULL

How do I delete the first matching record in Table B for each record in Table A?

Table A contains multiple records that should be deleted from Table B. However, there can be multiple records in Table B that match a single record in Table A. I only want to delete the first matching record in Table B for each record in Table A. If there are 50 records in Table A then a maximum of 50 records should be deleted from Table B. I'm using the SQL statement below which is deleting more records from Table B than are listed in Table A due to multiple matches. I can not further restrict the matching criteria in my statement due to limitations in the data.
DELETE FROM [#DraftInvoiceRecords] FROM [#DraftInvoiceRecords]
INNER JOIN [#ReversedRecords]
ON [#DraftInvoiceRecords].employee = [#ReversedRecords].employee
and [#DraftInvoiceRecords].amount = [#ReversedRecords].amount
and [#DraftInvoiceRecords].units = [#ReversedRecords].units
You need some way to distinguish the rows to delete from the rows to keep. I've used someOtherColumn in the below to achieve this:
create table #DraftInvoiceRecords (
employee int not null,
amount int not null,
units int not null,
someOtherColumn int not null
)
create table #ReversedRecords (
employee int not null,
amount int not null,
units int not null
)
insert into #DraftInvoiceRecords (employee,amount,units,someOtherColumn)
select 1,1,1,1 union all
select 1,1,1,2
insert into #ReversedRecords (employee,amount,units)
select 1,1,1
delete from dir
from
#DraftInvoiceRecords dir
inner join
#ReversedRecords rr
on
dir.employee = rr.employee and
dir.amount = rr.amount and
dir.units = rr.units
left join
#DraftInvoiceRecords dir_anti
on
dir.employee = dir_anti.employee and
dir.amount = dir_anti.amount and
dir.units = dir_anti.units and
dir.someOtherColumn > dir_anti.someOtherColumn --It's this condition here that allows us to distinguish the rows
where
dir_anti.employee is null
select * from #DraftInvoiceRecords
drop table #DraftInvoiceRecords
drop table #ReversedRecords

MySql stored procedures, delete record logically or phisically depending on existing table references

I have to write a Stored Procedure to delete record from a table.
I have a memory table "tableids" where I store all the ids to delete from another table, say "addresses".
CREATE TABLE `tempids` (
`id` INT(11) NULL DEFAULT NULL
)
COLLATE='latin1_swedish_ci'
ENGINE=MEMORY
ROW_FORMAT=DEFAULT
I could do this:
DELETE FROM addresses INNER JOIN tempids ON addresses.id = tempids.id;
BUT I want to physically delete the records in the addresses table if they have no references in other known tables in my model; otherwise I want to delete the records logically. I'd like to do this in a single shot, that is without writing a loop in my SP.
In pseudo-sql code:
DELETE
FROM addresses
WHERE
id NOT IN (SELECT address_id FROM othertable1 WHERE address_id=(SELECT id FROM tempids))
AND id NOT IN (SELECT address_id FROM othertable2 WHERE address_id=(SELECT id FROM tempids))
...more possible references in other tables
IF "no records deleted"
DELETE FROM addresses INNER JOIN tempids ON addresses.id = tempids.id;
ELSE
UPDATE addresses SET deleted=TRUE INNER JOIN tempids ON addresses.id = tempids.id;
How do I accomplish this?
Thanks.
You can check ROW_COUNT() function value after deleting.
http://dev.mysql.com/doc/refman/5.5/en/information-functions.html#function_row-count
So "no records deleted" condition is replaced with ROW_COUNT() == 0