I have within a database several tables where they all have username column. I would like to update one username and naturally I should update it in all tables.
I have this working solution:
UPDATE `user`,
`user_images`,
`user_comments`
SET `user`.`username` = 'new_name',
`user_images`.`username` = 'new_name',
`user_comments`.`username` = 'new_name'
WHERE `user`.`username` = 'old_name'
AND `user_images`.`username` = 'old_name'
AND `user_comments`.`username` = 'old_name'
I am hoping for a better query that can do the same action, as if table numbers got increased, do I really need to do this in 100 lines?
It sounds painful if you have to update each table. I would suggest using a stored procedure to finish the tedious job. Fisrt of all , make a table(named tablelist) which list all the tablename you would like to update. Then call the procedure by providing the two parameters where the o_name is the name you would like to change and the n_name is the new name to be changed into.
delimiter //
drop procedure if exists update_name//
create procedure update_name (o_name varchar(30),n_name varchar(30))
begin
declare t_name varchar(30);
declare done bool default false;
declare csr cursor for select tablename from tablelist;
declare continue handler for not found set done=true;
open csr;
lp: loop
fetch csr into t_name;
if done=true then
leave lp;
end if;
set #prep=concat('update ',t_name,' set `username`= "',n_name,'" where `username`= "',o_name,'";');
prepare prep_stat from #prep;
execute prep_stat;
deallocate prepare prep_stat;
end loop lp;
close csr;
end//
delimiter ;
The following call will change the name from john(case insensitive) to Xero in all tables listed in the tablelist table.
call update_name('John','Xero');
Related
I have created one procedure that returns me number of IDs
e.g. DECLARE ex_cursor for SELECT table_name from table_list
which will provide me List of tables for which I want to change the ID .
These IDs now I want to update, lets say ID = ID + 1 for that particular table.
and after this computation I want to store the value back to the table,
UPDATE table set ID = ID + 1
now what I am doing is that,
I retrieved table_list from the cursor ex_cursor and trying to write dynamic query and passing parameter of table in it, to update that particular table's ID.
OPEN ex_cursor ;
BEGIN
FETCH NEXT IN #temp
LOOP:
UPDATE #temp set ID = ID + 1
FETCH NEXT IN #temp
END LOOP;
END;
This is brief on what I am trying to do, I have written complete procedure in MySQL but there is error for this looping part. After opening the cursor I can not loop through it. but I can not find what the error is.
What should I do? I don't want any other approach because all Three queries are dependent on each other.
You must use prepared statements because #temp variable will no evaluate on direct a query. Also the loop format in MySQL is as follows:
DECLARE tmp_table_name varchar(50);
DECLARE done INT DEFAULT FALSE; -- flag to determine the end of loop
declare ex_cursor cursor for /*put your sql cursor here*/;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; -- event
OPEN ex_cursor;
ex_loop: LOOP
FETCH ex_cursor INTO tmp_table_name; -- must use a regular variable
if done then
leave ex_loop;
end if;
-- use prepard statement
set #sql = concat('UPDATE ',tmp_table_name,' set ID = ID + 1;');
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END LOOP;
I have a trigger in students table that deletes related records from other tables when i delete a student. I want to delete student's membership data by calling aspnet_delete_user stored procedure in the trigger . but this works just if I delete one student . and if I remover multiple students in one query it doesn't work .
How to call this SP for multi-row operation trigger ?
You'd have to use a cursor or build a dynamic SQL string (which uses a cursor without saying so). Alternatively, you could copy the logic from the stored procedure and see if you can tailor it to become set-based - I haven't looked at the procedure, so I'm not sure if this is feasible, practical or even possible, but it's the first thing I try to do before adding cursors or dynamic SQL to a trigger.
For a cursor, something like this (I'm guessing you pass a GUID or something to the procedure, but I have no idea):
CREATE TRIGGER dbo.StudentAfterDelete
ON dbo.Students
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MemberID UNIQUEIDENTIFIER;
DECLARE c CURSOR LOCAL STATIC FORWARD_ONLY READ_ONLY
FOR SELECT MemberID FROM deleted;
OPEN c;
FETCH NEXT FROM c INTO #MemberID;
WHILE ##FETCH_STATUS <> -1
BEGIN
EXEC dbo.aspnet_delete_user #MemberID;
FETCH NEXT FROM c INTO #MemberID;
END
CLOSE c;
DEALLOCATE c;
END
GO
Dynamic SQL, same assumptions:
CREATE TRIGGER dbo.StudentAfterDelete
ON dbo.Students
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += 'EXEC dbo.aspnet_delete_user '''
+ CONVERT(VARCHAR(36), MemberID) + ''';' FROM deleted;
EXEC sp_executesql #sql;
END
GO
However including the missing information up front is more useful. Don't assume that everyone who works with SQL Server has any clue what aspnet_delete_user does.
Maybe something like this:
CREATE TRIGGER TheNameOfTheTrigger
ON YourTableYouWantToTriggerOn
AFTER DELETE
AS
DECLARE #yourPrimaryKey int
DECLARE delete_cursor CURSOR FOR
SELECT
yourPrimaryKey
FROM
deleted
OPEN delete_cursor
FETCH NEXT FROM delete_cursor
INTO #yourPrimaryKey
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC aspnet_delete_user #yourPrimaryKey
FETCH NEXT FROM delete_cursor INTO #yourPrimaryKey
END
CLOSE delete_cursor
DEALLOCATE delete_cursor
GO
See more information here and here
I would like to use LIMIT in a cursor. The cursor should be used and updated several times within a loop, each time with different parameters of LIMIT. Here some code:
DELIMITER $$
CREATE PROCEDURE `updateIt`() READS SQL DATA
BEGIN
declare done int(1) default 0;
declare counter int(10) default 0;
declare xabc int(10) default 0;
declare tab1Cursor cursor for select abc from tab1 limit 100000*counter, 100000;
declare continue handler for not found set done=1;
loopCounter: LOOP
set done = 0;
open tab1Cursor;
igmLoop: loop
fetch tab1Cursor into xabc;
if done = 1 then leave igmLoop; end if;
-- do something
end loop igmLoop;
close tab1Cursor;
if (counter = 1039)
leave loopCounter;
end if;
set counter = counter + 1;
END LOOP loopCounter;
END $$
DELIMITER ;
This, however, does not work (I also tried it with the cursor in the LOOP counterLoop). Can Mysql deal with dynamic cursors?
From the MySQL Manual
a cursor cannot be used for a dynamic statement that is prepared and
executed with PREPARE and EXECUTE. The statement for a cursor is
checked at cursor creation time, so the statement cannot be dynamic.
However there are 2 ways, according to this post in mysql forums:
The first is for cases where absolutely only one user at a time will be running the procedure. A prepare statement can be used to create a view with the dynamic SQL and the cursor can select from this statically-named view. There's almost no performance impact. Unfortunately, these views are also visible to other users (there's no such thing as a temporary view), so this won't work for multiple users.
Analogously, a temporary table can be created in the prepare statement and the cursor can select from the temporary table. Only the current session can see a temporary table, so the multiple user issue is resolved. But this solution can have significant performance impact since a temp table has to be created each time the proc runs.
Bottom line: We still need cursors to be able to be created dynamically!
Here's an example of using a view to pass the table name and column name into a cursor.
DELIMITER //
DROP PROCEDURE IF EXISTS test_prepare//
CREATE PROCEDURE test_prepare(IN tablename varchar(255), columnname varchar(50))
BEGIN
DECLARE cursor_end CONDITION FOR SQLSTATE '02000';
DECLARE v_column_val VARCHAR(50);
DECLARE done INT DEFAULT 0;
DECLARE cur_table CURSOR FOR SELECT * FROM test_prepare_vw;
DECLARE CONTINUE HANDLER FOR cursor_end SET done = 1;
SET #query = CONCAT('CREATE VIEW test_prepare_vw as select ', columnname, ' from ', tablename);
select #query;
PREPARE stmt from #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
OPEN cur_table;
FETCH cur_table INTO v_column_val;
WHILE done = 0 DO
SELECT v_column_val;
FETCH cur_table INTO v_column_val;
END WHILE;
CLOSE cur_table;
DROP VIEW test_prepare_vw;
END;
//
DELIMITER ;
I have my data in a key-value table (in MySql) which has the shape:
id, key, value
Now for export to my users I need to transform it into a table with all the keys as columns. (actually only ~20 of the ~100 keys need to be in that table)
The workflow would be to provide it my users so that they can correct the table and to reimport the tables.
I'm just in writing a quite complex select command to give me such a table. It already is some heck of long command and I hope it will not need to be debugged.
I can't help but think this should be an already solved problem ;)
So I'm hoping anyone can provide me with some clues.
So far I hacked together a script that will construct a view.
DROP PROCEDURE IF EXISTS view_test;
DELIMITER //
CREATE PROCEDURE view_test()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE curr_prop VARCHAR(31);
DECLARE curr_table VARCHAR(31);
DECLARE fixed_prop VARCHAR(31);
DECLARE statement_a LONGTEXT DEFAULT 'SELECT pd.id,t0.date';
DECLARE statement_b LONGTEXT DEFAULT "FROM xxx.codes AS pd INNER JOIN
xxx.eav AS t0 ON (pd.id = t0.idX)";
DECLARE n_prop INT DEFAULT 0;
DECLARE cur1 CURSOR FOR SELECT DISTINCT prop FROM xxx.eav LIMIT 59;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
OPEN cur1;
main_loop: LOOP
FETCH cur1 INTO curr_prop;
IF done
THEN LEAVE main_loop;
END IF;
SET fixed_prop = curr_prop;
SET fixed_prop = REPLACE(fixed_prop,'-','_');
SET fixed_prop = REPLACE(fixed_prop,'+','_');
SET fixed_prop = REPLACE(fixed_prop,' ','_');
SET n_prop = n_prop + 1;
SET curr_table = CONCAT('t',n_prop);
SET statement_a = CONCAT(statement_a,',\n',curr_table,'.value AS ', fixed_prop);
SET statement_b = CONCAT(statement_b,' \nLEFT JOIN xxx.eav AS ',curr_table,
' ON (',curr_table,'.idX=pd.id AND t0.date=',curr_table,'.date AND
',curr_table,'.prop="',curr_prop,'")');
END LOOP;
-- cleanup
CLOSE cur1;
SET #S = CONCAT('CREATE OR REPLACE VIEW auto_flat_table AS\n',statement_a,
'\n',statement_b,
'\nGROUP BY pd.id,t0.date'); -- ,'\nGROUP BY pd.id'
PREPARE stmt_auto_demo FROM #S;
EXECUTE stmt_auto_demo;
DEALLOCATE PREPARE stmt_auto_demo;
END //
DELIMITER ;
CALL view_test();
That will create such a view for myself - though I'm a bit confused why this GROUP BY is neccesary.
Not being a SQL developer I'm pretty sure I took the long way in this program.
Also that I'm only able to use 61 tables for joins is a bummer. I think we will soon need more.
I'm working on an old database already in use for years and really crappy designed.
There is a table, "Articles", which contains a "code" column that will be our PK.
And many tables like "idXXXXX" where XXXXX is a "code" value with exactly the same structure.
I looked at the application using this database and saw that relations between tables is made there.
I'm not affraid of redesign the database access in the application, but I don't want to lose years of entries in the database.
I want to create a "campain" table which will have an "id" PK and a "id_code" as FK linking "campain" to "articles"
I'm not a SQL master but I know I can get tables names with
SELECT TABLE_NAME FROM INFORMATION_SCHEMA WHERE TABLE_NAME LIKE 'id%'
But I have really no idea about how to deal with the result (which is fine).
So how can I access to every tables named "idXXX" and insert every rows in the "campain" table + set "id_code" column to "XXX"?
Here is the procedure I saved (I didn't add every fields in the INSERT line for testing purpose) :
CREATE PROCEDURE JoinAllTables()
BEGIN
DECLARE done INT default 0;
DECLARE tableName CHAR(9);
DECLARE buffStr CHAR(7);
DECLARE buffId INT default 0;
DECLARE cur1 CURSOR FOR SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'id%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO tableName;
IF done THEN
LEAVE read_loop;
END IF;
SET buffStr = SUBSTRING(tableName, 3);
SET buffId = CAST(buffStr AS SIGNED);
set #sql = CONCAT("INSERT INTO campagnes(id, id_code) SELECT null, bufId FROM ",tableName); # Dynamically building sql statement
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur1;
END;
As u can see, I sub 'idXXXXX' to 'XXXXX' then CAST it AS INTEGER (SIGNED).
But I guess that in the "INSERT INTO" line, second tableName doesn't point to the variable. That's why I'm getting a
"#1446 - Tabble 'bddsoufflage.tablename'doesn't exist" Error :) Any idea ?
Edit: Updated answer
We can't have the tableName dynamically changed inside a prepared statement, so we must go through DynamicSQL to build the query using CONCAT, then compile the SQL with PREPARE, EXECUTE it and DEALLOCATE it.
DELIMITER //
CREATE PROCEDURE JoinAllTables()
BEGIN
DECLARE done INT default 0;
DECLARE tableName CHAR(9);
DECLARE buffStr CHAR(7);
DECLARE buffId INT default 0;
DECLARE cur1 CURSOR FOR SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'id%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO tableName;
IF done THEN
LEAVE read_loop;
END IF;
SET buffStr = SUBSTRING(tableName, 3);
SET buffId = CAST(buffStr AS SIGNED);
set #sql = CONCAT("INSERT INTO campagnes(id, id_code) SELECT null, ", buffId, " FROM ",tableName); # Dynamically building sql statement
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur1;
END; //
See also this answer MySQL Pass table name to cursor select
Old answer
The procedure should look something like this. Thanks Mchl for providing an Insert Into query example, I simply added it to the rest of the procedure.
DELIMITER //
CREATE PROCEDURE JoinAllTables()
BEGIN
DECLARE done INT default 0;
DECLARE tableName CHAR(7); # Variable to contain table names CHAr(7) is assuming id + 5Xs as characters.
DECLARE cur1 CURSOR FOR SELECT TABLE_NAME FROM INFORMATION_SCHEMA WHERE TABLE_NAME LIKE 'id%'; # Create a cursor to iterate over the tables
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO tableName;
IF done THEN
LEAVE read_loop;
END IF;
#Your Insert statement here, using tableName as a field.
INSERT INTO campain (id, id_code, otherfields) SELECT null, tableName, otherfields FROM tableName;
END LOOP;
CLOSE cur1;
END;//
Easiest way would be to run the information_schema query you have within some script (PHP,Python,Perl - whichever suits you best) and use it's results to create queries like:
INSERT INTO
campain (id, id_code, otherfields)
SELECT
null, 'idXXXX', otherfields FROM idXXXX