MySQL: Update table with an IN (#variable) failed - mysql

I'm trying to update a table, with the IN function and a variable.
The content of the variable is a sub-query that returns the expected values aka ID for each STRING. When I copy/paste the values in the update, everything is fine.
USE `DB1`;
SET SQL_SAFE_UPDATES = 0;
SET #VAR1 = "STRING1,STRING2,STRING3,STRING4";
SET #VAR2 = (SELECT GROUP_CONCAT(`id`) FROM `tbl_A` WHERE FIND_IN_SET(`description`, #VAR1) AND `fieldtype` = '');
UPDATE `tbl_A`
SET `idaccount` = 2
WHERE `id` IN (#VAR2);
SET SQL_SAFE_UPDATES = 1;
So why when I use the variable, it updates only the first row?

The variable #VAR2 is a scalar variable, and can't store a result set. To do that, you would need a table variable, which MySQL does not directly support (note that other databases, such as SQL Server, do support them).
But, you may inline the subquery and it should work:
SET #VAR1 = "STRING1,STRING2,STRING3,STRING4";
UPDATE tbl_A
SET idaccount = 2
WHERE id IN (SELECT id FROM (
SELECT id FROM tbl_A
WHERE FIND_IN_SET(description, #VAR1) AND fieldtype = ''
)x );
Note that the subquery following the IN clause of the update is wrapped twice, to avoid the problem of trying to reference the same table which is being updated. This trick actually materializes the subquery, leaving it independent of what happens during the update.

Related

Can I set multiple columns to NULL in MySQL in bulk?

I have a very large database and for testing, I want to set a certain amount of data to NULL.
As an example, I have 57 columns across 3 tables, all of which need to be nullified. I can't delete the rows, I just need to know that if the row exists and there's no data in those fields, that everything still works.
To clarify, all the data in those fields has been moved to anther table, and the old data was not wiped in the migration. To test my reports I need to know that the reports are pulling from the new location, not the old, since as new data is added, it will only go to the new location. Our plan is to generate each report from the old database, migrate, and then generate them again and compare. But to ensure that they are pulling from the right place, we want to wipe the old data so it doesn't provide a false positive.
Is there a way for me to do this in bulk or should I resign myself to writing one comma separated SET statement after another?
You can create the statements using the data from the internal information_schema.COLUMNS table.
Assuming you have this table:
CREATE TABLE my_table (
keep1 INT,
keep2 INT,
set_null1 INT,
set_null2 INT,
set_null3 INT
);
and you want to set all columns to NULL except of keep1 and keep2. Execute the following script:
set #db_name = 'test';
set #table_name = 'my_table';
set #exclude_columns = 'keep1,keep2';
select concat(
'UPDATE `', #table_name, '` SET\n',
group_concat('`', COLUMN_NAME, '` = NULL' separator ',\n'),
';'
)
from information_schema.COLUMNS c
where c.TABLE_SCHEMA = #db_name
and c.TABLE_NAME = #table_name
and find_in_set(c.COLUMN_NAME, #exclude_columns) = 0;
This will generate the following statement:
UPDATE `my_table` SET
`set_null1` = NULL,
`set_null2` = NULL,
`set_null3` = NULL;
Copy the result and paste it into your UPDATE script. Do it for all 12 tables adjusting the variables #db_name, #table_name and #exclude_columns.
See demo on db-fiddle.
This is a very unusual task for an SQL database, so it's not surprising that it's a bit awkward.
As you know, to set multiple columns to NULL in an UPDATE statement, you'd have to set each column individually.
UPDATE mytable
SET col1 = NULL, col2 = NULL, ... col57 = NULL
WHERE id = ?;
That could be quite a bit of typing. Or it could be a task to write code to loop over the column names in your table, and concatenate the terms for UPDATE statement. Up to you.
An alternative that might be easier is to delete the row and then re-insert it with no values specified except the primary key.
DELETE FROM mytable WHERE id = ?;
INSERT INTO mytable SET id = ?;
By omitting the other columns, they'll be NULL or else take a DEFAULT value defined in your table. If you want those columns with defaults to be NULL too, you'll have to specify that.
INSERT INTO mytable SET id = ?, col23 = NULL;

Execution of UPDATE based on condition

Is there a way in MySQL to insert a condition based on which the entire UPDATE query will be executed? I know that you can use IF or CASE within the query itself to insert different values, but I'm talking about this scenario:
IF ( condition is true ) UPDATE ...
Let's say I wanted to validate data and execute the UPDATE based on result (I know it's a bad idea and data validation should be done scripting wise, I'm just reviewing the theoretical possibilities). Like here below where I test a value against regexp to check if it's numerical value:
UPDATE executed:
IF ( "12345" REGEXP "[0-9]+" ) UPDATE table SET numdata = "12345" WHERE...;
UPDATE not executed:
IF ( "a1234" REGEXP "[0-9]+" ) UPDATE table SET numdata = "a1234" WHERE...;
Thanks,
Prez
Just put the REGEXP in your WHERE clause:
DECLARE mynumdata varchar(10) = "a1234";
UPDATE table SET numdata = mynumdata
WHERE #numdata REGEXP "[0-9]+"
AND <other update conditions>;
One way to do this would be to add the condition to the WHERE clause.
So if you originally had
UPDATE table SET numdata = "a1234" WHERE id=1
you could write this as
UPDATE table SET numdata = "a1234" WHERE id=1 AND "1234" REGEXP "[0-9]+"

sql -- select and update

I've being trying to amend the solution found in this tutorial to write an SQL query that both SELECTs and UPDATEs my table:
enter link description here
DECLARE #column1 varchar(2);
SET #column1 = (SELECT `Id`, `Url` FROM `MyTable` WHERE `Retrieved` = 0);
SELECT * FROM `MyTable` WHERE `AdId`, `Url` = #column1;
UPDATE `MyTable` SET `Retrieved` = 1 where `Id`, `Url` = #column1;
What i'm trying to achieve the following simultaneously:
SELECT Id, Url FROM MyTable WHERE Retrieved = 0
UPDATE MyTable SET Retrieved = 1
for the rows where i have SELECTed the results from
Basically, i want to select all data from ID and Url columns where the Retrieved column equals 0. I then want to set the Retrieved column to 1 for the rows i have selected.
The "normal" SQL method would be:
UPDATE MyTable
SET Retrieved = 1
WHERE id IN (SELECT Id FROM MyTable WHERE Retrieved = 0);
That does not work in MySQL. Assuming that id is unique in MyTable (a reasonable assumption in my opinion), then this does what you want:
UPDATE MyTable
SET Retrieved = 1
WHERE Retrieved = 0;
UPDATE t SET t.Retrieved=1 FROM MyTable t WHERE t.Retrieved=0
This is only updating rows, that essentially you've selected. In your case you want to update rows where the selected rows Retrieved column is equal to 0.
The other thing and maybe for readibility or you need the rows returned you can use a cte.
--first get only the records you need
WITH MyRecords_cte
AS
(
SELECT Id, URL, Retreived FROM MyTable WHERE Retreived=0
)
UPDATE MyRecords_cte SET MyRecords_cte.Retreived=1
Once you're done with the update you can return the data.

Adding incremented value to new column

i am trying to add incremented values to a new column in table.
Here is a sample structure of table
---------------------
Name - class - id
---------------------
abbc - 2 - null
efg - 4 - null
ggh - 6 - null
---------------------
i want to write a query that will generate unique id's for all records in table
Here is the query i have tried but show null
set #i=0;
update table1 set id =(#i:=#i+1);
What you have shown should work; the id column should be getting assigned values.
I tested your statement; I verified it works on my database. Here's the test case I ran:
CREATE TABLE table1 (`name` VARCHAR(4), class TINYINT, id INT);
INSERT INTO table1 (`name`,class) VALUES ('abbc',2),('efg',4),('ggh',6);
SET #i=0;
UPDATE table1 SET id =(#i:=#i+1);
SELECT * FROM table1;
Note that MySQL user variables are specific to a database session. If the SET is running in one session, and the UPDATE is running another session, that would explain the behavior you are seeing. (You didn't mention what client you ran the statements from; most clients reuse the same connection, and don't churn connections for each statement, I'm just throwing that out as a possibility.)
To insure that #i variable is actually initialized when the UPDATE statement runs, you can do the initialization in the UPDATE statement by doing something like this:
UPDATE table1 t
CROSS
JOIN (SELECT #i := 0) s
SET t.id =(#i:=#i+1);
I tested that, and that also works on my database.
try this query my friend:
set #i=0;
update table1 set id =(select #i:=#i+1);
SQL Fiddle
SET #a = 0;
UPDATE table_name SET id = #a:=#a+1;
Use AUTOINCREMENT parameter for the respective column instead. This parameter will put an unique auto incremented value in the respective column.

Trigger an update on a column of a row if a select partially matches one of that row's columns

To be more clear:
The table thetable (id int, username varchar(30), password varchar(30), last_successful_login timestamp, last_unsuccessful_login timestamp, another_variable varchar(30)) has the following row: (1, "tgh", "pass", 0, 0, "another")
1) Wrong User/Pass Pair, but there is a row with the username
I want select id from thetable where username="tgh" and password="wrongpass" and another_variable="another"; to update the last_unsuccessful_login columns of all the rows with username="tgh" AND another_variable="another" (which is unique, there can't be two rows with ("tgh", "another") pair. There can be ("tgh", "another2") though.) to CURRENT_TIMESTAMP.
So the example row would be (1, "tgh", "pass", 0, CURRENT_TIMESTAMP, "another"), after the "select" query that does not completely match.
To be even more clear, I am trying to avoid running an extra update with only username="tgh" and another_variable="another" on the table, i.e. update thetable set last_unsuccessful_login=CURRENT_TIMESTAMP where username="tgh" and another_variable="another";, according to the result of the select.
2) Correct User/Pass Pair
Also, if all three username and password and another_variable matches, this time I want to set the last_successful_login to CURRENT_TIMESTAMP.
That would make the example row `(1, "tgh", "pass", CURRENT_TIMESTAMP, 0, "another")
What is the most efficient way to do this?
The short answer to your question is no, it is not possible for a SELECT statement to cause or trigger an update. (The caveat here is that a SELECT statement can call a FUNCTION (MySQL stored program) which can perform an UPDATE.)
You can't get around issuing an UPDATE statement; an UPDATE statement has to be issued from somewhere, and a SELECT statement cannot "trigger" it.
It is possible to have a single UPDATE statement do the check of the supplied password against the current value in the password column, and set both the last_successful_login and last_unsuccessful_login columns, e.g.:
UPDATE thetable
SET last_successful_login =
IF(IFNULL(password,'')='wrongpass',CURRENT_TIMESTAMP,0)
, last_unsuccessful_login =
IF(IFNULL(password,'')='wrongpass',0,CURRENT_TIMESTAMP)
WHERE username='tgh'
AND another_variable='another'
So, you could issue an UPDATE statement first; and then issue a SELECT statement.
If you want to minimize the number of "roundtrips" to the database, at the cost of additional complexity (making it harder for someone else to figure out what is going on) you could put the UPDATE statement into a stored program. If you put this into a function, you could set the return value to indicate whether the login was successful.
SELECT udf_login('username','wrongpass','another')
So, from your application, it looks like you are doing a login check, but the called function can perform the UPDATE.
CREATE FUNCTION `udf_login`
( as_username VARCHAR(30)
, as_password VARCHAR(30)
, as_another_variable VARCHAR(30)
) RETURNS INT
READS SQL DATA
BEGIN
UPDATE `thetable`
SET `last_successful_login` =
IF(IFNULL(`password`,'')=IFNULL(as_password,''),CURRENT_TIMESTAMP,0)
, `last_unsuccessful_login` =
IF(IFNULL(`password`,'')=IFNULL(as_password,''),0,CURRENT_TIMESTAMP)
WHERE `username` = as_username
AND `another_variable` = as_another_variable;
-- then perform whatever checks you need to (e.g)
-- SELECT IFNULL(t.password,'')=IFNULL(as_password,'') AS password_match
-- FROM `thetable` t
-- WHERE t.username = as_username
-- AND t.another_variable = as_another_variable
-- and conditionally return a 0 or 1
RETURN 0;
END$$