MySQL update multiple rows at once depending on 2 fields - mysql

I'm trying to optimise some mysql queries on a website, and I've hit a bit of a snag - I currently have (in a loop):
UPDATE `table` SET `field1` = `field1` + 1 WHERE `field2` = '$some_value' AND `field3` = '$another_value'
and I wish to optimise it to perform this all in 1 query - if there was no field3 I know I could use a WHERE field2 = IN() and I know that I could do the query like:
UPDATE `table` SET `field1` = `field1` + 1 WHERE (`field2` = '$some_value' AND `field3` = '$another_value') OR (`field2` = '$some_value2' AND `field3` = '$another_value2') OR ...
but this is going to be for several thousand rows at once and is very hacky - can anyone think of a solution this?

Well, if the values for each of your $some_value's are relatively consistent over time, you could turn them into a view, then join against that view to perform the update:
UPDATE
table
SET
field1 = field1+1
FROM
myFilterView
WHERE
table.field2=myFilterView.field2 AND
table.field3=myFilterView.field3

You could create a temporary table with the new data in it (same exact columns as your original table). After all the inserts are done, then update the table with this data.
create temporary table temp (field1 int(5), field2 varchar(10), field3(varchar(10));
insert into t(field1, field2, field3) values (1, "abcd", "abcd"), (2, "efg", "efg"), etc
update tbl inner join t on t.field2 = tbl.field2 and t.field3 = tbl.field3
set tbl.field1 = t.field1 + 1;

Related

Update database sortorder column based on array value list

I need to update the sort order column based on the sequence of primary key values I get as array list from ajax call. For example I have 2 coulmns (ID, Sortorder) with values (23,1)(32,2)(21,3)(43,4), now the user from the frontend moves the 3rd row(21,3) above second row(32,2) and I get the ID array sequence as 23, 21, 32, 43 which I have to maintain. From this list, I am trying to update the sororder as per the sequence, so the database table values should look as (23,1)(32,3)(21,2)(43,4). Could you help me to get this DB update statement.
Attached the print screen for better understanding:
Java logic I have, trying to find an update sql statement to loop from an array list. I have ~1000 rows in my table and with my logic, this would trigger 1000 update queries, I don't think this is efficient. Trying to find an alternate efficient way.
Connection conn = null;
PreparedStatement pstmt = null;
conn = getConnection();
String query = "update Sortordertable set sortorder = ? where Id = ? ";
pstmt = conn.prepareStatement(query); // create a statement
String str[]=String.valueOf(s.getRecordId()).split(";");//id1;id;id3;.... list I get from ajax call
for(int i=0;i<str.length();i++)
{
pstmt.setId(1,i++); //set sortorder value as 1, 2, 3..
pstmt.setInt(2, str[i]); // In this line I want to use my array-list to update my table.
pstmt.executeUpdate(); // execute update statement
}
For the specific use case you have showed, you could use the following query, which would update only the rows with id=32 or id=23.
UPDATE t1 SET
sortorder = CASE WHEN id = 32 THEN 3 ELSE 2 END
WHERE id IN (32, 21);
This could be adapted for multiple updates if you don't update the database after each operation. but will grow with the number of operations made by the user before the update is triggered.
Edit to address comment:
If, as in the example given in your comment, you want to move the row with order 4 to the first row, you can use the following:
UPDATE t1 SET
sortorder = CASE WHEN sortorder = 4 THEN 1 ELSE sortorder + 1 END
WHERE sortorder <= 4;
I've added a where clause in that last query to illustrate that you can easily adapt this to different use case.
Here's what you can do:
DELIMITER $$
CREATE PROCEDURE `sp_update_positions`(
IN `p_positions` VARCHAR(255)
)
BEGIN
SET #positions = REPLACE(p_positions, ',', '),(');
SET #positions = CONCAT('(', #positions, ')');
DROP TEMPORARY TABLE IF EXISTS tmpPositions;
CREATE TEMPORARY TABLE tmpPositions(
`position` int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`order_id` int(10) unsigned NOT NULL
);
SET #insertString = CONCAT('INSERT INTO tmpPositions (`order_id`) VALUES ', #positions);
PREPARE insertStatement FROM #insertString;
EXECUTE insertStatement;
DEALLOCATE PREPARE insertStatement;
UPDATE orders
INNER JOIN tmpPositions ON order_id = orders.id
SET orders.Sortorder = tmpPositions.position;
SELECT ROW_COUNT() AS rowCount;
END$$
DELIMITER ;
Then you call it like this:
CALL sp_update_positions('24,23,21,22');

MySQL update or insert for tables without primary key

I know the simple way to perform update or insert is using 'REPLACE' but it needs a primary key, how about in the case of a table without primary key?
I have 5 columns in my table:
remark_id(the auto-increment primary key)
user_id
remark_user_id
remark
last_modified
I wish to check whether the pair of user_id and remark_user_id exists first before updating the remark, else a new row will be created to save the remark with the user_id and remark_user_id.
Here's my code
INSERT INTO `users_remark` (`user_id`, `remark_user_id`, `remark`)
SELECT 1,3 ,'testing123'
FROM dual
WHERE NOT EXISTS
(SELECT *
FROM `users_remark`
WHERE `user_id` = 1
AND `remark_user_id` = 3)
After running the SQL, nothing happens in my Database. No record was added or updated.
[Edited]
Code changes using IF...ELSE... but it comes with some syntax errors on first line
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'IF EXISTS (SELECT * FROM users_remark WHERE user_id' at line 1
IF EXISTS (
SELECT * FROM `users_remark`
WHERE `user_id`=1 AND `remark_user_id` = 3
)
THEN UPDATE `users_remark` SET `remark` = 'testing123'
WHERE `user_id`=1 AND `remark_user_id` = 3
ELSE
INSERT INTO `users_remark` SET `remark` = 'testing123', `user_id`=1, `remark_user_id` = 3
Here is the query with both INSERT and UPDATE clauses (T-SQL syntax):
IF [condition here] BEGIN
UPDATE `users_remark`
SET `remark` = 'testing123'
WHERE `user_id`=1
AND `remark_user_id` = 3
END
ELSE BEGIN
INSERT INTO `users_remark` (`user_id`, `remark_user_id`, `remark`)
VALUES (1, 3, 'testing123)
END
EDIT: Same query with MySQL syntax
DECLARE #numrecords INT
SELECT #numrecords = count(*)
FROM `users_remark`
WHERE `user_id` = 1
AND `remark_user_id` = 3
IF #numrecords > 0 THEN
UPDATE `users_remark`
SET `remark` = 'testing123'
WHERE `user_id`=1
AND `remark_user_id` = 3
ELSE
INSERT INTO `users_remark` (`user_id`, `remark_user_id`, `remark`)
VALUES (1, 3, 'testing123)
END IF
Hope this will help you.
I finally try on putting code to php, it works and much easier to understand.
$checkRemark = mysqli_query($GLOBALS['db_conn'], "SELECT * FROM `users_remark` WHERE `user_id`=".$data['uid']." AND `remark_user_id` = ".$data['ruid']);
if (mysqli_num_rows($checkRemark)>0){
$remarkSql = "UPDATE `users_remark`
SET `remark` = '".$data['remark']."'
WHERE `user_id`=".$data['uid']."
AND `remark_user_id` = ".$data['ruid'];
} else {
$remarkSql = "INSERT INTO `users_remark` (`user_id`, `remark_user_id`, `remark`)
VALUES (".$data['uid'].", ".$data['ruid'].", '".$data['remark']."')";
}

Update data based on previous row combination values

I have a MySQL table with the following structure:
ID
CatLevel1 CatLevel2 CatLevel3 ... CatLevel6 AcctLevel1 AcctLevel2 ... AcctLevel6
(6 columns for CatLevel, from 1 to 6) and 6 columns for AcctLevel, from 1 to 6).
Beginning with AcctlLevel1, I need to update it depending on the values found in the Catelevel fields in the following manner:
Begin iteration on CatLevel1. Initial value for AcctLevel1 -> 01.
If CatLevel1 Row(n) <> CatLevel1 Row(n-1) then AcctLevel1 -> 02.
meaning that each time a new value (not matching the previous row) is found in CatLevel1, increase the AcctLevel1 by 1 also adding a leading zero for values less than 10.
When the last row in CatLevel1 has been iterated, then begin with CatLevel2 and iterate through it in the same manner.
I was wondering which way to go, to break it down in smaller pieces and code it with PHP or to do it all in MySQL with some sort or recursiveness?
Many of you will think I'm asking for the solution, but I'm really asking for some ideas to get me started because I'm a bit confused on how to go.
Based on your requirement I've come with with the following.
The first Update does a necessary reset.
The second causes AcctLevel1 to be 1 whenever there is a change with the previous row.
The third Update sums up these 1 to create the final result.
Data:
create table data
(
id int,
CatLevel1 varchar(5),
AcctLevel1 varchar(5)
);
insert into data values (0,'1','0');
insert into data values (1,'2','0');
insert into data values (2,'1','1');
insert into data values (3,'2','1');
insert into data values (4,'2','1');
SQL Commands:
UPDATE `data` t1 SET AcctLevel1 = 0;
update `data` t1
left outer JOIN `data` t2
on t1.id-1 = t2.id
set t1.AcctLevel1 =
case when t1.CatLevel1 = t2.CatLevel1 then t1.AcctLevel1
else t2.AcctLevel1+1 end;
update `data` t1
set t1.AcctLevel1 =
( select SUM(TEMP.AcctLevel1) from (SELECT *FROM `data`) AS TEMP where TEMP.ID <= t1.ID );
SQL Fiddle: http://sqlfiddle.com/#!2/8fb647/2
2ND SQL Fiddle:http://sqlfiddle.com/#!2/c047bc/1
Update: Final Toy Query
UPDATE `data` t4
SET AcctLevel1 =
(
SELECT CASE WHEN TEMP.SCHANGES IS NULL THEN 0 ELSE TEMP.SCHANGES END from
(SELECT T3.ID,
(SELECT SUM(CASE WHEN t1.CatLevel1 = t2.CatLevel1 THEN 0 ELSE 1 END) AS SUM_CHANGES
FROM `data` t1,
`data` t2
WHERE t1.id-1 = t2.id
AND t1.ID <= t3.ID) AS SCHANGES
FROM `DATA` t3 ) as TEMP where TEMP.ID = T4.ID
);
Final JSFiddle: http://sqlfiddle.com/#!2/325f16/2

mysql REPLACE INTO and optional values IFNULL

I'm trying to do something like this inside of a stored procedure:
REPLACE INTO mytable
SET myid = `IDvalue`, mytitle = `sMyTitle`, myoptionalvalue = IFNULL(`sMyOptValue`, myoptionalvalue);
But not seems to work, any idea how to do this?
Thanks!
The REPLACE INTO syntax works exactly like INSERT INTO except that any old rows with the same primary or unique key is automaticly deleted before the new row is inserted.
This means that instead of a WHERE clause, you should add the primary key to the values beeing replaced to limit your update.
REPLACE INTO myTable (
myPrimaryKey,
myColumn1,
myColumn2
) VALUES (
100,
'value1',
'value2'
);
...will provide the same result as...
UPDATE myTable
SET myColumn1 = 'value1', myColumn2 = 'value2'
WHERE myPrimaryKey = 100;

Calling stored procedure sequentially from .sql file

I'm stuck here.
I've got a Procedure that I want to run X* times in a row. (*X is couple of thousands times)
The procedure based on input data does this:
1. Looks for an actions.id, if not found LEAVEs.
2. Looks for users.id, if not found, creates one and uses LAST_INSERT_ID();
3-5. Looks for summaries.id (3 types, total, daily and monthly), if not found, creates one and uses it's ID.
6. Once all required ids are collected, INSERTs new row into actions and either updates the summaries rows in a transaction, so if any fails - it does a ROLLBACK - no harm done.
7. Depending on the outcome SELECTs message.
CREATE PROCEDURE NEW_ACTION(
IN a_date TIMESTAMP,
IN u_name VARCHAR(255),
IN a_name VARCHAR(255),
IN a_chars INT,
IN url VARCHAR(255),
IN ip VARCHAR(15))
lbl_proc: BEGIN
DECLARE a_id, u_id, us_id, usd_id, usm_id, a_day, a_month, error INT;
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET error = 1;
SET error = 0;
SET a_day = DATE_FORMAT(SUBSTRING(a_date ,1,10), '%Y%m%d');
SET a_month = SUBSTRING(a_day, 1, 6);
/* 1. RETREIVING action.id */
SET a_id = (SELECT `id` FROM `actions` WHERE `name` = a_name);
IF a_id IS NULL THEN
SELECT 'error';
LEAVE lbl_proc;
END IF;
/* 2. RETREIVING users.id */
SET u_id = (SELECT `id` FROM `users` WHERE `name` = u_name);
IF u_id IS NULL THEN
INSERT INTO `users` (name) VALUES (u_name);
SET u_id = (SELECT LAST_INSERT_ID());
END IF;
/* 3. RETREIVING user_summaries.id */
SET us_id = (SELECT `id` FROM `users_summaries` WHERE `user_id` = u_id AND `action_id` = a_id);
IF us_id IS NULL THEN
INSERT INTO `users_summaries` (user_id, action_id) VALUES (u_id, a_id);
SET us_id = (SELECT LAST_INSERT_ID());
END IF;
/* 4. RETREIVING user_summaries_days.id */
SET usd_id = (SELECT `id` FROM `users_summaries_days` WHERE `day` = a_day AND `user_id` = u_id AND `action_id` = a_id);
IF usd_id IS NULL THEN
INSERT INTO `users_summaries_days` (day, user_id, action_id) VALUES (a_day, u_id, a_id);
SET usd_id = (SELECT LAST_INSERT_ID());
END IF;
/* 5. RETREIVING user_summaries_months.id */
SET usm_id = (SELECT `id` FROM `users_summaries_months` WHERE `month` = a_month AND `user_id` = u_id AND `action_id` = a_id);
IF usm_id IS NULL THEN
INSERT INTO `users_summaries_months` (month, user_id, action_id) VALUES (a_month, u_id, a_id);
SET usm_id = (SELECT LAST_INSERT_ID());
END IF;
/* 6. SAVING action AND UPDATING summaries */
SET autocommit = 0;
START TRANSACTION;
INSERT INTO `users_actions` (`date`, `user_id`, `action_id`, `chars`, `url`, `ip`) VALUES (a_date, u_id, a_id, a_chars, url, ip);
UPDATE `users_summaries` SET qty = qty + 1, chars = chars + a_chars WHERE id = us_id;
UPDATE `users_summaries_days` SET qty = qty + 1, chars = chars + a_chars WHERE id = usd_id;
UPDATE `users_summaries_months` SET qty = qty + 1, chars = chars + a_chars WHERE id = usm_id;
IF error = 1 THEN
SELECT 'error';
ROLLBACK;
LEAVE lbl_proc;
ELSE
SELECT 'success';
COMMIT;
END IF;
END;
Now, I've got raw data that I want to feed into this procedure. There's currently about 3000 rows.
I tried all the solutions I knew:
A. # mysql -uuser -ppass DB < calls.sql - Using php I've basically created a list of calls like this:
CALL NEW_ACTION('2010-11-01 13:23:00', 'username1', 'actionname1', '100', 'http://example.com/', '0.0.0.0');
CALL NEW_ACTION('2010-11-01 13:23:00', 'username2', 'actionname1', '100', 'http://example.com/', '0.0.0.0');
CALL NEW_ACTION('2010-11-01 13:23:00', 'username1', 'actionname2', '100', 'http://example.com/', '0.0.0.0');
...
This fails always (tried few times) at row 452 where it found two summary IDs (step 3).
I thought this could be due to the fact that earlier (rows 375-376) there are calls for the same user for the same action.
As if mysql didn't update tables in time, so the summary row created in CALL from line 375 isn't yet visible when line 376 gets executed - therefore creating another summary line.
Tought I'd try delaying calls...
B. Using mysql's SLEEP(duration).
This didn't change anything. Execution stops at the very same CALL again.
I'm out of ideas now.
Suggestions and help hugely appreciated.
NOTE: action names and user names repeat.
PS. Bear in mind this is one of my first procedures ever written.
PS2. Running mysql 5.1.52-community-log 64bit (Windows 7U), PHP 5.3.2 and Apache 2.2.17
EDIT
I've removed PHP related part of question to a separate question here.
EDIT2
Ok, I've deleted the first 200 calls from the .sql file. For some reason it went fine past the previous line that was stopping execution. Now it stopped at row 1618.
This would mean, that at one point a newly INSERTed summary row is no visible for a moment, therefore when it happens that one of the following iterations want to SELECT it, it's not yet accessible for them. Is that a MySQL bug?
EDIT3
Now there's another interesting thing I noticed. I investigated where two users_summaries get created. This happens (not always, but if, then it is) when there are two CALLs referring to the same user and action in close proximity. They could be next to each other or separated by 1 or 2 different calls.
If I move one of them (within .sql file) like 50-100 rows lower (executed earlier) than it's fine. I even managed to make the .sql file work as a whole. But this still doesn't really solve the problem. With 3000 rows it's not that bad, but if I had 100000, I'm lost. I can't rely on manual tweaks to .sql file.
This isn't really a solution, but a workaround.
Just to clarify, summary tables had id column as PRIMARY KEY with AUTO_INCREMENT option and indexes on both user_id and action_id column.
My investigation showed that although my procedure was looking for an entry that existed using WHERE user_id = u_id AND action_id = a_id in certain situations it didn't find it causing new row being inserted with the same user_id and action_id values - something I did not want.
Debugging the procedure showed that the summary row I was looking for, although not accessible with WHERE user_id = u_id AND action_id = a_id condition, was properly returned when calling it's id - PRIMARY KEY.
With this find I decided to change format of id column, from UNASIGNED INT with AUTO_INCEREMENT to a CHAR(32) which consisted of:
<user_id>|<action_id>
This meant that I knew exactly what the id of the row I wanted is even before it existed. This solved the problem really. It also enabled me to use INSERT ... ON DUPLICATE KEY UPDATE ... construct.
Below my updated procedure:
CREATE PROCEDURE `NEW_ACTION`(
IN a_date TIMESTAMP,
IN u_name VARCHAR(255),
IN a_name VARCHAR(255),
IN a_chars INT,
IN url VARCHAR(255),
IN ip VARCHAR(15))
SQL SECURITY INVOKER
lbl_proc: BEGIN
DECLARE a_id, u_id, a_day, a_month, error INT;
DECLARE us_id, usd_id, usm_id CHAR(48);
DECLARE sep CHAR(1);
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET error = 1;
SET sep = '|';
SET error = 0;
SET a_day = DATE_FORMAT(SUBSTRING(a_date ,1,10), '%Y%m%d');
SET a_month = SUBSTRING(a_day, 1, 6);
/* RETREIVING action.id */
SET a_id = (SELECT `id` FROM `game_actions` WHERE `name` = a_name);
IF a_id IS NULL THEN
SELECT 'error';
LEAVE lbl_proc;
END IF;
/* RETREIVING users.id */
SET u_id = (SELECT `id` FROM `game_users` WHERE `name` = u_name);
IF u_id IS NULL THEN
INSERT INTO `game_users` (name) VALUES (u_name);
SET u_id = LAST_INSERT_ID();
END IF;
/* SETTING summaries ids */
SET us_id = CONCAT(u_id, sep, a_id);
SET usd_id = CONCAT(a_day, sep, u_id, sep, a_id);
SET usm_id = CONCAT(a_month, sep, u_id, sep, a_id);
/* SAVING action AND UPDATING summaries */
SET autocommit = 0;
START TRANSACTION;
INSERT INTO `game_users_actions` (`date`, `user_id`, `action_id`, `chars`, `url`, `ip`)
VALUES (a_date, u_id, a_id, a_chars, url, ip);
INSERT INTO `game_users_summaries` (`id`, `user_id`, `action_id`, `qty`, `chars`)
VALUES (us_id, u_id, a_id, 1, a_chars)
ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars;
INSERT INTO `game_users_summaries_days` (`id`, `day`, `user_id`, `action_id`, `qty`, `chars`)
VALUES (usd_id, a_day, u_id, a_id, 1, a_chars)
ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars;
INSERT INTO `game_users_summaries_months` (`id`, `month`, `user_id`, `action_id`, `qty`, `chars`)
VALUES (usm_id, a_month, u_id, a_id, 1, a_chars)
ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars;
IF error = 1 THEN
SELECT 'error';
ROLLBACK;
LEAVE lbl_proc;
ELSE
SELECT 'success';
COMMIT;
END IF;
END
Anyway, I still think there's some kind of a bug in MySQL, but I consider problem solved.