MySQL - searching multiple schemas for particular value - mysql

I have a server with multiple schemas. I need to find specyfic email address. Every schema looks pretty much te same, they have same structure. What i wanted to do is to write a script to for displaying dbname and specyfic data related to given email . Heres my script, but it do not work:(
DELIMITER $$
#Drop procedure if exists SearchAllDb $$
Create procedure SearchAllDb()
BEGIN
DECLARE DB_NAME Varchar(50);
DECLARE done INT default FALSE;
DECLARE CURSOR_ALL_DB_NAMES CURSOR FOR
SELECT schema_name from information_schema.schemata
WHERE schema_name like 'itools_%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN CURSOR_ALL_DB_NAMES;
myloop: LOOP
FETCH CURSOR_ALL_DB_NAMES INTO DB_NAME;
IF done THEN LEAVE myloop;
END IF;
WHILE done != TRUE DO
SET #SQL = CONCAT('select DB_NAME, id, sn, name, email
from',DB_NAME,'.`open_cases` where email like 'xxx#yahoo.com'');
prepare stmt from #SQL;
execute stmt;
deallocate prepare stmt;
END WHILE;
CLOSE CURSOR_ALL_DB_NAMES;
END;
DELIMITER ;
I have something similar written in t-sql and it works...
DECLARE #DB_NAME VARCHAR(50);
DECLARE CURSOR_ALL_DB_NAMES CURSOR FOR
SELECT name FROM master.sys.databases
WHERE name like 'pickup-%' and name!= 'pickup-stored';
OPEN CURSOR_ALL_DB_NAMES;
FETCH NEXT FROM CURSOR_ALL_DB_NAMES INTO #DB_NAME
WHILE ##Fetch_Status = 0
BEGIN
Print #DB_NAME;
exec ('select '''+ #DB_NAME + ''' as db, id, sn, email from ['+
#DB_NAME+'].dbo.requests where email in (''xxx#live.com'')');
FETCH NEXT FROM CURSOR_ALL_DB_NAMES INTO #DB_NAME
END
CLOSE CURSOR_ALL_DB_NAMES;
DEALLOCATE CURSOR_ALL_DB_NAMES;

Looks like you were doing fine up until you introduced the WHILE.
THE WHILE is redundant since you are using a LOOP, you should remove those lines.
The LOOP doesn't have an END.
The CONCAT statement will treat the first use of DB_NAME as one of your column names and there are some quoting issues with the LIKE.
The ';' at the end of the procedure should be '$$' which you have specified as your DELIMITER.
I've taken a few liberties with your variable names but this tidied up version should illustrate the points.
DELIMITER $$
DROP PROCEDURE IF EXISTS SearchAllDb $$
CREATE PROCEDURE SearchAllDb()
BEGIN
DECLARE db_name VARCHAR(64);
DECLARE done INT DEFAULT FALSE;
DECLARE cur CURSOR FOR
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name LIKE 'itools_%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
myloop: LOOP
FETCH cur INTO db_name;
IF done THEN
LEAVE myloop;
END IF;
SET #SQL = CONCAT('SELECT ''', db_name, ''', id, sn, name, email
FROM `', db_name,'`.`open_cases`
WHERE email LIKE ''xxx#yahoo.com''');
PREPARE stmt FROM #SQL;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur;
END $$
DELIMITER ;
There's a very good CURSOR example in the documentation at https://dev.mysql.com/doc/refman/5.7/en/cursors.html which will help you with the structure of the procedure.

Related

Can't specify table name on mysql stored procedure

I am mass assigning new id numbers to things in the DB to make room for some stuff at the beginning of each table. I created a procedure that works, but when I try adding input parameters to allow scripting, it can't find the table
delimiter |
CREATE PROCEDURE changeID
( IN in_table_name varchar(64))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE a,b INT DEFAULT 800000;
DECLARE cur1 CURSOR FOR SELECT id FROM in_table_name ORDER BY id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO b;
IF done THEN
LEAVE read_loop;
END IF;
UPDATE in_table_name SET id = a + 1 where id = b;
SET a = a+1;
END LOOP;
CLOSE cur1;
END;
|
delimiter ;
When I run this using call changeID('users'), I get the error:
[Err] 1146 - Table 'databaseName.in_table_name' doesn't exist
I was hoping to loop through using a simple list of commands like this so it could run unattended instead of manually changing the in_table_name between each execution:
call changeID('users');
call changeID('appointments');
call changeID('groups');
You can't dynamically pass a table name in a query, however, you can concatenate a string and then execute it as a statement. You of course want to be careful and ensure that this data has been sanitized etc. I wasn't able to test this, but something to this effect should get you going.
...
END IF;
SET #Query = CONCAT('UPDATE ',in_table_name,' SET `id` = ',a+1,' WHERE `id`=',b);
PREPARE stmt FROM #Query;
EXECUTE stmt;
...
https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html
KChason got me started in the right direction, but I had to take it a little further to get the first part working from tips here: https://forums.mysql.com/read.php?98,138495,138908#msg-138908.
DROP PROCEDURE
IF EXISTS `workingversion`;
delimiter |
CREATE PROCEDURE `workingversion` (IN tableName VARCHAR(100))
BEGIN
DECLARE done INT DEFAULT 0 ;
DECLARE a,
b INT DEFAULT 800000 ;
DROP VIEW IF EXISTS v1;
SET #stmt_text = CONCAT("CREATE VIEW v1 AS SELECT id FROM ", tableName, " ORDER BY id") ;
PREPARE stmt
FROM
#stmt_text ; EXECUTE stmt ; DEALLOCATE PREPARE stmt ;
BEGIN
DECLARE cur1 CURSOR FOR SELECT * FROM v1 ;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000'
SET done = 1 ; OPEN cur1 ;
REPEAT
FETCH cur1 INTO b ;
IF NOT done THEN
SET #Query = CONCAT('UPDATE ',tableName,' SET `id` = ',a+1,' WHERE `id`=',b);
PREPARE stmt FROM #Query;
EXECUTE stmt;
SET a = a+1;
END
IF ; UNTIL done
END
REPEAT
; CLOSE cur1 ;
END ;
END

Using a variable to reference another database in SELECT statement

I am trying to cycle through all rows in a table that holds the database name for another database. This is what I have which compiles but doesn't work as MySQL takes the 'database' bit as the actual database name rather than the contents.
How can I change the following so that it takes the contents of the variable?
DROP PROCEDURE IF EXISTS CYCLE;
DELIMITER ;;
CREATE PROCEDURE CYCLE()
BEGIN
DECLARE data_name CHAR(255);
DECLARE done INT DEFAULT FALSE;
DECLARE cursor_i CURSOR FOR SELECT database FROM company;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursor_i;
read_loop: LOOP
FETCH cursor_i INTO database;
IF done THEN
LEAVE read_loop;
END IF;
SELECT * FROM database.table LIMIT 1;
END LOOP;
CLOSE cursor_i;
END;
;;
DELIMITER ;
Update: to be clear I know that 'database' is a reserved word. I have used that here in place of the actual variable name I have used.
You can't evaluate the content on a variable on a table select statement, in that case you must build a query in a string an execute:
DROP PROCEDURE IF EXISTS CYCLE;
DELIMITER ;;
CREATE PROCEDURE CYCLE()
BEGIN
DECLARE data_name CHAR(255); -- unused?
DECLARE done INT DEFAULT FALSE;
DECLARE cursor_i CURSOR FOR SELECT database_name FROM company;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursor_i;
read_loop: LOOP
FETCH cursor_i INTO data_name;
IF done THEN
LEAVE read_loop;
END IF;
-- now build the query before execute:
set #sql = concat('SELECT * FROM `',data_name,'`.`your_table` LIMIT 1');
-- and execute
prepare stmt from #sql;
execute stmt;
deallocate prepare stmt ;
END LOOP;
CLOSE cursor_i;
END;
;;
DELIMITER ;
*Would better not confusing us using variable names that causes more questions. #JustSaying

MYSQL variable with CURSOR and FETCH

I wanted to use table names and run a statement with the table name as variable. I used cursor/fetch but when I run a statement with the variable it is not using the value of the variable but just seems to use the variable_name itself. I have seen example with concat where another variable was defined but what if I just wanted to reference the table name in a COMMAND?
DELIMITER $$
DROP PROCEDURE IF EXISTS test $$
CREATE PROCEDURE test()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_table_name TEXT;
DECLARE cur1 CURSOR FOR
SELECT TABLE_NAME FROM information_schema.tables where table_schema = 'rt';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done= TRUE;
OPEN cur1;
myloop: loop
FETCH cur1 INTO v_table_name;
IF done THEN
LEAVE myloop;
END IF;
COMMAND table v_table_name;
END loop;
close cur1;
END $$
If by COMMAND you mean you want to use the value of a variable as an identifier in another SQL statement... you may be able to make use of a prepared statement (in the context of a MySQL stored program).
Reference: https://dev.mysql.com/doc/refman/5.6/en/sql-syntax-prepared-statements.html
As a trivial example of what that might look like:
SET #sql = CONCAT('select * from `',v_table_name,'` limit 1');
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
SET #sql = NULL;
Note that this approach is not safe from SQL Injection vulnerabilities.
If that's not what you are looking for, I'm at a loss. I don't understand what you are referring to as a COMMAND.

Execute a statement for every table in a database

I know this query for changing the charset and collation of mysql table.
alter table <some_table> convert to character set utf8 collate utf8_unicode_ci;
But I need a query for changing all the tables in a db. Is there any possible solutons.
I have written this procedure to execute statements for every table in a database:
DROP PROCEDURE IF EXISTS sp_forEveryTable;
DELIMITER $$
CREATE PROCEDURE sp_forEveryTable(IN p_schema varchar(50), IN p_stmt varchar(100))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_tbl varchar(50);
DECLARE cur CURSOR FOR SELECT table_name FROM information_schema.tables WHERE table_schema = p_schema;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO v_tbl;
IF done THEN
LEAVE read_loop;
END IF;
SET #sql := REPLACE(p_stmt, '?', v_tbl);
IF (UPPER(p_stmt) LIKE 'SELECT %') THEN
SET #sql := CONCAT('SELECT "', v_tbl, '", ', SUBSTRING(#sql FROM 7));
ELSE
SELECT v_tbl AS 'Execute statement for following table:';
END IF;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur;
END $$
DELIMITER ;
Use it like this:
CALL sp_forEveryTable('your_database_name', 'ALTER TABLE ? CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci');
To have the tables created in the future in this database have the utf8 character set and collation as default use the statement given in this answer.

Pass table name to select statement from cursor

How can I use values returned from Cursor as table names in mysql procedures ?
DECLARE cur CURSOR FOR
select table_name, column_name
from information_schema.columns
where table_schema = 'foo' and table_name like 'bar%';
OPEN cur;
loop1: LOOP
FETCH cur
INTO table_val, column_val;
IF no_more_rows THEN
CLOSE cur;
LEAVE loop1;
END IF;
update table_val SET column_val ...
This throws error that foo.table_val doesnt exist. How can I get the actual table name to be passed to the select statement ?
Change update table_val SET column_val ... into
SET #sql = CONCAT('UPDATE ', table_val, ' SET ', column_val, ' = whatever WHERE...');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Read more about it here.
But note that you can not parameterize table and column names. This only works with values.
I will also share my fix. hope it will help someone too.
-- set to "$$" as we have ";" inside of Procedure. MySQL will be confused.(is this part of Procedure or should I run it now?)
DELIMITER $$
CREATE PROCEDURE UpdateTable()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE _table_name CHAR(255);
DECLARE cur CURSOR FOR
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'db_name' AND table_type = "BASE TABLE";
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
My_loop: LOOP
FETCH cur INTO _table_name;
SET #my_table_name = _table_name;
IF done THEN
LEAVE My_loop;
END IF;
SET FOREIGN_KEY_CHECKS = 0;
SET #stmt = CONCAT('ALTER TABLE ', #my_table_name, ' CONVERT TO CHARACTER SET utf8;');
PREPARE stmt1 FROM #stmt;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
SET FOREIGN_KEY_CHECKS = 1;
END LOOP;
CLOSE cur;
END$$
-- set to normal. ";"
DELIMITER ;