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 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
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.
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.
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');
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');