I know this works, but is it good practice?
update test set field1=field1+field2, field2=0;
I don't want field2 to be set to 0 until the value has been added to the field1 total. Will this always work as planned?
It should work if MySQL followed the standard. But it doesn't, (follow the standard) in this case. Note. In standard ISO?ANSI SQL, these statements are equivalent and produce the same changes in the table. (Anyone can try them in SQL-Server or Oracle. The 3rd variation works in Postgres only I think):
UPDATE test
SET field1 = field1 + field2, field2 = 0 ;
UPDATE test
SET field2 = 0, field1 = field1 + field2 ;
UPDATE test
SET (field1, field2) = (field1 + field2, 0) ;
So, in MySQL your statement will work like you expect most of the times, as the MySQL documentation states:
Single-table UPDATE assignments are generally evaluated from left to right. For multiple-table updates, there is no guarantee that assignments are carried out in any particular order.
Note the "generally" though. To be 100% sure, you can do the update in 2 statements (inside a transaction):
UPDATE test
SET field1 = field1 + field2 ;
UPDATE test
SET field2 = 0 ;
or using a temporary table.
found it in the manual (http://dev.mysql.com/doc/refman/5.0/en/update.html):
UPDATE t1 SET col1 = col1 + 1, col2 = col1;
Single-table UPDATE assignments are generally evaluated from left to
right. For multiple-table updates, there is no guarantee that
assignments are carried out in any particular order.
so it will always work
Related
There is a table with three column: id, field1, field2.
And there is a row: id=1, field1=1, field2=1.
Run a update SQL: UPDATE my_table SET field1=field2+1, field2=field1+1 WHERE id=1;
I expected the result is: id=1, field1=2, field2=2. But in fact I got: id=1, field1=2, field2=3. Because when calculating field2=field1+1, the value of field1 has changed!
I figure out a SQL to solve this problem:
UPDATE my_table dest, (SELECT * FROM my_table) src
SET dest.field1=src.field2+1, dest.field2=src.field1+1
WHERE dest.id=1;
However I want to insert a record, and if the row was existed then do a update just like above.
INSERT INTO my_table (id, field1, field2) VALUES(1, 1, 1)
ON DUPLICATE KEY UPDATE
field1=field2+1, field2=field1+1;
This SQL has problem same as the first SQL. So how can I do this update using the value before change with ON DUPLICATE KEY UPDATE clause?
Thanks for any help!
Couldn't think of anything else but a temp variable. However, couldn't think of a way to make SQL syntax work, other than this:
set #temp = 0;
update test.test set
f1 = (#temp:=f1),
f1 = f2 + 1,
f2 = #temp + 1
where id = 1;
Hope this helps, and hope even more it helps you find a better way :)
I find a trick way to do this.
Use the IF clause to create temp variable. Field update use temp variable to calculate.
INSERT INTO my_table (id, f1, f2) VALUES(1, 1, 1)
ON DUPLICATE KEY UPDATE
id=IF((#t1:=f1 & #t2:=f2), 1, 1), f1=#t2+1, f2=#t1+1;
There is some point to notice:
The performance is a bit slow. Especially copy TEXT value to temp variable.
If field id need to use IF clause, the expr will be more complicated like:
((#t1:=f1 & #t2:=f2) || TRUE) AND (Your Condition)
In some cases I have to copy values from one column to another and set the first to NULL. This SQL-Statement works as expected:
UPDATE lessons SET order_id_old = order_id, order_id = NULL WHERE id = 1
But I'm not sure if this is a right way to do it so. Or should I better use 2 queries for this purpose?
UPDATE lessons SET order_id_old = order_id WHERE id = 1;
UPDATE lessons SET order_id = NULL WHERE id = 1;
I would definitely go with second approach. Here's what the documentation says:
Single-table UPDATE assignments are generally evaluated from left to
right. For multiple-table updates, there is no guarantee that
assignments are carried out in any particular order.
In your case, it's fine at the moment as there is only one table. However, in future, if someone modifies this statement and adds a new table/join (assuming it'll work fine as it did with one table) it will stop working/give inconsistent results.
So, for the readability/maintainability purpose, go ahead with second approach. (Also, I would recommend wrapping both the update statements into a transaction to preserve atomicity)
From the documentation:
If you access a column from the table to be updated in an expression, UPDATE uses the current value of the column. For example, the following statement sets col1 to one more than its current value:
UPDATE t1 SET col1 = col1 + 1;
The second assignment in the following statement sets col2 to the current (updated) col1 value, not the original col1 value. The result is that col1 and col2 have the same value. This behavior differs from standard SQL.
UPDATE t1 SET col1 = col1 + 1, col2 = col1;
Single-table UPDATE assignments are generally evaluated from left to right. For multiple-table updates, there is no guarantee that assignments are carried out in any particular order.
In your case, it should be fine to use the single statement.
I'm converting Stored Procedures from SQL Server to MySQL and I've found a query that has me stumped:
IF EXISTS (SELECT * FROM dbo.T_ExampleTable WHERE Col1 = #Col1 AND Col2 = #Col2 AND Col3 = #Col3)
SET #return_value = -10
ELSE IF EXISTS (SELECT * FROM dbo.T_ExampleTable WHERE Col1 = #Col1 OR Col2 = #Col2 OR Col3 = #Col3)
SET #return_value = -20;
IF #return_value = 0
BEGIN
MERGE ExampleDB.dbo.T_ExampleTable AS T
USING (VALUES (#Col1, #Col2, #Col3)) AS S (Col1, Col2, Col3)
ON T.Col1 = S.Col1
WHEN NOT MATCHED THEN
INSERT VALUES (S.Col1, S.Col2, S.Col3);
END
For security purposes, I have altered the names of variables but the syntax is the same.
I think I have a grasp on what's happening with the MERGE here (but please correct me if I'm wrong). I assume that we're taking our 3 variables (#Col1, #Col2, and #Col3) and placing them in a result set we call "S". Then, we're checking each row in T_ExampleTable for a matching Col1 value. Finally, we're saying if it DOESN'T match in any of the rows, INSERT these values.
Now, I'm not too familiar with the MERGE syntax, so I may be off with the above assumption. Assuming I'm correct, however, isn't this the same as just performing a SELECT FROM T_ExampleTable WHERE Col1 = #Col1, then checking the ##ROWCOUNT (or FOUND_ROWS() in MySQL) and if it doesn't equal 0, performing an INSERT?
Furthermore, I am even more baffled by logic above the MERGE. If my understanding of how MERGE works is correct (and again, it may be off) then the entire statement is pointless. Because basically, if #Col1 exists within the T_ExampleTable, then #return_value is going to equal -10 or -20. Thus, it can't equal 0 and we won't hit the MERGE. If #Col1 does not exist within T_ExampleTable, then #return_value is going to equal 0, but the MERGE isn't going to return any result set since T.Col1 = S.Col1 will never be true.
This is why I believe I must be misunderstanding something about how MERGE works. Either that, or the SQL Server code I'm porting is just poorly written (which is also possible, I guess).
So ultimately, I'm looking for two answers here. One that helps clarify what the logic of a MERGE statement is and if my understanding of the above code is correct, and another that shows the possible solution for converting this to MySQL.
Thanks!
For practical purposes, I think this is equivalent to this (in both databases):
INSERT INTO T(Col1, Col2, Col3)
SELECT col1, col2, col3
FROM (SELECT #Col1 as col1, #Col2 as col2, #Col3 as col3) s
WHERE NOT EXISTS (SELECT 1
FROM ExampleDB.dbo.T_ExampleTable t
WHERE t.col1 = s.col1
);
There may be some edge cases where they are not exactly the same -- say, in terms of concurrent calls to the stored procedure or when col1 is NULL. But you already have differences in locking between the two databases anyway.
(And, although you didn't ask, I would expect Postgres to be a more natural database to switch to from SQL Server because Postgres and SQL Server have a larger overlap in functionality.)
Let's say I have these possible queries:
UPDATE table SET field1 = 2 WHERE id = 1;
UPDATE table SET field1 = 4, field2 = 8 WHERE id = 16;
table has a BEFORE UPDATE trigger that calculates a new value for field2 based on field1, but I want it to only do this if I don't specifically pass a value.
I am detecting a change to field1 with IF OLD.field1 != NEW.field1 THEN ..., but how do I detect field2 being passed or not, regardless of the value actually changing?
If MySQL does not receive a value through the UPDATE for field2 it will contain the old value in NEW.field2.
In Oracle you will get a NULL if the field2 is not passed trough the update.
If NEW and OLD values are the same you can assume that no update is required for that field. MySQL will also return 0 rows updated if all NEW values match what is already there in the table.
MySQL has this incredibly useful yet proprietary REPLACE INTO SQL Command.
Can this easily be emulated in SQL Server 2005?
Starting a new Transaction, doing a Select() and then either UPDATE or INSERT and COMMIT is always a little bit of a pain, especially when doing it in the application and therefore always keeping 2 versions of the statement.
I wonder if there is an easy and universal way to implement such a function into SQL Server 2005?
This is something that annoys me about MSSQL (rant on my blog). I wish MSSQL supported upsert.
#Dillie-O's code is a good way in older SQL versions (+1 vote), but it still is basically two IO operations (the exists and then the update or insert)
There's a slightly better way on this post, basically:
--try an update
update tablename
set field1 = 'new value',
field2 = 'different value',
...
where idfield = 7
--insert if failed
if ##rowcount = 0 and ##error = 0
insert into tablename
( idfield, field1, field2, ... )
values ( 7, 'value one', 'another value', ... )
This reduces it to one IO operations if it's an update, or two if an insert.
MS Sql2008 introduces merge from the SQL:2003 standard:
merge tablename as target
using (values ('new value', 'different value'))
as source (field1, field2)
on target.idfield = 7
when matched then
update
set field1 = source.field1,
field2 = source.field2,
...
when not matched then
insert ( idfield, field1, field2, ... )
values ( 7, source.field1, source.field2, ... )
Now it's really just one IO operation, but awful code :-(
The functionality you're looking for is traditionally called an UPSERT. Atleast knowing what it's called might help you find what you're looking for.
I don't think SQL Server 2005 has any great ways of doing this. 2008 introduces the MERGE statement that can be used to accomplish this as shown in: http://www.databasejournal.com/features/mssql/article.php/3739131 or http://blogs.conchango.com/davidportas/archive/2007/11/14/SQL-Server-2008-MERGE.aspx
Merge was available in the beta of 2005, but they removed it out in the final release.
What the upsert/merge is doing is something to the effect of...
IF EXISTS (SELECT * FROM [Table] WHERE Id = X)
UPDATE [Table] SET...
ELSE
INSERT INTO [Table]
So hopefully the combination of those articles and this pseudo code can get things moving.
I wrote a blog post about this issue.
The bottom line is that if you want cheap updates and want to be safe for concurrent usage, try:
update t
set hitCount = hitCount + 1
where pk = #id
if ##rowcount < 1
begin
begin tran
update t with (serializable)
set hitCount = hitCount + 1
where pk = #id
if ##rowcount = 0
begin
insert t (pk, hitCount)
values (#id,1)
end
commit tran
end
This way you have 1 operation for updates and a max of 3 operations for inserts. So, if you are generally updating, this is a safe cheap option.
I would also be very careful not to use anything that is unsafe for concurrent usage. It's really easy to get primary key violations or duplicate rows in production.