Dynamic MySQL Update Statement - mysql

I'm trying to write a prodecure that updates a value in a given column-name where the users id equals given user ID.
_strong_1 is a variable that contains the column name, i.e: 'category_1', for example.
SELECT COLUMN_NAME FROM information_schema.`COLUMNS` C
WHERE table_name = 'subscribers_preferences' AND COLUMN_NAME LIKE _strong_1 INTO #columns;
SET #table = 'subscribers_preferences';
SET #s = CONCAT('UPDATE ',#table,' SET ', #columns = 1);
PREPARE stmt FROM #s;
EXECUTE stmt;
There's an error within the 'SET #s =' statement. I can get it to work with a simple SELECT statement, but UPDATE is being tricky.
Thanks in advance.

You need to put = 1 in quotes.
SET #s = CONCAT('UPDATE ',#table,' SET ', #columns, ' = 1');
Otherwise, you're comparing #columns with 1, and concatenating either 1 or 0 (probably always 0, since I doubt you have a column named 1) to the SQL, which is creating invalid SQL.
Note that the above code will only update one column. If #columns is supposed to hold 3 columns, you need to use GROUP_CONCAT in your query that sets it.
SELECT GROUP_CONCAT(CONCAT(column_name, ' = 1')) AS #columns
FROM information_schema.columns
WHERE table_name = 'subscribers_preferences' and column_name LIKE _strong_1;
SET #table = 'subscribers_preferences';
SET #s = CONCAT('UPDATE ',#table,' SET ', #columns);
I suspect you also need to add a WHERE clause to this SQL so it just updates the row for the given ID. As currently written, it will update all rows.
The fact that you need to write the query like this suggests improper normallization of your data. Instead of having each preference option in a different column, they should be different rows of the table, with the key being something like (user_id, setting_name).

Related

How to call MySQL function for every column that is present in table?

I need to call a mySQL function for all columns in a table.
I know how to do it for a particular column
Like this:
UPDATE `table_name` set `column_name` = function_name(`column_name`)
But i have no clue how to do it for all columns at once.
Thanks in advance.
Little clarification: I dont want to manually mention all columns, as i probably could have 200 columns table.
But i have no clue how to do it for all columns at once.
You just can't - there is no such shortcut in the update syntax.
You can do this with a single update statement, but you need to enumerate each and every column, like:
update table_name set
column_name1 = function_name(column_name1),
column_name2 = function_name(column_name2),
column_name3 = function_name(column_name3)
An alternative would be to use dynamic SQL to programatically generate the proper query string from catalog table information_schema.columns, and then execute it. This seems uterly complicated for what looks like a one-shot task... But here is sample code for that:
-- input variables
set #table_schema = 'myschema';
set #table_name = 'mytable';
set #function_name = 'myfunction';
-- in case "GROUP_CONCAT()" returns more than 1024 characters
set session group_concat_max_len = 100000;
-- build the "set" clause of the query string
select
#sql := group_concat(
'`', column_name, '` = ', #table_schema, '.', #function_name, '(`', column_name, '`)'
separator ', '
)
from information_schema.columns
where table_schema = #table_schema and table_name = #table_name;
-- entire query string
set #sql := concat('update ', #table_schema, '.', #table_name, ' set ', #sql);
-- debug
select #sql mysql;
-- execute for real
prepare stmt from #sql;
execute stmt;
deallocate prepare stmt;

How to loop through all the tables on a database to update columns

I'm trying to update a column (in this case, a date) that is present on most of the tables on my database. Sadly, my database has more than 100 tables already created and full of information. Is there any way to loop through them and just use:
UPDATE SET date = '2016-04-20' WHERE name = 'Example'
on the loop?
One painless option would be to create a query which generates the UPDATE statements you want to run on all the tables:
SELECT CONCAT('UPDATE ', a.table_name, ' SET date = "2016-04-20" WHERE name = "Example";')
FROM information_schema.tables a
WHERE a.table_schema = 'YourDBNameHere'
You can copy the output from this query, paste it in the query editor, and run it.
Update:
As #PaulSpiegel pointed out, the above solution might be inconvenient if one be using an editor such as HeidiSQL, because it would require manually copying each record in the result set. Employing a trick using GROUP_CONCAT() would give a single string containing every desired UPDATE query in it:
SELECT GROUP_CONCAT(t.query SEPARATOR '; ')
FROM
(
SELECT CONCAT('UPDATE ', a.table_name,
' SET date = "2016-04-20" WHERE name = "Example";') AS query,
'1' AS id
FROM information_schema.tables a
WHERE a.table_schema = 'YourDBNameHere'
) t
GROUP BY t.id
You can use SHOW TABLES command to list all tables in database. Next you can check if column presented in table with SHOW COLUMNS command. It can be used this way:
SHOW COLUMNS FROM `table_name` LIKE `column_name`
If this query returns result, then column exists and you can perform UPDATE query on it.
Update
You can check this procedure on sqlfiddle.
CREATE PROCEDURE UpdateTables (IN WhereColumn VARCHAR(10),
IN WhereValue VARCHAR(10),
IN UpdateColumn VARCHAR(10),
IN UpdateValue VARCHAR(10))
BEGIN
DECLARE Finished BOOL DEFAULT FALSE;
DECLARE TableName VARCHAR(10);
DECLARE TablesCursor CURSOR FOR
SELECT c1.TABLE_NAME
FROM INFORMATION_SCHEMA.COLUMNS c1
JOIN INFORMATION_SCHEMA.COLUMNS c2 ON (c1.TABLE_SCHEMA = c2.TABLE_SCHEMA AND c1.TABLE_NAME = c2.TABLE_NAME)
WHERE c1.TABLE_SCHEMA = DATABASE()
AND c1.COLUMN_NAME = WhereColumn
AND c2.COLUMN_NAME = UpdateColumn;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET Finished = TRUE;
OPEN TablesCursor;
MainLoop: LOOP
FETCH TablesCursor INTO TableName;
IF Finished THEN
LEAVE MainLoop;
END IF;
SET #queryText = CONCAT('UPDATE ', TableName, ' SET ', UpdateColumn, '=', QUOTE(UpdateValue), ' WHERE ', WhereColumn, '=', QUOTE(WhereValue));
PREPARE updateQuery FROM #queryText;
EXECUTE updateQuery;
DEALLOCATE PREPARE updateQuery;
END LOOP;
CLOSE TablesCursor;
END
This is just an example how to iterate through all tables in database and perform some action with them. Procedure can be changed according to your needs.
Assuming you are using MySQL, You can use Stored Procedure.
This post is a very helpful.
Mysql-loop-through-tables

MySQL Select columns where the column name contains a substring

I have a table with a lot of fields about a person and then several recommendations of other people.
They are named:
"recommendation_1_name" "recommendation_1_company" 'recommendation_1_contact"
"recommendation_2_name" "recommendation_2_company" "recommendation_2_contact"
and so on.
I am trying to come up with a statement that allows me to only get the recommendations.
I imported an excel file into the table so it's just one large table.
This is what I have and it is returning an Empty set.
select * from questionnaire where 'COLUMN_NAME' like '%recommendation%';
I've been playing around with it making a table with only the recommendation fields and it still doesn't return anything.
Mysql: select recommendation_1_name, recommendation_2_name etc... from (table) where (USER) = (USERID) or however you can uniquely identify that user.
This Query generates you dynamic a SELECT query with all fields like 'recommendation%'. You only must setup the Databasename, and the Tablename. You can directly query the result of my query or add the WHERE clause.
SELECT
CONCAT( 'SELECT ',
GROUP_CONCAT(COLUMN_NAME SEPARATOR ',\n')
)
FROM information_schema.columns
WHERE TABLE_SCHEMA = 'DBNAME'
AND TABLE_NAME = 'TABLENAME'
AND COLUMN_NAME LIKE 'recommendation%';
You really need to normalize your schema.
But just as an experiment and example for some other cases (maybe somebody really need it). Here is solution to get this case resolved using stored procedure:
CREATE PROCEDURE `get_recommendations`()
BEGIN
DECLARE Q VARCHAR(100);
DECLARE C_NAME VARCHAR(100);
DECLARE cur CURSOR FOR SELECT GROUP_CONCAT(column_name) as `columns`
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'test'
AND TABLE_NAME ='questionnaire'
AND COLUMN_NAME LIKE '%recommendation%'
;
SET Q = 'SELECT ';
OPEN cur;
FETCH cur INTO C_NAME;
SET Q = CONCAT(Q,C_NAME,' ');
CLOSE cur;
SET #Q = CONCAT(Q,'FROM questionnaire;');
PREPARE stmt FROM #Q;
EXECUTE stmt ;
END
Don't forget to replace TABLE_SCHEMA = 'test' with your real database name.

INSERT INTO ... SELECT without detailing all columns

How do you insert selected rows from table_source to table_target using SQL in MySQL where:
Both tables have the same schema
All columns should transfer except for the auto-increment id
Without explicitly writing all the column names, as that would be tedious
The trivial INSERT INTO table_target SELECT * FROM table_source fails on duplicate entries for primary key.
Either you list all of the fields you want in the insert...select, or you use something else externally to build the list for you.
SQL does not have something like SELECT * except somefield FROM, so you'll have to bite the bullet and write out the field names.
Column names have to be specified -
INSERT INTO table_target SELECT NULL, column_name1, column_name2, column_name3, ...
FROM table_source;
Just pass NULL as a value for the auto-increment id field.
Of course, primary key must be unique. It depends on what you want to achieve, but you could exclude rows with a primary key that already exists.
INSERT INTO table_target SELECT * FROM table_source
WHERE table_source.id NOT IN (SELECT id FROM table_target)
UPDATE: since you also need the extra rows, you should resolve the conflict first, does table_source have relationships? If not you could change those keys:
UPDATE table_source SET id = id + 1000
WHERE id IN (SELECT id FROM table_target)
Where 1000, is a constant, big enough so they go after the end of your table.
Tedious but safe and correct.
Writing INSERT statements without providing a list of columns leads to code that's hard to debug and, more importantly, very fragile code that will break if the definition of the table is changed.
If you absolutely can't write the column names out yourself then it's relatively easy to build a tool into your code that will create the comma-separated list for you.
This is my final solution to mass update with 'replace insert' command.
SET ##session.group_concat_max_len = ##global.max_allowed_packet;
SET #schema_db = 'db';
SET #tabl = 'table';
SET #cols = (SELECT CONCAT('`',GROUP_CONCAT(COLUMN_NAME SEPARATOR '`, `'), '`') FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = #schema_db AND TABLE_NAME = #tabl GROUP BY TABLE_NAME);
SET #Querystr = CONCAT('REPLACE INTO ',#schema_db,'.',#tabl,' SELECT ', #cols,' FROM import.tbl_', #tabl);
PREPARE stmt FROM #Querystr;
EXECUTE stmt;
I think you could use syntax like:
INSERT INTO table (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE c=c+1;
REF: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
Hope it helps
INSERT IGNORE just "bypass" the duplicate rows.
http://dev.mysql.com/doc/refman/5.5/en/insert.html
You can probably do it with prepared statements.
PREPARE table_target_insert FROM 'INSERT INTO table_target SELECT ? FROM table_source';
SET #cols:='';
SELECT #cols:=GROUP_CONCAT(IF(column_name = 'id','NULL',column_name) separator ",") FROM information_schema.columns WHERE table_name='table_source';
EXECUTE table_target_insert USING #cols;
It seems as if columns can not be given as a place holder in a MySQL Prepared Statement. I have compiled the following solution for testing:
SET #schema_db = 'DB';
SET #table = 'table';
SET #cols = (SELECT CONCAT(GROUP_CONCAT(COLUMN_NAME SEPARATOR ', '), "\n") FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = #schema_db AND TABLE_NAME = #table GROUP BY TABLE_NAME);
SET #Querystr = CONCAT('SELECT',' ', #cols,' ','FROM',' ',#schema_db,'.',#table,' ', 'Limit 5');
PREPARE stmt FROM #Querystr;
EXECUTE stmt;
You can use dynamic query:
DECLARE #Columns VARCHAR(MAX)=''
DECLARE #Query VARCHAR(MAX)=''
SELECT
#Columns = ISNULL(#Columns +',', '') + T.COLUMN_NAME
FROM
(
select name as COLUMN_NAME from sys.all_columns
where object_id = (select object_id from sys.tables where name = 'Source_Table')
and is_identity = 0
)T
set #Query = 'insert into Target_Table (' + SUBSTRING(#Columns,2 , 9999) + ') select ' + SUBSTRING(#Columns,2 , 9999) + ' from Source_Table';
PRINT #Query
EXEC(#Query)
The easiest way to do it is to use phpmyadmin to write the list of columns, then to change it as needed, in the example below I want to duplicate row with id=1078 and in this table I have id unique auto increment and alias unique.therefore I created my query as follow, with id & alias replaced by a desired value. and it worked like a charm.
INSERT INTO sy3_menuselect 1079, menutype, title, "alias", note, path, link, type, published, parent_id, level, component_id, checked_out, checked_out_time, browserNav, access, img, template_style_id, params, lft, rgt, home, language, client_id from sy3_menuwhere id=1078
Alternatively, to auto increment id, use the following Join statement:
INSERT INTO sy3_menuselect *
from (SELECT MAX(id+1 )from sy3_menu)a
join (select menutype, title, "alias", note, path, link, type, published, parent_id, level, component_id, checked_out, checked_out_time, browserNav, access, img, template_style_id, params, lft, rgt, home, language, client_idfrom sy3_menuwhere id=1079)b

Using mySQL variables

Is it possible in a sequence of SQL statements to get the value of a field and use that to name a table in another statement? I'm not sure if that's clear, so here's an psudo-example of what I'm trying to do:
// dataType is equal to "ratings"
#var = select dataType from theTable where anID = 5;
// needs to run as "from ratings-table"
select field1,field2 from #var-table where anID = 5;
I've been reading http://dev.mysql.com/doc/refman/5.0/en/user-variables.html but either I don't properly understand this, or its not the solution I'm looking for.
Yes, you can do this using prepared statements:
SET #TableName := 'ratings';
SET #CreateQuery := CONCAT('SELECT `field1`, `field2` FROM `', #TableName, '-table` WHERE `anID` = 5');
PREPARE statementCreate FROM #CreateQuery;
EXECUTE statementCreate;