can we run code in mysql without creating a procedure - mysql

I want to create a sql file for my dba that will check whether index exists on a table. IF doesn't exist-create it.
I found many examples that use stored procedure, but I only want to run it once.
Something like this:
-- Creates an index if it does not already exist in MySQL.
START TRANSACTION;
SET IndexIsThere = 0;
SET given_table = 'IDR_CHGS';
SET given_index = 'FK_IDR_PATIENT_PT_ID_idx1';
SELECT COUNT(1) INTO IndexIsThere
FROM INFORMATION_SCHEMA.STATISTICS
WHERE table_name = given_table
AND index_name = given_index;
IF IndexIsThere = 0 THEN
SET #sqlstmt = CONCAT('CREATE INDEX ',given_index,' ON ', given_database,'.',given_table,' (',given_columns,')');
PREPARE st FROM #sqlstmt;
EXECUTE st;
DEALLOCATE PREPARE st;
SELECT CONCAT('Created index ', given_table,'.', given_index, ' on columns ', given_columns) AS 'CreateIndex status';
ELSE
SELECT CONCAT('Index ', given_index,' Already Exists on Table ', given_database,'.',given_table) AS 'CreateIndex status';
END IF;
COMMIT;
Is this doable?

MySQL doesn't support IF/THEN/ELSE constructs outside of stored routines or triggers.
If you use PREPARE and EXECUTE, you could simply form a string that has the CREATE INDEX statement if there is no index, and a no-op statement (e.g. a comment) if not.
SELECT COALESCE(CONCAT('SELECT \'index ', S.INDEX_NAME, ' exists already\''),
'CREATE INDEX `idx_x` ON test.foo (`x`)') INTO #sql
FROM (SELECT NULL) AS d
LEFT OUTER JOIN INFORMATION_SCHEMA.STATISTICS AS S
ON (S.TABLE_SCHEMA, S.TABLE_NAME, S.INDEX_NAME) = ('test', 'foo', 'idx_x');
PREPARE s from #sql;
EXECUTE s;
If the index exists, #sql will be:
mysql> select #sql;
+-------------------------------------+
| #sql |
+-------------------------------------+
| SELECT 'index idx_x exists already' |
+-------------------------------------+
If the index does not exist, #sql will be:
mysql> select #sql;
+----------------------------------------+
| #sql |
+----------------------------------------+
| CREATE INDEX `idx_x` ON test.foo (`x`) |
+----------------------------------------+
By the way, all CREATE and ALTER statements implicitly commit before and after the statement, so there's no purpose in using start transaction and commit in the way you're doing. See http://dev.mysql.com/doc/refman/5.6/en/implicit-commit.html

Related

How to Create View or Table from Prepared Statement (Select Query)

I am trying to create View from the following prepared statement:
CREATE VIEW myview AS -- this line is not working
SELECT CONCAT(GROUP_CONCAT('SELECT ''', COLUMN_NAME,''' MyColumns, SUM(`', COLUMN_NAME,'`) Total FROM mydb.source_table' SEPARATOR '\n UNION ALL \n'),'\nORDER BY Total ASC')
INTO #sql
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'mydb'
AND TABLE_NAME = 'source_table'
AND COLUMN_NAME NOT IN ('ID', 'Name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
I am still confused with handling group_concat syntax.
I just need help on how to create View with the above prepared statement.
UPDATE 1: Stored procedure
As suggested by #nbk, I have to create into 5 stored procedures in single column to be able the data and use it to CREATE TABLE query. Here's the stored procedure version of the above codes.
CREATE DEFINER=`admin`#`%` PROCEDURE `sp_result`()
BEGIN
SELECT CONCAT(GROUP_CONCAT('SELECT ''', COLUMN_NAME,''' MyColumns FROM mydb.source_table' SEPARATOR '\n UNION ALL \n'), '\n LIMIT 0, 1 \n')
INTO #sql
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'mydb'
AND TABLE_NAME = 'source_table'
AND COLUMN_NAME NOT IN ('ID', 'Name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END
Here's the result of this stored procedure:
+-----------+
| MyColumns |
+-----------+
| Abc |
+-----------+
In order this approach to be useful to me, I want the above result to be the one of the column of my CREATE TABLE query:
Here are my attempts but no luck:
First attempt: Separate Query
CREATE TABLE my_table AS SELECT id, name, mydb.sp_result() FROM source_table; -- Error Code: 1305. FUNCTION project_x.best_vendor1_name does not exist
Second attempt: Inserted at the last part of the stored procedure
CREATE TABLE my_table AS SELECT #sql FROM source_table; -- no effect
Here, I really do not know how CREATE TABLE using the stored procedure returned data.
UPDATE 2: Illustrate Encapsulation
CREATE DEFINER=`root`#`%` PROCEDURE `proc_column_sum`()
BEGIN
DROP TABLE IF EXISTS table2;
SELECT CONCAT('
CREATE TABLE table2 AS (',GROUP_CONCAT('
SELECT ''', COLUMN_NAME,''' MyColumns, SUM(`', COLUMN_NAME,'`) Total
FROM testdb.products '
SEPARATOR '\n UNION ALL \n'), -- runtime syntax error somewhere here
'\n ORDER BY Total ASC)') -- missing closing single quote right after ASC
INTO #sql
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'testdb'
AND TABLE_NAME = 'products'
AND COLUMN_NAME NOT IN ('ID', 'Name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END
You must put the CREATE TABLE in your prepared statement.
this example uses a temporary table only for demonstration purposes
CREATE DEFINER=`root`#`%` PROCEDURE `proc_column_sum`()
BEGIN
SELECT
CONCAT('CREATE TEMPORARY TABLE IF NOT EXISTS table2 AS (',GROUP_CONCAT('SELECT ''', COLUMN_NAME,''' MyColumns, SUM(`', COLUMN_NAME,'`) Total FROM testdb.products ' SEPARATOR '\n UNION ALL \n'),'\nORDER BY Total ASC)')
INTO #sql
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'testdb'
AND TABLE_NAME = 'products'
AND COLUMN_NAME NOT IN ('ID', 'Name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END
you call it then
call proc_column_sum();
SELECT * FROM table2
But with this approach you have call every time the procedure to get the newest data.
I can' figure out, what went wrong in your query
CREATE DEFINER=`root`#`%` PROCEDURE `proc_column_sum`()
BEGIN
DROP TABLE IF EXISTS table2;
SELECT CONCAT('
CREATE TABLE IF NOT EXISTS table2 AS (',GROUP_CONCAT('
SELECT ''', COLUMN_NAME,''' MyColumns, SUM(`', COLUMN_NAME,'`) Total
FROM testdb.products '
SEPARATOR '\n UNION ALL \n')
,'\nORDER BY Total ASC)')
INTO #sql
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'testdb'
AND TABLE_NAME = 'products'
AND COLUMN_NAME NOT IN ('ID', 'Name');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END

How to execute this script in every schema?

I have this piece of code that runs 'optimize table' to every table of a schema.
However, I need to run it to every table in every schema. How can I do that without causing problems?
set #a=null,#c=null,#b=concat("show tables where",ifnull(concat(" `Tables_in_",database(),"` like '",#c,"' and"),'')," (#a:=concat_ws(',',#a,`Tables_in_",database(),"`))");
Prepare `bd` from #b;
EXECUTE `bd`;
DEALLOCATE PREPARE `bd`;
set #a:=concat('optimize table ',#a);
PREPARE `sql` FROM #a;
EXECUTE `sql`;
DEALLOCATE PREPARE `sql`;
set #a=null,#b=null,#c=null;
Here is a stored procedure for your purpose.
It works by first querying INFORMATION_SCHEMA.TABLES to retrieve the list of schemas and table names. Then it goes into a loop and executes the OPTIMIZE TABLE commands sequentially.
Important note: running such a command on all tables is probably not a good idea. Specifically, you don't (or can't) adress MySQL built-in schemas. I added a WHERE clause in the query that excludes the following schemas: information_schema, performance_schema, mysql and sys. I would suggest that you further restrict the WHERE clause to your specific need (having a fixed list of schema would be a good idea).
DELIMITER $$
CREATE PROCEDURE OptimizeAllTables()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE tableName CHAR(100);
DECLARE tableSchema CHAR(100);
DECLARE tableList CURSOR FOR
SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys');
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN tableList;
tableListLoop: LOOP
SET done = FALSE;
FETCH tableList INTO tableSchema, tableName;
IF done THEN
LEAVE tableListLoop;
END IF;
SET #VarSQL = CONCAT('OPTIMIZE TABLE `', tableSchema, '`.`', tableName, '`');
-- SELECT #VarSQL;
PREPARE stmt FROM #VarSQL;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE tableList;
END$$
I would also strongly suggest that you run this procedure in debug mode before executing it for real. For this, you can change this block:
-- SELECT #VarSQL;
PREPARE stmt FROM #VarSQL;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
To:
SELECT #VarSQL;
-- PREPARE stmt FROM #VarSQL;
-- EXECUTE stmt;
-- DEALLOCATE PREPARE stmt;
This will show the commands without actually executing them.
Demo on DB Fiddle (in debug mode):
-- create a few tables for testing
create table table1(id int primary key);
create table table2(id int primary key);
create table table3(id int primary key);
-- call the procedure
call OptimizeAllTables();
Yields:
| #VarSQL |
| ------------------------------ |
| OPTIMIZE TABLE `test`.`table1` |
| #VarSQL |
| ------------------------------ |
| OPTIMIZE TABLE `test`.`table2` |
| #VarSQL |
| ------------------------------ |
| OPTIMIZE TABLE `test`.`table3` |

Why doesn't my mysql code work

I wrote this code and when I execute it it says I have a problem with mysql syntax near the update statement
set #s1 = (select if ((select count(*) from information_schema.columns where table_name='foo' and column_name='bar_id') > 0,
'select 1',
'alter table foo add column bar_id bigint; update foo set bar_id = baz_id;'));
prepare stmt from #s1;
execute stmt;
deallocate prepare stmt;
If I change my code to
set #s1 = (select if ((select count(*) from information_schema.columns where table_name='foo' and column_name='bar_id') > 0,
'select 1',
'alter table foo add column bar_id bigint;'));
prepare stmt from #s1;
execute stmt;
deallocate prepare stmt;
update foo set bar_id = baz_id;
then it works. but I want the update statement inside the if condition.
I cannot make this into a SP.
Error:
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'update foo set bar_id = baz_id' at line 1
In your first code block, you attempt to prepare a string that contains two SQL statements. Unfortunately, MySQL prepare / execute cannot have multiple statements.
If you can't use SP, I think I'd suggest doing this like so:
set #s1 = (select if ((select count(*) from information_schema.columns where table_name='foo' and column_name='bar_id') > 0,
'select 1',
concat('alter table foo add column bar_id bigint default ', baz_id)));
prepare stmt from #s1;
execute stmt;
deallocate prepare stmt;
alter table foo alter column bar_id drop default;
But, honestly, I'd suggest you minimize DDL changes as those can have unpredictable run-time behavior. In this case, that means adding the foo.bar_id out-of-band and just perform an update as needed.
The problem is that MySQL's prepared statements do not support multi-statements.
If you want to script the database structure updates, easiest way is to use a procedure without dynamic SQL (you might want to check the table_schema as well when you are doing the changes).
create procedure sp_fix_structure()
begin
declare v_cnt int;
select count(*) into v_cnt
from information_schema.columns
where table_schema=database() and table_name='foo' and column_name='bar_id';
if (v_cnt=0) then
alter table foo add column bar_id bigint;
update foo set bar_id = baz_id;
end if;
end

How to list all columns in table with Sql trigger

CREATE TRIGGER `table_triger_on_before_delete`
BEFORE DELETE ON `db`.`table`
FOR EACH ROW
INSERT INTO db_bkp.`table`
(col1, col2, col3)
VALUES
(OLD.col1, OLD.col2, OLD.col3)
I want to get this
(col1, col2, col3)
without type them one by one
If you're absolutely sure that the tables are with the same fields you can use simply that
CREATE TRIGGER `table_triger_on_before_delete`
BEFORE DELETE ON `db`.`table`
FOR EACH ROW
INSERT INTO db_bkp.`table`
SELECT * FROM `db`.`table` WHERE `you_primary_key`= OLD.`you_primary_key`
Becouse you row isn't deleted yet :)
It is possible to query the list of columns of a particular table from information_schema.columns table within MySQL.
However, in this case you need to create your insert statement using prepared statement because this allows you to create the sql command as a string and then you can execute it.
...
declare s_fields, s_sql varchar(1000); --length should depend on how many fields you have
select group_concat(column_name) into s_fields
from information_schema.columns
where table_name='yourtable'
group by table_name
limit 1;
set s_sql=concat('insert into tablename (',s_fields, ') values (', variable_holding_comma_separated_list_of_values,')');
prepare stmt from s_sql;
execute stmt;
deallocate prepare stmt;
...
I have modified the code from #Shadow. you must also include the schema name.
and also put it in backquotes to prevent spaces in the fieldnames.
...
DECLARE s_sql VARCHAR(1000); --length should depend ON how many fields you have
SELECT
CONCAT(
'insert into tablename ('
,GROUP_CONCAT(CONCAT('`',COLUMN_NAME,'`'))
,') values ('
,GROUP_CONCAT(CONCAT('`OLD`.`',COLUMN_NAME,'`')),
')'
)
INTO s_sql
FROM information_schema.columns
WHERE
TABLE_NAME='your_table'
AND
TABLE_SCHEMA='your_schema'
GROUP BY TABLE_NAME;
prepare stmt from s_sql;
execute stmt;
deallocate prepare stmt;
...
sample
MariaDB [your_schema]> SELECT
-> CONCAT(
-> 'insert into tablename ('
-> ,GROUP_CONCAT(CONCAT('`',COLUMN_NAME,'`'))
-> ,') values ('
-> ,GROUP_CONCAT(CONCAT('`OLD`.`',COLUMN_NAME,'`')),
-> ')'
-> )
-> INTO #s_sql
-> FROM information_schema.columns
-> WHERE
-> TABLE_NAME='your_table'
-> AND
-> TABLE_SCHEMA='your_schema'
-> GROUP BY TABLE_NAME;
Query OK, 1 row affected (0.00 sec)
MariaDB [your_schema]> select #s_sql;
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| #s_sql |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| insert into tablename (`sr_no`,`scholar_no`,`paid_amount`,`due_amount`,`fee_date`,`section_id_fk`) values (`OLD`.`sr_no`,`OLD`.`scholar_no`,`OLD`.`paid_amount`,`OLD`.`due_amount`,`OLD`.`fee_date`,`OLD`.`section_id_fk`) |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
MariaDB [your_schema]>

How to convert an SQL select query into a formatted HTML table within a MySQL function

I'm looking for a way to generate valid HTML code within MySQL (without PHP) by converting any query output into an HTML table.
Here's my progress so far and evidently, I'm stuck. I hope I can get some help, thanks.
1. "dynSQL" - A procedure to take any Select query and create a named table out of it
Since MySQL doesn't allow dynamic queries in functions, I'm calling a procedure that creates a named table, tmp. I can't use a temporary table because info about temporary tables is not available in information_schema (in mysql 5.6)
CREATE DEFINER=`root`#`%` PROCEDURE `dynSQL`(SQL_QUERY TEXT)
BEGIN
set #SQLQ := 'Drop table if exists tmp;';
PREPARE stmt from #SQLQ;
Execute stmt;
SET #SQLQ := concat('create table tmp as ',SQL_QUERY);
PREPARE stmt from #SQLQ;
Execute stmt;
-- I'm adding a auto increment ID column to be able to loop through the rows later
SET #SQLQ := "ALTER TABLE tmp add column CustColHTML_ID INT NOT NULL AUTO_INCREMENT FIRST, ADD primary KEY Id(CustColHTML_ID)";
PREPARE stmt from #SQLQ;
Execute stmt;
DEALLOCATE PREPARE stmt;
END
2. "MakeHTML" - Function to read from the table tmp and return a formatted HTML table
CREATE DEFINER=`root`#`%` FUNCTION `MakeHTML`() RETURNS text CHARSET utf8
DETERMINISTIC
BEGIN
DECLARE HTML text default "<TABLE><TR>";
DECLARE rowCount int default 0;
DECLARE i int default 0;
select concat('<TR>',group_concat('<TD>',column_name,'</TD>' separator ''),'</TR>') into html from information_Schema.`columns` where table_name='tmp';
Select max(CustColHTML_ID) into rowCount from `tmp`; -- Set the row counter
WHILE i<=rowCount DO
-- What do I do here? How do I loop through the columns of table tmp?
set i:=i+1;
END WHILE;
RETURN HTML;
END
As you can see, I'm stuck at looping through the unknown and dynamic columns of table tmp. I read about how a cursor can be used here, but all the examples I saw make use of known columns and assign those into named variables. However, since the query itself is dynamic, I wouldn't know the names of the columns.
I'd really appreciate your time and assistance, thanks!
p.s. I've posted this as a new question because my earlier question was marked as closed as being too broad. I subsequently edited my question but it was still showing as Closed. I've therefore deleted the older question and replaced it with this one.
With a sample table as such:
CREATE TABLE tmp (ID INT, Col1 INT, Col2 INT);
The SQL you would need to generate your HTML is:
SELECT CONCAT('<table>', GROUP_CONCAT(CONCAT('<tr><td>',ID,'</td><td>',Col1,'</td><td>',Col2,'</td><tr>')), '</table>')
FROM tmp;
You can generate this using the INFORMATION_SCHEMA:
SELECT CONCAT
(
'SELECT CONCAT(''<table>'', GROUP_CONCAT(CONCAT(''<tr>'', ',
GROUP_CONCAT(CONCAT('''<td>'',', COLUMN_NAME, ',''</td>''')),
', ''</tr>'')), ''</table>'') FROM tmp'
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tmp';
It is then just a case of executing this:
SET #SQL = (
SELECT CONCAT
(
'SELECT CONCAT(''<table>'', GROUP_CONCAT(CONCAT(''<tr>'', ',
GROUP_CONCAT(CONCAT('''<td>'',', COLUMN_NAME, ',''</td>''')),
', ''</tr>'')), ''</table>'') FROM tmp'
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tmp'
);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Example on SQL Fiddle
ADDENDEUM
Forgot to include table headers:
SET #SQL = (
SELECT CONCAT
(
'SELECT CONCAT(''<table><tr>'',',
GROUP_CONCAT(CONCAT('''<th>'',''', COLUMN_NAME, ''',''</th>''')),
', ''</tr>'', GROUP_CONCAT(CONCAT(''<tr>'', ',
GROUP_CONCAT(CONCAT('''<td>'',', COLUMN_NAME, ',''</td>''')),
', ''</tr>'')), ''</table>'') FROM tmp'
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tmp'
);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Example on SQL Fiddle