Select 3 results from every table - mysql

Assuming a database with a ridiculous amount of tables (200+), how can I perform SELECT * FROM <> LIMIT 3; where <> represents all the tables in the database? My goal is to get an idea of what each table contains, and the column names shown in DESCRIBE are not particularly useful. Therefore I would like to see 3 records from each table.
I know that I could easily script this in PHP by iterating over the output of show tables; however I am looking for a command to run on the MySQL interpreter (mysql> prompt).

It's described in detail under this link (haven't tried it myself though, it's just in my bookmarks):
http://www.youdidwhatwithtsql.com/mysql-clone-of-sp_msforeachtable/624
DELIMITER $$
DROP PROCEDURE IF EXISTS `usp_mysql_foreachtable`$$
CREATE PROCEDURE `usp_mysql_foreachtable`(IN sql_string VARCHAR(1000))
LANGUAGE SQL
NOT DETERMINISTIC
COMMENT 'Functional clone of sp_MsForEachTable'
BEGIN
DECLARE var_tablename VARCHAR(100);
DECLARE last_row BIT;
DECLARE table_cursor CURSOR FOR SELECT TABLE_NAME
FROM information_schema.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_SCHEMA = DATABASE();
DECLARE CONTINUE HANDLER FOR NOT FOUND SET last_row = 1;
OPEN table_cursor;
FETCH table_cursor INTO var_tablename;
SET last_row = 0;
SET #var = '';
lbl_table_cursor: LOOP
SET #qry = REPLACE(sql_string, '?', var_tablename);
PREPARE q FROM #qry;
EXECUTE q;
DEALLOCATE PREPARE q;
FETCH table_cursor INTO var_tablename;
IF last_row = 1 THEN
LEAVE lbl_table_cursor;
END IF;
END LOOP lbl_table_cursor;
CLOSE table_cursor;
END$$
DELIMITER ;
Then you call it with
CALL usp_mysql_foreachtable('SELECT * FROM ? LIMIT 3;');

Related

Running a query for each entry in a list of tables

I intend to write a procedure to run a query on each of the tables in a provided list (can be a comma separated list or a table - undecided on that yet)
I started off with creating a while loop to iterate through each element in the provided list. Have been able to extract each element but I don't know how to run a query for that extracted element/table.
DELIMITER $$
DROP PROCEDURE IF EXISTS retain_demo_clients$$
CREATE PROCEDURE retain_demo_clients()
BEGIN
DECLARE counter INT(10);
DECLARE client_tables VARCHAR(255);
DECLARE table_count INT(10);
DECLARE table_in_process VARCHAR(255);
SET counter = 1;
SET table_count = 3;
SET client_tables = 'client_table, somerandomstuff, somemorestuff';
WHILE (counter < table_count +1) DO
SET table_in_process = substring_index(substring_index(client_tables, ',',counter),',',-1);
SELECT table_in_process;
SET counter = counter +1;
END WHILE;
END$$
DELIMITER ;
CALL retain_demo_clients();
I expect to do something like 'select * from table_in_process'. Would also appreciate if there is a better way to loop through the list of tables.
Here is DBFiddle link, if someone wants to tinker: https://www.db-fiddle.com/f/v6EMsiWvXFrBoNLgoZwDVX/1
You can use EXECUTE to run a text that represent a single statement
SET #someQuery = CONCAT('SELECT * FROM ', table_in_process ) ;
PREPARE preparable_stmt FROM #someQuery;
EXECUTE preparable_stmt;

Drop a database if there is no table in it

Is there a MySQL command can do something like:
If there is nothing in a database, drop it. If there is any tables in it, do nothing.
Such as:
drop database if exists foobar
But:
drop database if empty foobar
Is there any way to do this?
As Barmar said you can use INFORMATION_SCHEMA.TABLES with stored procedure.
Here is my small effort:
DELIMITER //
CREATE PROCEDURE spDropDB_IF_Empty()
BEGIN
IF (SELECT COUNT(table_name) from INFORMATION_SCHEMA.tables
WHERE Table_Schema = 'mariadb')= 0 THEN
DROP DATABASE mariadb;
ELSE
SELECT 'There are tables in the mariaDB';
END IF;
END //
DELIMITER ;
Call SP:
CALL spDropDB_IF_Empty()
Hopefully this will help others as well. I just created a procedure for my own purpose after reading your question and commentators' comments.
use mysql;
-- switch to a different delimiter
delimiter $$
create procedure drop_empty_databases()
begin
declare table_schema varchar(200); -- will hold schema obtained from query
declare schema_end int default 0; -- temp variable that's set to 1 when reading of all schema is done
-- cursor that lists all schemas with no tables
declare cur cursor for
select s.schema_name
from information_schema.schemata s
left join information_schema.tables t on t.table_schema = s.schema_name
group by s.schema_name
having count(t.table_name) = 0;
-- set schema_end to 1 when we run out of schemas while looping
declare continue handler for not found set schema_end = 1;
open cur;
looper: loop
fetch cur into table_schema;
if schema_end = 1 then
leave looper;
end if;
set #sql = concat('drop database ', table_schema);
prepare stmt from #sql;
execute stmt;
end loop;
close cur;
end
$$
-- switch back to semi-colon delimiter
delimiter ;
Usage:
use mysql;
create database test123;
call drop_empty_databases(); -- test123 database should be gone after running this
Please test this on a non-production server and confirm that it does what you want it to do.

pass IN sql stament to run to a procedure in mysql

I want to create procedure to use limit option either insert into.. with select.. or create table as select.....
I want to use limit to insert only 500K records at a time so I am using while loop I set count3 this time as i am trying to insert 1.5mil records.
call proc1( with long sql statement......................)
DELIMITER //
CREATE PROCEDURE proc1(IN sqllines text)
BEGIN
DECLARE valFrom INT;
DECLARE valTo INT;
DECLARE count INt default 0;
SET #sqlin = sqllines;
SET valFrom = 0;
SET valTo = 500000;
SET #sql = ('#sqlin LIMIT valFrom , valTo');
WHILE count < 3
DO
PREPARE stmt from #sql;
EXECUTE stmt;
SET valFrom = valFrom+500000;
SET valTo = valTo+500000;
set count = count + 1;
END WHILE;
END;
//
DELIMITER ;
Sorry to say, but you cannot pass a variable into a dynamic-SQL statement in T-SQL.
You can find more information in a question that I myself posted here. The answer here has detailed solution on this.

mySQL drop tables with Wildcard using only SQL statement?

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.

Join all tables with same structure in one MYSQL

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