More efficient way to write multiple UPDATE queries - mysql

Is there a better / more efficient / shorter way to write this SQL Query:
UPDATE mTable SET score = 0.2537 WHERE user = 'Xthane' AND groupId = 37;
UPDATE mTable SET score = 0.2349 WHERE user = 'Mike' AND groupId = 37;
UPDATE mTable SET score = 0.2761 WHERE user = 'Jack' AND groupId = 37;
UPDATE mTable SET score = 0.2655 WHERE user = 'Isotope' AND groupId = 37;
UPDATE mTable SET score = 0.3235 WHERE user = 'Caesar' AND groupId = 37;

UPDATE mTable
SET score =
case user
when 'Xthane' then 0.2537
when 'Mike' then 0.2349
when 'Jack' then 0.2761
when 'Isotope' then 0.2655
when 'Caesar' then 0.3235
else score
end
where groupId = 37

You can use a CASE statement to perform this type of UPDATE.
UPDATE mTable
SET score
= CASE user
WHEN 'Xthane' THEN 0.2537
WHEN 'Mike' THEN 0.2349
WHEN 'Jack' THEN 0.2761
WHEN 'Isotope' THEN 0.2655
WHEN 'Caesar' THEN 0.3235
ELSE score
END
WHERE groupId = 37

You could create a temporary table, insert score, user and groupid for all the records you want to update then do something like this:
UPDATE
FROM mTable m
INNER JOIN tmpTable t
ON m.groupId = t.groupId
AND m.user = t.user
SET m.score = t.score;

Your original statements look short enough, and are easy enough to understand, and you can determine whether there were any rows affected on each of those separate UPDATE statements.
For a large number of statements, however, there's a considerable amount of overhead making "roundtrips" to the database to execute each individual statement. You can get much faster execution (shorter elapsed time) for a large set of updates by "batching" the updates together in a single statement execution.
So, it depends on what you are trying to achieve.
Better? Depends on how you define that. (Should the statements be more understandable, easier to debug, less resource intensive?
More efficient? In terms of reduced elapsed time, yes, there are other ways to accomplish these same updates, but the statements are not as easy to understand as yours.
Shorter? In terms of SQL statements with fewer characters, yes, there are ways to achieve that. (Some examples are shown in other answers, but note that the effects of the statements in some of those answers is significantly DIFFERENT than your statements.)
The actual performance of those alternatives is really going to depend on the number of rows, and available indexes. (e.g. if you have hundreds of thousands of rows with groupId = 37, but are only updating 5 of those rows).

Related

Understanding how to design MySQL indexes for good performance

Through trial and error, I've arrived at a good index for this query, but I'd really like to understand why this and only this index helps, and how to avoid having to repeat the t&e next time.
The InnoDB table structure for a log table is:
This is my query—it's looking for all users who have one kind of action in the log, but not another kind of action. It's also restricting to certain values of org and a certain date range.
SELECT DISTINCT USER AS 'Dormant Users'
FROM db.log
WHERE `action` = #a1
AND `org` = #orgid
AND `logdate` >= #startdate
AND USER NOT IN (SELECT DISTINCT USER
FROM db.log
WHERE `action` = #a2
AND `org` = #orgid
AND `logdate` >= #startdate)
;
With no indexes, this takes about 21 seconds, and EXPLAIN shows this:
So, I thought having an index on org, logdate, and action might help. And it does—if I create an index on those columns in that precise order, the query time is reduced to about 0.3s, and the EXPLAIN output is now:
But, if I change the order of the columns within the index, or even just add another, unrelated index (say on the user column), the query takes about 2 seconds.
So, how can I understand and even design the index to perform well based on that query, and avoid the rather degenerate case of adding another index and harming performance? Or is it just a case of test and see what works?
My answer is not the answer because it is not about how to set the index but how to write your query to make it more efficient.
Avoid using NOT IN if the subquery is not a small table :
SELECT DISTINCT l1.USER AS 'Dormant Users'
FROM db.log l1
WHERE `action` = #a1
AND `org` = #orgid
AND `logdate` >= #startdate
AND NOT EXISTS (SELECT 1
FROM db.log l2
WHERE l1.`user` = l2.`user`
AND l1.`org` = l2.`org`
AND l2.`action` = #a2
AND l2.`logdate` >= #startdate)
;
EDIT : I removed the explanation link as it is not what I thought. I am only a skilled developer and not a DBA. Thus, I have optimized a lot of queries and I always have had better results with NOT EXISTS than NOT IN when volumes get hihg. But I am not able to argue about the internal reason (and I guess it depends on the RDBMS)
...or with an outer join...
SELECT DISTINCT user
FROM log x
LEFT
JOIN log y
ON y.user = x.user
AND y.org = x.org
AND y.action = #a2
AND y.logdate > = #startdate
WHERE x.action` = #a1
AND x.org = #orgid
AND x.logdate >= #startdate
AND y.user IS NULL;
I'm not too hot on indexing, but I'd start with (org, action, logdate)

MySQL- Select and Update at the same time

I have this query
SELECT * FROM outbox where Status=0 ;
then I need to update the selected records so Status should be equal 1
i.e (UPDATE outbox(selected records from SELECT query) SET Status =1 )
any help ?
This is a much harder problem than it sounds. Yes, in the simplistic case where you are only thinking of one user and a few records, it seems easy. But, databases are designed to be ACID-compliant, with multiple users and multiple concurrent transactions that can all be affecting the data at the same time. And there is no single statement in MySQL that does what you want (other databases support an OUTPUT clause, RETURNING or something similar).
One structure that will work in MySQL is to place the items in a temporary table, then do the update, then return them. The following shows the semantics using transactions:
start transaction;
create temporary table TempOutboxStatus0 as
select *
from outbox
where status = 0;
update outbox o
set status = 1
where status = 0;
select *
from TempOutboxStatus0;
commit;
For the update, I actually prefer:
where exists (select 1 from TempOutboxStatus0 t where t.outboxid = o.outboxid);
because its intention is clearer -- and the code is safer in case the conditions subtly change.
Note: you may want to use explicit table locks. Such considerations depend on the storage engine you are using.
BEGIN
Start transaction;
SELECT *
FROM
outbox
where
Status = 0 and
Is_Expired = 0 and
Service_ID=p_service_id
order by
Next_Try_Date asc FOR Update;
update outbox
set
Status=1
where
Status = 0 and
Is_Expired = 0 and
Service_ID=p_service_id;
commit;
END
is this possible .. it seems it works with me
You can do something like that, the outbox is your table:
update outbox set Status = 1 where Status = 0
you can do it like below
$sql=mysql_query("SELECT * FROM outbox where `Status`=0");
while($result=mysql_fetch_array($sql))
{
$update="UPDATE `outbox` SET `Status` =1 where
'your column name'='your previous fetched value');
}

MySQL "CASE WHEN" control VS multiple queries

In between the 2 query cases below, which one is faster:
update t set v = case when id = 1000000 then 100 when id = 10000000 then 500 else v end
Or
update t set v = 100 where id = 1000000;
update t set v = 500 where id = 10000000;
the table t has an unique index on id and the table can be pretty big (millions of entry).
My guess is that although the second case make multiple queries it is still faster because it can use the index to find the entries while in the first case it is doing a full scan of the table (but it is just a guess, I have actually no clue on how MySQL deal with CASE control flow).
Thank you in advance for any answers !
The second version you have would be better and cleaner, however, a cleaner single update is also possible to take advantage of the index on the "id" columns...
update t
set v = if( id = 1000000, 100, 500 )
where id in ( 1000000, 10000000 )

MySql update two tables at once

I have two tables that need the exact same values for denormalization purposes.
Here's the query.
first table
UPDATE Table_One
SET win = win+1, streak = streak+1, score = score+200
WHERE userid = 1 AND lid = 1 LIMIT 1
second table
UPDATE Table_Two
SET win = win+1, streak = streak+1, score = score+200
WHERE userid = 1 LIMIT 1
As you can see the only difference between both tables is their name and table two doesn't have the field lid
Anyway to combine both updates to just one?
It should be possible with a multi-table update, as described in the documentation.
http://dev.mysql.com/doc/refman/5.5/en/update.html
UPDATE Table_One a INNER JOIN Table_Two b ON (a.userid = b.userid)
SET
a.win = a.win+1, a.streak = a.streak+1, a.score = a.score+200,
b.win = b.win+1, b.streak = b.streak+1, b.score = b.score+200
WHERE a.userid = 1 AND a.lid = 1 AND b.userid = 1
Note: Multi-table doesn't support LIMIT, so this could cause more grief depending on the details.
Stored procedures or transactions may be a nicer solution.
If there is a one to one or one to many relation from Table_One to Table_Two, this would work:
UPDATE Table_One T1, Table_Two T2
SET T1.win = T1.win+1, T1.streak = T1.streak+1, T1.score = T1.score+200,
T2.win = T2.win+1, T2.streak = T2.streak+1, T2.score = T2.score+200
WHERE T1.userid = 1 AND T1.lid = 1 AND T2.userid = T1.userid;
If you can join the tables, then you could create a view of two tables, then update via that view. In your example it looks like userid might be a suitable key.
In creating the view, you'd need to stick to the following guidelines.
They’re two separate queries and so must be treated as such. Sorry to say it, but if you’re updating two tables with identical data, there’s probably a better way to design your database. Remember to keep your programming DRY.
Edit: Should retract that; you can use it for multiple tables, but you can’t use ORDER BY or LIMIT.

Update multiple rows with multiple values and multiple conditions mysql

I am facing a complex situation of SQL queries. The task is to update multiple rows, with multiple values and multiple conditions. Following is the data which I want to update;
Field to update: 'sales', condition fields: 'campid' and 'date':
if campid = 259 and date = 22/6/2011 then set sales = $200
else if campid = 259 and date = 21/6/2011 then set sales = $210
else if campid = 260 and date = 22/6/2011 then set sales = $140
else if campid = 260 and date = 21/6/2011 then set sales = $150
I want to update all these in one query.
Try this:
UPDATE your_table SET sales =
CASE
WHEN campid = 259 AND date = 22/6/2011 THEN 200
WHEN campid = 259 AND date = 21/6/2011 THEN 210
WHEN campid = 259 AND date = 22/6/2011 THEN 140
WHEN campid = 259 AND date = 21/6/2011 THEN 150
ELSE sales
END
Naturally I don't know if date field is really DATE or DATETIME, so I left query showing what you can do, but maybe you have to fix dates comparison according to data type.
If date field is DATE (as it should) you can write AND date = '2011-06-22' and so on.
Note ELSE condition: it's necessary to avoid records not falling inside other cases will be set to NULL.
Rather than write a sql query that is far too complicated and time involved, I believe you would be better off spending your time writing a data access object to handle these rather simple manipulations on a per record basis. This makes later maintenance of the code, along with development of new code using your data access objects far easier than a one time use, intricate sql query.
You certainly should not do these in a single query. Instead, if what you aim for is to update them atomically, all at the same time, you should issue several UPDATE statements in a single transaction.
You do not say which MySQL version you use, and not which storage engine. Assuming InnoDB - which is the standard in recent versions of MySQL and should generally be used for transactional systems - and also assuming you are doing this from the command line client, you would
mysql> set autocommit=0;
mysql> UPDATE ....;
mysql> UPDATE ....;
mysql> ...
mysql> commit;
You can then reenable autocommit if you like by repeating the first line, but with a value of 1:
mysql> set autocommit=1;