Database is mysql/mariaDB.
I have database designed to store monthly reports about something. and their names are (example): table1, table2, table3...
I want to create function/procedure that will create/recreate view that contains all tables union (union ALL).
Something like:
1. first select all table names from information schema.
SELECT TABLE_NAME from information_schema.`TABLES` where TABLE_NAME like 'table%'
then i would try to set it in some loop to use result set from first query.
But i have problem with first step where i try to merge only one fixed table + one from first query and it returns error to me.
i try:
select * from `table4`
UNION
SELECT * from (SELECT TABLE_NAME from information_schema.`TABLES`
where TABLE_NAME like 'table%' limit 1) as dd
it returns me error: The used SELECT statements have a different number of columns ,
but when i execute sub query i get 1 result with correct name of table, and when i set that name in from clause without sub query, it works.
Any idea why it is happening, and maybe some advice how to accomplish that dynamic union.
I think a little push will help you to the correct way of handling this problem.
First, as Tim Biegeleisen suggests, the way to proceed is to use dynamic SQL, this is your only avenue if the table names cannot be absolutely determined before you try to run the query.
Second, you are correct to think that you need to start by querying the information_schema.TABLE, which you should do using a CURSOR. The results from that query should then be used to build up a query string which you then PREPARE and EXECUTE.
Third, I take it that the error message you included in your post refers specifically to the running of that query and doesn't indicate that the monthly tables differ in any way. You can't do a UNION unless the results from each part return the same number of columns.
Fourth, because we are going to build the query dynamically, this has to done within a stored procedure, it's not possible to do it in a stored function.
There are good tutorials in the mysql docs for using CURSOR and PREPARE/EXECUTE, which you should read. The version I give below will be based on those examples. I'm assuming the only input parameter will be the schema name (in case you happen to have some similarly named tables in another database on the server).
DELIMITER //
DROP PROCEDURE IF EXISTS dyn_union //
CREATE PROCEDURE dyn_union(IN v_sname VARCHAR(64))
READS SQL DATA
BEGIN
-- NB the order of declaration for variables cursor
-- and handler must be strictly observed
DECLARE sname VARCHAR(64); -- variable the schema names
DECLARE tname VARCHAR(64); -- variable the table names
DECLARE done INT DEFAULT FALSE; -- cursor control variable
DECLARE cur1 CURSOR FOR
SELECT table_schema, table_name
FROM information_schema.TABLES
WHERE table_schema = v_sname
AND table_name LIKE 'table%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
SET #sql = ''; -- build the query string in this var
OPEN cur1;
read_loop: LOOP -- loop over the rows returned by cursor
FETCH cur1 INTO sname, tname; -- fetching the schema and table names
IF done THEN
LEAVE read_loop;
END IF;
IF #sql = '' THEN -- build the select statement
SET #sql := CONCAT('SELECT * FROM `', sname, '`.`', tname, '`');
ELSE
SET #sql := CONCAT(#sql, ' UNION ALL SELECT * FROM `', sname, '`.`', tname, '`');
END IF;
END LOOP;
CLOSE cur1;
select #sql;
PREPARE stmt FROM #sql; -- prepare and execute the dynamically
EXECUTE stmt; -- created query.
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
-- call the procedure
CALL dyn_union('your_db_name');
Related
In MySQL, I have a number of procedures which are more or less identical - they all perform the same (or very similar) operations, but they perform it on different tables.
I'd like to reduce these to one procedure, parameterized by table name, if possible. For example, suppose I wanted to execute a generic select:
SELECT * FROM TableFor("TableName")
Is this (or anything similar) possible in MySQL? Is it possible in any SQL dialect?
Per Tomva's Answer
A full example:
DROP PROCEDURE IF EXISTS example;
CREATE PROCEDURE example(IN tablename VARCHAR(1000)) BEGIN
SET #statement = CONCAT('SELECT * FROM ', #tablename);
PREPARE statement FROM #statement;
EXECUTE statement;
DEALLOCATE PREPARE statement;
END;
CALL example('tablename');
You can do this with a prepared statement.
It will be something along the lines of
SET #stat = CONCAT('SELECT * FROM ', #tab');
PREPARE stat1 FROM #stat;
EXECUTE stat1;
DEALLOCATE PREPARE stat1;
Dynamic SQL does not work in a function, so make a Stored Procedure from this, and you will be able to provide the table parameter.
I am going to assume you know what a stored procedure is (I hope you do otherwise my answer will be useless)
First create a table object in your procedure
declare #tablenames table(name varchar)
insert into #MonthsSale (name) values ('firsttable')
insert into #MonthsSale (name) values ('secondtable')
...
You can add this little line to suppress the rows affected messages:
SET NOCOUNT ON
Then create a cursor for this table and a variable to save your table name
DECLARE #TABLENAME VARCHAR
DECLARE tables_cursor CURSOR FOR SELECT name FROM #tablenames
Then loop through cursor and execute your code for each table name
OPEN Tables_cursor
FETCH NEXT FROM Tables_cursor INTO #Tablename
WHILE ##FETCH_STATUS = 0
BEGIN
YOUR CODE USING THE #Tablename
END
CLOSE Tables_cursor
DEALLOCATE Tables_cursor
I want a procedure that will search all columns for non keyboard ascii characters (Dec 16 to Dec 31 or DLE to US) and update the column by replacing them with a space ' ' or nothing ''.
I have a SELECT statement that is finding the rows I need to update, but I have to manually change all columns myself.
SELECT column_name
FROM table_name
WHERE column_name REGEXP '[[.DLE.]-[.US.]]'
Here's the UPDATE script for modifying the column values
UPDATE table
SET
column = replace(column,char(16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31), '')
I want those two to be fused together to a single function or stored procedure but I don't know how, since I'm just starting to learn MySQL.
Disclaimer
Between using REGEXP and CURSORs to loop through each table and column, these examples are not going to be lightning fast. The speed will obviously vary depending on your environment and I suggest testing them out on development before production
One column in one table
To search a single column on a single table, you basically had the UPDATE as you needed it.
UPDATE t1
SET
column_name = replace(column_name,
char(16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31), '')
WHERE column_name REGEXP '[[.DLE.]-[.US.]]'
All columns in one table
To do all columns in a table, you need to identify the table, then loop through the columns using a cursor
DELIMITER $$
CREATE PROCEDURE table_regexp_replace(in_table VARCHAR(128))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE search_column VARCHAR(64);
DECLARE cur1 CURSOR FOR
SELECT DISTINCT `COLUMN_NAME` FROM `information_schema`.`COLUMNS`
WHERE `TABLE_NAME` = in_table ORDER BY `ORDINAL_POSITION` ;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
read_loop: LOOP
-- Process the next column
FETCH cur1 INTO search_column;
-- If we're done, stop the loop
IF done THEN
LEAVE read_loop;
END IF;
-- Replace everything in this column matching the regexp
SET #new_query := CONCAT ('UPDATE ', in_table,
' SET `', search_column, '` = replace(', search_column,
', char(16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31), \'\')
WHERE ', search_column, ' REGEXP \'[[.DLE.]-[.US.]]\'') ;
PREPARE stmt FROM #new_query;
EXECUTE stmt ;
END LOOP;
CLOSE cur1;
END$$
DELIMITER ;
Then usage
CALL table_regexp_replace('my_table');
How it works
Looks convoluted, it's actually pretty straight forward.
We create a procedure with one parameter in_table which is used to specify the table to work with.
Setup a cursor that pulls the column names from the information_schema table, in their correct order
Loop through each of those columns, executing the manually created UPDATE statement against each one.
You'll notice anywhere in the UPDATE query that required quotes, they've had to be escaped using \.
\'[[.DLE.]-[.US.]]\'
All columns in all tables
You could then use this procedure in a loop through all tables, using a similar method to above. Below is how you'd pull all the table names from information_schema:
SELECT DISTINCT TABLE_NAME FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_database_name';
Seen a lot for dropping tables using a wildcard but not a direct SQL statement except this one:
http://azimyasin.wordpress.com/2007/08/11/mysql-dropping-multiple-tables/
It says:
SHOW TABLES LIKE ‘phpbb_%’;
then DROP TABLES, is there a neat way to combine this all into one SQL Statement?
You could use dynamic SQL to do it, inside a stored procedure. It'd look something like this (untested):
CREATE PROCEDURE drop_like (IN pattern VARCHAR(64))
BEGIN
DECLARE q tinytext;
DECLARE done INT DEFAULT FALSE;
DECLARE cur CURSOR FOR
SELECT CONCAT('DROP TABLE "', table_schema, '"."', table_name, '"')
FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_name LIKE pattern;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
drop_loop: LOOP
FETCH cur INTO q;
IF done THEN
LEAVE drop_loop;
END IF;
PREPARE stmt FROM #q;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur;
END;
Using dynamic SQL in a query, as per derobert's answer, is the only to do this with pure SQL (no app code).
I wrote a generalized procedure to do this sort of thing (run a query for every table in a database) that you can find here - to use it, you would just need to run this query:
CALL p_run_for_each_table('databasename', 'DROP TABLE `{?database}`.`{?table}`');
It works in essentially the same way as derobert's answer.
However, the writer of that blog post was probably expecting you to write app code to turn the names of tables into a single DROP statement.
To do this, you would iterate over the results of the SHOW TABLE in your code and build a single query like this:
DROP TABLE table1, table2, tablewhatever;
This can be achieved via stored procedure, for example:
CREATE DEFINER=`some_user`#`%` PROCEDURE `drop_tables`()
LANGUAGE SQL
NOT DETERMINISTIC
MODIFIES SQL DATA
SQL SECURITY DEFINER
COMMENT ''
BEGIN
#We need to declare a variable with default 0 to determine weather to continue the loop or exit the loop.
DECLARE done INT DEFAULT 0;
DECLARE archive_table_name VARCHAR(100);
#Select desired tables from `information_schema`
DECLARE cur CURSOR FOR
SELECT t.`TABLE_NAME` FROM information_schema.`TABLES` t WHERE t.`TABLE_NAME` LIKE 'some_table_name%'
AND t.CREATE_TIME BETWEEN DATE_SUB(NOW(), INTERVAL 9 MONTH) AND DATE_SUB(NOW(), INTERVAL 6 MONTH);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur;
read_loop: LOOP
#Fetch one record from CURSOR and set variable (if not found, then variable `done` will be set to 1 by continue handler)
FETCH cur INTO archive_table_name;
IF done THEN
LEAVE read_loop; #If done is set to 1, then exit the loop, else continue
END IF;
#Do your work
-- Create the truncate query
SET #s = CONCAT('DROP TABLE IF EXISTS ', archive_table_name);
-- Prepare, execute and deallocate the truncate query
PREPARE drop_statement FROM #s;
EXECUTE drop_statement;
DEALLOCATE PREPARE drop_statement;
END LOOP;
CLOSE cur; #Closing the cursor
END
Pay attention to the database user, which is creating/executing the stored routine: it must have appropriate credentials for executing/dropping tables.
I'm performing some database clean up and have noticed that there are a lot of columns that have both empty strings and NULL values in various columns.
Is it possible to write an SQL statement to update the empty strings to NULL for each column of each table in my database, except for the ones that do not allow NULL's?
I've looked at the information_schema.COLUMNS table and think that this might be the place to start.
It's not possible to do this with one simple SQL statement.
But you can do it using one statement for each column.
UPDATE TABLE SET COLUMN = NULL
WHERE LENGTH(COLUMN) = 0
or, if you want to null out the items that also have whitespace:
UPDATE TABLE SET COLUMN = NULL
WHERE LENGTH(TRIM(COLUMN)) = 0
I don't think it's possible within MySQL but certainly with a script language of your choice.
Start by getting all tables SHOW TABLES
Then for each table get the different columns and find out witch ones allow null, either with DESC TABLE, SHOW CREATE TABLE or SELECT * FROM information_schema.COLUMNS, take the one you rather parse
Then for each column that allows null run a normal update that changes "" to null.
Prepare to spend some time waiting :)
I figured out how to do this using a stored procedure. I'd definitely look at using a scripting language next time.
DROP PROCEDURE IF EXISTS settonull;
DELIMITER //
CREATE PROCEDURE settonull()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE _tablename VARCHAR(255);
DECLARE _columnname VARCHAR(255);
DECLARE cur1 CURSOR FOR SELECT
CONCAT(TABLE_SCHEMA, '.', TABLE_NAME) AS table_name,
COLUMN_NAME AS column_name
FROM information_schema.COLUMNS
WHERE IS_NULLABLE = 'YES'
AND TABLE_SCHEMA IN ('table1', 'table2', 'table3');
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO _tablename, _columnname;
IF done THEN
LEAVE read_loop;
END IF;
SET #s = CONCAT('UPDATE ', _tablename, ' SET ', _columnname, ' = NULL WHERE LENGTH(TRIM(', _columnname, ')) = 0' );
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur1;
END//
DELIMITER ;
CALL settonull();
I have a joomla mysql database with a table name prefix of "jos_" on all of my table names. But I would like to remove it from all of my tables. I understand how to rename each table, one at a time, but I have 600 tables. Is there an easy to run a sql query to do this.
If someone has a solution, could you please post the exact sql query I can use?
In phpmyadmin select all tables of your database.
From the dropdown 'With selected:' choose 'Replace table prefix'
Set from->to replacement.
DONE
You can generate the necessary statements with a single query:
select 'RENAME TABLE ' || table_name || ' TO ' || substr(table_name, 5) ||';'
from information_schema.tables
Save the output of that query to a file and you have all the statements you need.
Or if that returns 0s and 1s rather the statemenets, here's the version using concat instead:
select concat('RENAME TABLE ', concat(table_name, concat(' TO ', concat(substr(table_name, 5), ';'))))
from information_schema.tables;
You can create your own stored procedure to rename your tables, with that you don't need to open an external editor everything will be done on the server:
delimiter //
CREATE PROCEDURE rename_tables( IN db CHAR(255), IN srch CHAR(255), IN rplc CHAR(255) )
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE from_table CHAR(255);
DECLARE cur1 CURSOR FOR SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=db;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
read_loop: LOOP
IF done THEN
LEAVE read_loop;
END IF;
FETCH cur1 INTO from_table;
SET #to_table = REPLACE(from_table, srch, rplc);
IF from_table != #to_table THEN
SET #rename_query = CONCAT('RENAME TABLE ', db, '.', from_table, ' TO ', #to_table, ';');
PREPARE stmt FROM #rename_query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END LOOP;
CLOSE cur1;
END//
delimiter ;
Usage:
CALL rename_tables('test', 'jos_', '');
Update: This was my first MySQL stored procedure and I ran into the 6 years old bug #5967 which was quite annoying, your variable names must be different from the field names, because if they aren't you'll get NULL values in your variables.
So be aware of that if you decide to write a MySQL stored procedure.