Extract data from multiple tables (mysql) - mysql

My problem: I have a few tables in mysql database with column "URL". I want to extract all of URLs to text file or another table.
Is it good way to go?
DELIMITER $$
CREATE or replace PROCEDURE link()
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE tablename varchar(1000) DEFAULT "";
DECLARE link_tables
CURSOR for
SELECT table_name FROM information_schema.columns WHERE table_name like '__baza%' and COLUMN_NAME like 'url';
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
OPEN link_tables;
getTable: LOOP
fetch link_tables into tablename;
IF finished = 1 THEN s
LEAVE getTable;
END IF;
select url from tablename into ... <<<< is it good idea? what to do next?
END LOOP getTable;
close link_tables;
END$$
DELIMITER ;
CALL link();

CREATE PROCEDURE link_urls (prefix VARCHAR(64))
BEGIN
SELECT GROUP_CONCAT( CONCAT( 'SELECT url FROM ', table_name) SEPARATOR ' UNION ')
INTO #sql
FROM information_schema.columns
WHERE table_name like CONCAT(prefix, '%');
SET #sql = CONCAT( 'CREATE TABLE urls ', #sql );
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END
fiddle

Related

How to convert mysql/mariadb row in triggers (NEW/OLD) into json

how i can convert a row inside a mysql/mariadb trigger into an json object with new JSON features?
BEGIN
CALL my_audit_insert(tableName, id, ... JSON_OBJECT(NEW) ...);
END
Is there any possibility to get programatically columns of NEW or OLD?
First Try - Create a Statement
Idea is to get colums from system tables and get each value from NEW/OLD programatically
BEGIN
SET #s = 'SELECT NEW.? INTO #result';
PREPARE stmt FROM #s;
SET #a = 'id';
EXECUTE stmt USING #a;
CALL audit_insert(NEW.id, 'pages', JSON_ARRAY(result));
END
(1336): Dynamic SQL is not allowed in stored function or trigger
Second Idea - Select the row via PrimaryKey as JSON_Object in after-triggers
procedure spGetJson from https://stackoverflow.com/a/35957518/7080961
DROP PROCEDURE IF EXISTS `spGetJson`;
DELIMITER //
CREATE DEFINER=`root`#`%` PROCEDURE `spGetJson`(pTableName varchar(45), pId int, out pJson JSON)
begin
select group_concat(concat("'", COLUMN_NAME, "', ", COLUMN_NAME) separator ',')
into #cols
from information_schema.columns
where TABLE_NAME = pTableName and TABLE_SCHEMA = database();
set #q = concat('select json_object(', #cols, ') INTO #a from ', pTableName);
if pId is not null then
set #q = concat(#q, ' where id = ', pId);
end if;
set #q = concat(#q, ';');
prepare statement from #q;
execute statement;
deallocate prepare statement;
SET pJson = #a;
end//
DELIMITER;
After Insert Trigger:
BEGIN
CALL spGetJson('pages', NEW.id, #a);
CALL audit_insert(NEW.id, 'pages', #a);
END
same: (1336): Dynamic SQL is not allowed in stored function or trigger
Conclusion:
have to wait for this feature: https://bugs.mysql.com/bug.php?id=89366
or switch to postresql

Error Code: 1111. Invalid use of group function inside of a stored procedure to create triggers automatically

I'm trying to create a stored procedure that would create triggers automatically for all the tables that exist in my Database.
I came up with the following code but I got this error when I run it:
Error Code: 1111. Invalid use of group function
DELIMITER $$
DROP PROCEDURE IF EXISTS procCountAllTables $$
CREATE PROCEDURE procCountAllTables()
BEGIN
DECLARE table_name VARCHAR(255);
DECLARE end_of_tables INT DEFAULT 0;
# DECLARE column_name VARCHAR(255);
DECLARE cur CURSOR FOR
SELECT t.table_name
FROM information_schema.tables t
WHERE t.table_schema = DATABASE() AND t.table_type='BASE TABLE';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET end_of_tables = 1;
OPEN cur;
tables_loop: LOOP
FETCH cur INTO table_name;
IF end_of_tables = 1 THEN
LEAVE tables_loop;
END IF;
# SET #s = CONCAT('SELECT ''', table_name, ''', COUNT(*) AS Count FROM ' , table_name);
SET #s = CONCAT('DROP TRIGGER IF EXISTS auditemployees_insert;
CREATE TRIGGER audit',
table_name,
'_insert AFTER INSERT ON ',
table_name,
'
FOR EACH ROW
BEGIN
INSERT INTO ',
table_name,
'_trigger (',
GROUP_CONCAT(CONCAT('`', column_name, '`')
SEPARATOR ','),
') SELECT ',
GROUP_CONCAT(CONCAT('`', column_name, '`')
SEPARATOR ','),
' FROM ',
table_name,
' WHERE id = NEW.id;
END$$');
PREPARE stmt FROM #s;
EXECUTE stmt;
END LOOP;
CLOSE cur;
END $$
DELIMITER ;
Any ideas how to correct my error?
Yo, please do try this one out, mate:
DELIMITER $$
DROP PROCEDURE IF EXISTS procCountAllTables $$
CREATE PROCEDURE procCountAllTables()
BEGIN
DECLARE table_name VARCHAR(255);
DECLARE end_of_tables INT DEFAULT 0;
-- DECLARE column_name VARCHAR(255);
DECLARE cur CURSOR FOR
SELECT t.table_name
FROM information_schema.tables t
WHERE t.table_schema = DATABASE() AND t.table_type='BASE TABLE';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET end_of_tables = 1;
OPEN cur;
tables_loop: LOOP
FETCH cur INTO table_name;
IF end_of_tables = 1 THEN
LEAVE tables_loop;
END IF;
-- SET #s = CONCAT('SELECT ''', table_name, ''', COUNT(*) AS Count FROM ' , table_name);
SET #s = CONCAT(
'DELIMITER $$',
'DROP TRIGGER IF EXISTS auditemployees_insert; CREATE TRIGGER audit',
table_name,
'_insert AFTER INSERT ON ',
table_name,
'FOR EACH ROW BEGIN INSERT INTO ',
table_name,
'_trigger (',
"GROUP_CONCAT(CONCAT('`', column_name, '`') SEPARATOR ',')",
') SELECT ',
"GROUP_CONCAT(CONCAT('`', column_name, '`') SEPARATOR ',')",
' FROM ',
table_name,
"WHERE id = NEW.id;"
"END$$"
);
PREPARE stmt FROM #s;
EXECUTE stmt;
END LOOP;
CLOSE cur;
END $$
DELIMITER ;
Notes:
This type/style of implementation is not really recommended, especially if you will put this into a production level of environment
This will provide output/expected outcome but can sacrifice maintainability due to improper implementation
I like the idea of making a trigger through a stored procedure, but somehow I am against it
The only error I can see is the proper usage of ' and "
Proper indentation helps, trust me
And the commented line(s), you know what to do with those
Anyway, cheers
You're missing a query and then trying to fit it into the construction of the trigger code string. For each table, you need to
SELECT GROUP_CONCAT(CONCAT('`', column_name, '`') SEPARATOR ',') INTO #columnsList FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = [the current table name]
;
You could also just join to INFORMATION_SCHEMA.COLUMNS (on table_schema and table_name) in your cursor query, and have two fields; table_name and columns_list.
Edit: Also, I am not sure how much prepared statements accept multiple statements; so you may want to prepare and execute the preliminary DROP of each trigger separately from it's (re)creation.
Also:
I am not sure what purpose you had in mind for the COUNT query.
You may want to filter table names ending in _trigger from your cursor.
Edit: Something like this... (untested, so I might have typos or other similar oversights)
DELIMITER $$
DROP PROCEDURE IF EXISTS procCountAllTables $$
CREATE PROCEDURE procCountAllTables()
BEGIN
DECLARE table_name VARCHAR(255);
DECLARE trigger_name VARCHAR(255);
DECLARE target_tablename VARCHAR(255);
DECLARE end_of_tables INT DEFAULT 0;
DECLARE column_names VARCHAR(1024);
-- Be aware of GROUP_CONCAT's configured length limitation
DECLARE cur CURSOR FOR
SELECT t.table_name
, GROUP_CONCAT(CONCAT("`",c.column_name,"`")) AS column_names
FROM information_schema.tables AS t
INNER JOIN information_schema.columns AS c
USING (table_schema, table_name)
WHERE t.table_schema = DATABASE() AND t.table_type='BASE TABLE'
GROUP BY t.table_name
;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET end_of_tables = 1;
OPEN cur;
tables_loop: LOOP
FETCH cur INTO table_name, column_names;
IF end_of_tables = 1 THEN
LEAVE tables_loop;
END IF;
SET target_tablename := CONCAT(table_name, '_trigger');
SET trigger_name := CONCAT('audit', table_name, '_insert');
SET #s := CONCAT("DROP TRIGGER IF EXISTS `", trigger_name, "`;");
PREPARE stmt FROM #s;
EXECUTE stmt;
SET #s := CONCAT(
"CREATE TRIGGER `", trigger_name, "` "
"AFTER INSERT ON `", table_name, "` "
"FOR EACH ROW "
"INSERT INTO `", target_tablename, "` (", column_names, ") "
"SELECT ", column_names, " "
"FROM `", table_name, "` "
"WHERE id = NEW.id "
";"
;
PREPARE stmt FROM #s;
EXECUTE stmt;
END LOOP;
CLOSE cur;
END $$
DELIMITER ;
Note that I've removed the BEGIN and END from the trigger definition. Since the trigger itself executes only a single statement, I think they are not necessary; and in my limited experience with these specific kinds of tasks, a delimiter override (which was not actually done on the prepare statement) tends to confuse prepared statements.
Also, you can probably get away without even using a select in the trigger if the cursor is changed like so:
SELECT t.table_name
, GROUP_CONCAT(CONCAT("`", column_name, "`")) AS column_names
, GROUP_CONCAT(CONCAT("NEW.`", column_name, "`")) AS source_list
...
and the trigger insert changed like so:
...
"INSERT INTO `", target_tablename, "` (", column_names, ") "
"VALUES (", source_list, ") "
";"
of course, you'll need to fetch the source_list from the cursor into a source_list local variable.

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.

How to get example values from every column in a schema

I'm reverse-engineering a MySQL database and I'd like to get a list of example values from every column in every table. I'd like to run a query like this:
select
table_name,
column_name,
group_concat(
(select distinct table_name.column_name limit 100)
separator ','
) as examples
from
information_schema.columns
where
table_schema = 'myschema'
;
I'd like the output to look something like this:
table1 column1 (123,124,234)
table1 column2 ('Joe','Sara','Bob')
MySQL won't accept table_name.column_name as valid syntax. What's the right way to write this query?
I think Sam, you are looking for something like that, or at least it would be a better approach:
select
table_name,
column_name,
group_concat((column_name) separator ',') as examples
from
information_schema.columns
where
table_schema = 'test'
GROUP BY table_name
;
Based on rene's suggestion, I wrote a stored procedure which outputs examples of values from each column in every table. It's ugly and slow, but it works. I'd welcome suggestions on how to improve this code.
DELIMITER //
CREATE PROCEDURE column_example_values(
IN db_name VARCHAR(64),
IN tbl VARCHAR(64),
IN col VARCHAR(64),
OUT result MEDIUMTEXT)
BEGIN
SET #s = CONCAT('SELECT GROUP_CONCAT(tbl1.',col,
' separator \',\') FROM (SELECT DISTINCT ',
col,' FROM ',db_name,'.',tbl,
' LIMIT 100) tbl1 INTO #result1');
PREPARE stmt FROM #s;
EXECUTE stmt;
SET result = IFNULL(#result1,'');
END;
//
DELIMITER ;
DELIMITER //
CREATE PROCEDURE all_columns_example_values(IN db_name VARCHAR(64))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE tbl, col VARCHAR(64);
DECLARE cur1 CURSOR FOR
SELECT
table_name,
column_name
FROM
information_schema.columns
WHERE
table_schema = db_name
ORDER BY
table_name,
column_name;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
CREATE TEMPORARY TABLE results (
tbl VARCHAR(64), col VARCHAR(64), examples MEDIUMTEXT);
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO tbl, col;
IF done THEN
LEAVE read_loop;
END IF;
CALL column_example_values(db_name,tbl,col,#result);
INSERT INTO results (tbl, col, examples) VALUES (tbl, col, #result);
END LOOP;
CLOSE cur1;
SELECT * FROM results;
DROP TABLE results;
END;
//
DELIMITER ;
It can be called with
CALL all_columns_example_values('mydb');

Querying multiple databases at once

I have WordPress instances with each in its own database. For an update I need to query all active plugins, that are stored in a table 'wp_options' and accessible via
WHERE option_name='active_plugins'
How can I access all active plugin settings (spread over multiple databases) and output them in one single SQL result? I know the database.tablename syntax, but how do I go on from there with the above Where statement?
A request in a single database would look like this:
SELECT option_value
FROM `database`.`wp_options`
WHERE option_name="active_plugins"
SELECT option_value
FROM `database1`.`wp_options`
WHERE option_name="active_plugins"
UNION
SELECT option_value
FROM `database2`.`wp_options`
WHERE option_name="active_plugins"
The solution by Pentium10 is good but its drawback is that you have to extend the query for every schema to be included. The below solution uses a prepared statement to produce a result set for all schemas on your MySQL server which have the wp_options table. This should be more convenient for you.
DROP PROCEDURE IF EXISTS `MultipleSchemaQuery`;
DELIMITER $$
CREATE PROCEDURE `MultipleSchemaQuery`()
BEGIN
declare scName varchar(250);
declare q varchar(2000);
DROP TABLE IF EXISTS ResultSet;
create temporary table ResultSet (
option_value varchar(200)
);
DROP TABLE IF EXISTS MySchemaNames;
create temporary table MySchemaNames (
schemaName varchar(250)
);
insert into MySchemaNames
SELECT distinct
TABLE_SCHEMA as SchemaName
FROM
`information_schema`.`TABLES`
where
TABLE_NAME = 'wp_options';
label1:
LOOP
set scName = (select schemaName from MySchemaNames limit 1);
set #q = concat('select option_value from ', scName, '.wp_options where option_name=\'active_plugins\'');
PREPARE stmt1 FROM #q;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
delete from MySchemaNames where schemaName = scName;
IF ((select count(*) from MySchemaNames) > 0) THEN
ITERATE label1;
END IF;
LEAVE label1;
END LOOP label1;
SELECT * FROM ResultSet;
DROP TABLE IF EXISTS MySchemaNames;
DROP TABLE IF EXISTS ResultSet;
END
$$
DELIMITER ;
CALL MultipleSchemaQuery();
Gruber's answer works great, but it has a syntax error --- there's a spurious comma at the end of line 10. Here is the code, with syntax error fixed:
DELIMITER $$
CREATE PROCEDURE `MultipleSchemaQuery`()
BEGIN
declare scName varchar(250);
declare q varchar(2000);
DROP TABLE IF EXISTS ResultSet;
create temporary table ResultSet (
option_value varchar(200)
);
DROP TABLE IF EXISTS MySchemaNames;
create temporary table MySchemaNames (
schemaName varchar(250)
);
insert into MySchemaNames
SELECT distinct
TABLE_SCHEMA as SchemaName
FROM
`information_schema`.`TABLES`
where
TABLE_NAME = 'wp_options';
label1:
LOOP
set scName = (select schemaName from MySchemaNames limit 1);
set #q = concat('select option_value from ', scName, '.wp_options where option_name=\'active_plugins\'');
PREPARE stmt1 FROM #q;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
delete from MySchemaNames where schemaName = scName;
IF ((select count(*) from MySchemaNames) > 0) THEN
ITERATE label1;
END IF;
LEAVE label1;
END LOOP label1;
SELECT * FROM ResultSet;
DROP TABLE IF EXISTS MySchemaNames;
DROP TABLE IF EXISTS ResultSet;
END
$$
Yet another example of querying multiple databases using procedure, cursor, union all and prepared statement. Does not require drop and delete permissions:
USE `my_db`;
DROP PROCEDURE IF EXISTS `CountAll`;
DELIMITER $$
CREATE PROCEDURE `CountAll`(IN tableName VARCHAR(255))
BEGIN
DECLARE db_name VARCHAR(250);
DECLARE exit_loop BOOLEAN;
DECLARE union_query TEXT DEFAULT '';
DECLARE my_databases CURSOR FOR
SELECT DISTINCT `table_schema`
FROM `information_schema`.`tables`
WHERE
`table_schema` LIKE 'myprefix\_%' AND
`table_name` = tableName;
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET exit_loop = TRUE;
OPEN my_databases;
get_database: LOOP
FETCH my_databases INTO db_name;
IF exit_loop THEN
-- remove trailing UNION ALL statement
SET union_query = TRIM(TRAILING ' UNION ALL ' FROM union_query);
LEAVE get_database;
END IF;
SET union_query = concat(union_query, 'SELECT COUNT(*) AS qty FROM ',
db_name, '.', tableName, ' UNION ALL ');
END LOOP get_database;
CLOSE my_databases;
SET #final_query = concat('SELECT SUM(qty) FROM (', union_query,
') AS total;');
PREPARE stmt1 FROM #final_query;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END$$
DELIMITER ;
CALL CountAll('wp_options');