Stored procedure - mysql

I've been googling to find an answer but can't find anything. I have a cursor statement that pulls the name of the tables that are present in the database.
THe goal is:
a stored procedure with 2 parameters, database1 and database2
comparing both databases and outputting the difference.
database names are tab/space delimited
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE db_tables VARCHAR(256);
DECLARE cursor1 CURSOR FOR
SELECT TABLE_NAME, TABLE_SCHEMA
FROM information_schema.tables
WHERE TABLE_SCHEMA = db1
AND TABLE_TYPE = 'BASE TABLE';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursor1;
FETCH cursor1 into db_tables;
WHILE done = FALSE DO
SET query1 = SELECT * FROM db1 WHERE table1 IN(table_name);
END WHILE;
CLOSE cursor1;
END

This uses the INFORMATION_SCHEMA.TABLES information
The Schema
create database db1;
create database db2;
create table db1.s1 (id int);
create table db1.s2 (id int);
create table db1.s3 (id int);
create table db2.s2 (id int);
create table db2.s3 (id int);
create table db2.s4 (id int);
The Query
select t1.table_name, 2 as 'not in this one'
from INFORMATION_SCHEMA.TABLES t1
where t1.table_schema='db1'
and not exists (select * from INFORMATION_SCHEMA.TABLES t2 where t2.table_schema='db2' and t2.table_name=t1.table_name)
union
select t1.table_name, 1 as 'not in this one'
from INFORMATION_SCHEMA.TABLES t1
where t1.table_schema='db2'
and not exists (select * from INFORMATION_SCHEMA.TABLES t2 where t2.table_schema='db1' and t2.table_name=t1.table_name)
The Results
+------------+-----------------+
| table_name | not in this one |
+------------+-----------------+
| s1 | 2 |
| s4 | 1 |
+------------+-----------------+
This means that table s1 is in database db1, but not in db2, and that table s4 is in the database db2, but not in db1.
Stored Proc
delimiter $$
create procedure showDBDiffInTableNames
( x1 varchar(40),x2 varchar(40) )
BEGIN
--
-- passed parameters, x1 is a string containing the name of a database
-- x2 is a string containing the name of another database
--
select t1.table_name, 2 as 'not in this one'
from INFORMATION_SCHEMA.TABLES t1
where t1.table_schema=x1
and not exists (select * from INFORMATION_SCHEMA.TABLES t2 where t2.table_schema=x2 and t2.table_name=t1.table_name)
union
select t1.table_name, 1 as 'not in this one'
from INFORMATION_SCHEMA.TABLES t1
where t1.table_schema=x2
and not exists (select * from INFORMATION_SCHEMA.TABLES t2 where t2.table_schema=x1 and t2.table_name=t1.table_name);
END
$$
DELIMITER ;
Test it:
call showDBDiffInTableNames('x1','x2');
same results
t1 and t2 are just table aliases. See the manual page here. From the manual page:
The following list describes general factors to take into account when
writing joins.
A table reference can be aliased using tbl_name AS alias_name or
tbl_name alias_name:
....
I almost never write a query without an alias if, knowing ahead of time, I am going after two or more tables. It cuts down on the typing. They are especially common in self-joins (to the same table). You need a way to differentiate which one you are dealing with to remove Ambiguous errors from queries. So that is why that alias is in there. Plus, you will note that the table is gone after twice.
There are two ways you can write it, as seen in the pink/peach block above.

Related

How to execute a stored statement in a stored procedure

I'm creating a function in mySQL that will update table metrics each time the any table has something inserted or deleted from it. So I've got my Table_metrics table, with elements table_name and row_count:
CREATE TABLE Table_metrics (
table_name VARCHAR(64),
row_count INTEGER DEFAULT 0 );
So any time something gets added to any of my tables (other than this one) in the database the matching row gets updated with the number of rows in that table.
To do this I've tried making a stored procedure:
CREATE PROCEDURE table_update_metric(IN tablename VARCHAR(64))
UPDATE Table_metrics
SET row_count=(SELECT COUNT(*) FROM tablename)
WHERE table_name = tablename;
This produces an error when I call the procedure call table_update_metric('Owners'); (Owners being a table in my database)
table DB.tablename doesn't exist
I've done a bit of digging into stored procedures, trying to figure out how they would work. I assume the issue is coming from the line `SELECT COUNT(*) FROM tablename), so I tried having a stored statement in the procedure:
CREATE PROCEDURE table_update_metric(IN TABLENAME VARCHAR(64))
SET #s = CONCAT('SELECT COUNT(*) FROM ', tablename)
UPDATE Table_metrics
SET row_count=EXECUTE #s
WHERE table_name = tablename;
I'm not really sure how to properly do a stored statement as I'm still relatively new to mySQL, but I believe that it's the way to go.
Can anyone offer any insight into this problem?
What you are trying to do is already provided in Mysql. Information Schema Tables stores this information for you :)
SELECT TABLE_NAME, TABLE_ROWS)
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = '{your_db}';
From what Somonare said, I created a table in my database that uses that information:
CREATE TABLE Table_metrics
SELECT TABLE_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'Sensors';
And this table will be updated with a trigger each time the tables in Sensors are updated.

How to delete all tables that have 1-2 rows

Im having a database which is over 60k tables and i want to delete all the tables that have 1 or 2 rows.
You can use the below code in SQL Server 2012. It will delete all the tables which is having row_count less than 3.
USE [YourDB]
GO
DECLARE #Max int, #Count int,#Table_Name Varchar(20)
SET #Max =0
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp
(
table_name sysname ,
row_count INT,
reserved_size VARCHAR(50),
data_size VARCHAR(50),
index_size VARCHAR(50),
unused_size VARCHAR(50)
)
IF OBJECT_ID('tempdb..#temp1') IS NOT NULL
DROP TABLE #temp1
CREATE TABLE #temp1
(
ID int IDENTITY(1,1),
table_name sysname ,
row_count INT
)
SET NOCOUNT ON
INSERT #temp
EXEC sp_msforeachtable 'sp_spaceused ''?'''
INSERT INTO #temp1
SELECT a.table_name,
a.row_count
FROM #temp a
INNER JOIN information_schema.columns b
ON a.table_name collate database_default
= b.table_name collate database_default
GROUP BY a.table_name, a.row_count
HAVING a.row_count <3
SET #Count =(SELECT COUNT(*) FROM #temp1)
WHILE #Count > #Max
BEGIN
SET #Max = #Max +1
SET #Table_Name = (SELECT table_name FROM #temp1 WHERE ID = #Max)
EXEC('DROP TABLE ' +#Table_Name)
END
Use a MySQL-GUI, order by number of rows and drop all tables with 1-2 rows. it is as easy as deleting files in a windows folder. this
would take ~10 seconds + sort and drop time
or
Select table names of tables with 1-2 rows from information_schemas, load into an excel file and build your drop statements. takes around 2-5 minutes + drop time
or
Build a stored procedure that uses a Cursor to parse all the relevant table names into variables of your drop statement (like Hansa mentioned). this makes sense if you want to repeat your process from time to time. takes around 1-12 hours for beginners (depending on knowledge level) + drop time
Since logging in on SO probably took more time than solution 1 would,
i would recommend that solution.
In case you want to spend more time , the following query will show all table names for tables with 1-2 rows:
SELECT table_schema, table_name From information_schema.tables
WHERE table_schema='your_schema' # use your table_schema here
AND table_rows BETWEEN 1 and 2 ;
I would do it within some application side logic, using a programming language of your choice (which offers a mysql API).
Get all table names, described here, grouping them by table names
e.g. this could look something like:
SELECT COUNT(table_rows), table_name
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = '{your_db}'
GROUP BY table_name;
(this is not tested, but to give you a basic idea...)
Execute a delete statement for the tables with rowcount 2 or lower. Should be an easy task as soon as you got the results in your programming language (btw SQL statement to delete them is "DROP TABLE")
Note: I think it should be also possible using a SQL cursor, but as I said I would prefer the above solution.
You can try this.
First take all the table names from the database.
After getting all the table names put them in a array and execute a foreach loop that checks the num of rows for each table, something like this
$tables = array();
foreach ($tables as $table_name){
$query = "select * from '$table_name'";
$result = mysqli_query($dbCon, $query);
$num_rows = mysql_num_rows($result);
if($num_rows < 3){
$delete = "drop table $table_name";
$res_del = mysqli_query($dbCon, $delete);
echo $table_name." Deleted";
}
}
You probably cant do this with 1 SQL Statement unless you are using stored procedures (see below).
You will need to select all tables in MySQL with a programming language (e.g. Java with JDBC) using the following statement:
select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_ROWS <= 2 AND TABLE_ROWS >= 1
Then you can use the results in the programming languge to issue drop Statements for each result record in a loop:
drop table <tablename>
For doing this with a stored procedure (i.e. without a programming language)
see the third comment on this page: MySQL Reference Drop Table.
Of course you will need to adapt this to your needs because this example does not select tables by their row numbers but by their names.

How I can use the result of SELECT statement in ALTER TABLE in MySQL?

I have a table with some properties. I would like use these properties to create some columns in another table (ALTER TABLE).
The follow SELECT statement result in multiple results:
SELECT property FROM table1 GROUP BY property;
Result example:
firstName
lastName
address
phone
For each result of select statement above, perform:
ALTER TABLE table2 ADD property boolean null;
How do?
Here is an example for postgres. With little modification you can use it for mysql too.
DO $$
DECLARE row_results record;
BEGIN FOR row_results IN
SELECT column_name
FROM information_schema.columns
WHERE table_schema='public'
AND table_type='BASE TABLE'
AND table_name = 'table1'
LOOP
EXECUTE format('ALTER TABLE table2 ADD COLUMN %I boolean NULL', row_results.column_name);
END LOOP;
END$$;

Unioning from a variable number of tables. (Overloaded stored MySQL Procedure)

I have a 10 tables with the same schema. I'm trying to create an overloaded stored procedure so that I can union a bunch of tables together with simple selects (SELECT * FROM tableX). If each table has 1000 (different) rows, then I want to create a stored procedure where the following would happen:
CALL getRowsByNum(table1); -> 1000 rows
CALL getRowsByNum(table1, table2, table4); -> 3000 rows
...etc.
I got part of the way through writing 10 overloaded procedures that would SELECT * FROM X UNION ALL SELECT * FROM X UNION ALL..... etc, but that's really madness.
Anyone have a different suggestion? This silly setup is the result of an architectural decision made a while ago.
Thanks!
I agree that you have to rethink your db structure.
By the way, just for fun :)
drop database if exists my_test;
create database my_test;
use my_test;
create table table1 (
id int not null auto_increment primary key,
my_field varchar(10)
) engine = myisam;
create table table2 like table1;
create table table3 like table1;
create table table4 like table1;
create table table5 like table1;
insert into table1 (my_field) values ('aaa'),('bbb');
insert into table2 (my_field) values ('ccc'),('ddd'),('eee');
insert into table3 (my_field) values ('fff'),('ggg');
insert into table4 (my_field) values ('hhh'),('iii'),('jjj');
insert into table5 (my_field) values ('kkk'),('lll');
delimiter //
drop procedure if exists tables_union //
create procedure tables_union (in str varchar(10000),in db varchar(100))
begin
set #qry = (select group_concat(concat('select * from ',table_name) separator ' union all ')
from information_schema.tables
where find_in_set(table_name,str)
and table_schema = db);
-- select #qry;
prepare stmt from #qry;
execute stmt;
deallocate prepare stmt;
end; //
delimiter ;
call tables_union('table4,table3,table1','my_test');
+----+----------+
| id | my_field |
+----+----------+
| 1 | aaa |
| 2 | bbb |
| 1 | fff |
| 2 | ggg |
| 1 | hhh |
| 2 | iii |
| 3 | jjj |
+----+----------+
7 rows in set (0.00 sec)
Not really. You can't have variable number of parameters or procedures with the same name.
I dont think there is an easy solution to your problem.
EDIT: I've thought of an really ugly solution but I'll leave it to you if you wanna use it. This is untested pseudo code only.
Create a proc where you take a varchar in, long enough to hold the value "table1,table2,table3,..." and so on for as many tables you like to union at most. (could be another identifier of course)
Write all tables in union. Since they are the same just use * to save time and space
delimiter //
create procedure megaunion (tables varchar(255))
begin
select * from table1 where find_in_set('table1', tables)
union
select * from table2 where find_in_set('table2', tables)
....
end//
list all your tables. At least you don't have to list the possible permutations and the user of the procedure won't know how you did it :)

Using ALTER to drop a column if it exists in MySQL

How can ALTER be used to drop a column in a MySQL table if that column exists?
I know I can use ALTER TABLE my_table DROP COLUMN my_column, but that will throw an error if my_column does not exist. Is there alternative syntax for dropping the column conditionally?
I'm using MySQL version 4.0.18.
For MySQL, there is none: MySQL Feature Request.
Allowing this is arguably a really bad idea, anyway: IF EXISTS indicates that you're running destructive operations on a database with (to you) unknown structure. There may be situations where this is acceptable for quick-and-dirty local work, but if you're tempted to run such a statement against production data (in a migration etc.), you're playing with fire.
But if you insist, it's not difficult to simply check for existence first in the client, or to catch the error.
MariaDB also supports the following starting with 10.0.2:
DROP [COLUMN] [IF EXISTS] col_name
i. e.
ALTER TABLE my_table DROP IF EXISTS my_column;
But it's arguably a bad idea to rely on a non-standard feature supported by only one of several forks of MySQL.
There is no language level support for this in MySQL. Here is a work-around involving MySQL information_schema meta-data in 5.0+, but it won't address your issue in 4.0.18.
drop procedure if exists schema_change;
delimiter ';;'
create procedure schema_change() begin
/* delete columns if they exist */
if exists (select * from information_schema.columns where table_schema = schema() and table_name = 'table1' and column_name = 'column1') then
alter table table1 drop column `column1`;
end if;
if exists (select * from information_schema.columns where table_schema = schema() and table_name = 'table1' and column_name = 'column2') then
alter table table1 drop column `column2`;
end if;
/* add columns */
alter table table1 add column `column1` varchar(255) NULL;
alter table table1 add column `column2` varchar(255) NULL;
end;;
delimiter ';'
call schema_change();
drop procedure if exists schema_change;
I wrote some more detailed information in a blog post.
I know this is an old thread, but there is a simple way to handle this requirement without using stored procedures. This may help someone.
set #exist_Check := (
select count(*) from information_schema.columns
where TABLE_NAME='YOUR_TABLE'
and COLUMN_NAME='YOUR_COLUMN'
and TABLE_SCHEMA=database()
) ;
set #sqlstmt := if(#exist_Check>0,'alter table YOUR_TABLE drop column YOUR_COLUMN', 'select ''''') ;
prepare stmt from #sqlstmt ;
execute stmt ;
Hope this helps someone, as it did me (after a lot of trial and error).
I just built a reusable procedure that can help making DROP COLUMN idempotent:
-- column_exists:
DROP FUNCTION IF EXISTS column_exists;
DELIMITER $$
CREATE FUNCTION column_exists(
tname VARCHAR(64),
cname VARCHAR(64)
)
RETURNS BOOLEAN
READS SQL DATA
BEGIN
RETURN 0 < (SELECT COUNT(*)
FROM `INFORMATION_SCHEMA`.`COLUMNS`
WHERE `TABLE_SCHEMA` = SCHEMA()
AND `TABLE_NAME` = tname
AND `COLUMN_NAME` = cname);
END $$
DELIMITER ;
-- drop_column_if_exists:
DROP PROCEDURE IF EXISTS drop_column_if_exists;
DELIMITER $$
CREATE PROCEDURE drop_column_if_exists(
tname VARCHAR(64),
cname VARCHAR(64)
)
BEGIN
IF column_exists(tname, cname)
THEN
SET #drop_column_if_exists = CONCAT('ALTER TABLE `', tname, '` DROP COLUMN `', cname, '`');
PREPARE drop_query FROM #drop_column_if_exists;
EXECUTE drop_query;
END IF;
END $$
DELIMITER ;
Usage:
CALL drop_column_if_exists('my_table', 'my_column');
Example:
SELECT column_exists('my_table', 'my_column'); -- 1
CALL drop_column_if_exists('my_table', 'my_column'); -- success
SELECT column_exists('my_table', 'my_column'); -- 0
CALL drop_column_if_exists('my_table', 'my_column'); -- success
SELECT column_exists('my_table', 'my_column'); -- 0
Chase Seibert's answer works, but I'd add that if you have several schemata you want to alter the SELECT thus:
select * from information_schema.columns where table_schema in (select schema()) and table_name=...
You can use this script, use your column, schema and table name
IF EXISTS (SELECT *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TableName' AND COLUMN_NAME = 'ColumnName'
AND TABLE_SCHEMA = SchemaName)
BEGIN
ALTER TABLE TableName DROP COLUMN ColumnName;
END;
Perhaps the simplest way to solve this (that will work) is:
CREATE new_table AS SELECT id, col1, col2, ... (only the columns you actually want in the final table)
FROM my_table;
RENAME my_table TO old_table, new_table TO my_table;
DROP old_table;
Or keep old_table for a rollback if needed.
This will work but foreign keys will not be moved. You would have to re-add them to my_table later; also foreign keys in other tables that reference my_table will have to be fixed (pointed to the new my_table).
Good Luck...
I realise this thread is quite old now, but I was having the same problem.
This was my very basic solution using the MySQL Workbench, but it worked fine...
get a new sql editor and execute SHOW TABLES to get a list of your tables
select all of the rows, and choose copy to clipboard (unquoted) from the context menu
paste the list of names into another editor tab
write your query, ie ALTER TABLE x DROP a;
do some copying and pasting, so you end up with separate query for each table
Toggle whether the workbench should stop when an error occurs
Hit execute and look through the output log
any tables which had the table now haven't
any tables which didn't will have shown an error in the logs
then you can find/replace 'drop a' change it to 'ADD COLUMN b INT NULL' etc and run the whole thing again....
a bit clunky, but at last you get the end result and you can control/monitor the whole process and remember to save you sql scripts in case you need them again.