Use string from select to select a specific column in mysql - mysql

I'm trying to select a column based on a string I get from another select:
SELECT id, (
SELECT COLUMN_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'database_name'
AND TABLE_NAME = 'a'
AND DATA_TYPE = 'varchar'
ORDER BY `COLUMNS`.`ORDINAL_POSITION` ASC LIMIT 1) AS `name`
FROM `a`
The problem is like this I'm getting only the string from the sub select not the column content of the main query.
(what I want to do in this case, is to return the value of the first column that is varchar)

You can do this with a dynamic query constructed in a stored procedure which you then call with the appropriate parameters.
I'd put the example in a sqlfiddle if I could find a way of querying the information_schema but you can just copy/paste this into your normal client and it should work.
I've gone a little further than your example by allowing you to select the id and first varchar column from any table but you can easily adjust the procedure and hardcode the database and table names if that's all you require.
CREATE DATABASE IF NOT EXISTS `mydb`;
USE `mydb`;
CREATE TABLE IF NOT EXISTS `mydb`.`tableA` (
id INT auto_increment primary key,
char_col char(3),
varchar_col varchar(25)
);
-- clear out any existing records
TRUNCATE `mydb`.`tableA`;
INSERT INTO `mydb`.`tableA` VALUES (null,'abc','varchar value abc');
INSERT INTO `mydb`.`tableA` VALUES (null,'def','varchar value def');
INSERT INTO `mydb`.`tableA` VALUES (null,'ghi','varchar value ghi');
INSERT INTO `mydb`.`tableA` VALUES (null,'klm','varchar value klm');
DELIMITER //
DROP PROCEDURE IF EXISTS `mydb`.`dyntest` //
CREATE PROCEDURE `mydb`.`dyntest` (IN dbname VARCHAR(64), IN tname VARCHAR(64))
BEGIN
DECLARE colname VARCHAR(64);
-- get the column name (as your example)
SELECT `COLUMN_NAME` INTO colname
FROM `information_schema`.`COLUMNS`
WHERE `TABLE_SCHEMA` = dbname
AND `TABLE_NAME` = tname
AND `DATA_TYPE` = 'VARCHAR'
ORDER BY `COLUMNS`.`ORDINAL_POSITION` ASC LIMIT 1;
-- construct the query
SET #sqlquery = CONCAT('SELECT `id`,`', colname , '` FROM `' , dbname, '`.`', tname, '`');
-- Prepare and execute
PREPARE stmt FROM #sqlquery;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
This returns
mysql> CALL `mydb`.`dyntest`('mydb','tableA');
+----+-------------------+
| id | varchar_col |
+----+-------------------+
| 1 | varchar value abc |
| 2 | varchar value def |
| 3 | varchar value ghi |
| 4 | varchar value klm |
+----+-------------------+
4 rows in set (0.06 sec)

Related

Concatenating tablenames and columns in joined SQL query

How can i use the content of switches.address as a table-name, and switches.pin as a column-name to perform some sort of joined query on "switches" that that gives me (in the below case) the value of PIN3 from 0x68?
something like
SELECT name, state FROM switches
with 0x68.PIN3 as state
CREATE TABLE switches (
name varchar(20), address varchar(20), pin varchar(20));
INSERT INTO switches VALUES
("Lights_Kitchen", "0x68", "PIN3");
| name | address | pin |
+----------------+---------+------+
| Lights_Kitchen | 0x68 | PIN3 |
CREATE TABLE `0x68` (
PIN1 INT, PIN2 INT, PIN3 INT, PIN4 INT);
INSERT INTO `0x68` VALUES
(0,0,1,0)
| PIN1 | PIN2 | PIN3 | PIN4 |
+------+------+------+------+
| 0 | 0 | 1 | 0 |
and so on..
I'm not entirely sure about your condition but you can try PREPARED STATEMENTS. Perhaps something like this:
SET #tbl = NULL;
SET #col = NULL;
SET #sql = NULL;
SELECT address INTO #tbl FROM switches WHERE address='0x68';
SELECT pin INTO #col FROM switches WHERE address='0x68';
SELECT #tbl, #col;
SET #sql := CONCAT('SELECT ',#col,' FROM `',#tbl,'`;');
SELECT #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE stmt;
There are three variables #tbl, #col & #sql here and each are quite self-explanatory. The processes are:
Set all variables with NULL value: just in case that the variables has been used and still holds the last value being set.
Assigning variables with table & column value that need to be used in the final query.
Setting #sql variable with a generated query concatenated with the table and column variables.
Prepare, execute and deallocate the statement.
P/S: Those SELECT #tbl, #col and SELECT #sql query in between are just for checking for what has been set in the variables and not required in the final query structure.
Demo fiddle

MySQL find column name where it's value match the pattern

How to search the entire database for column name equal to my rule and specific value as well.
Let's say that i want to search for column name like voucher where it's value contain that word value10
So far i can find the column name but i don't know how to match with value as well.
SELECT column_name FROM information_schema.columns
WHERE TABLE_SCHEMA = 'dbname' AND column_name LIKE '%voucher%'
So the end goal is to find any column name like voucher containing value10 within it's content.
Procedure code:
CREATE PROCEDURE search_tables ( IN column_pattern TEXT,
IN value_pattern TEXT )
BEGIN
SELECT GROUP_CONCAT (CONCAT( ' SELECT ''',
TABLE_NAME,
'.',
COLUMN_NAME,
''' AS `table.column`, ',
COLUMN_NAME,
' AS `value`\nFROM ',
TABLE_NAME,
'\nWHERE ',
COLUMN_NAME,
' LIKE ''',
value_pattern,
'''' )
SEPARATOR ' UNION ALL ')
INTO #query
FROM INFORMATION_SCHEMA.COLUMNS
WHERE column_name LIKE column_pattern
AND TABLE_SCHEMA = DATABASE();
PREPARE stmt FROM #query;
EXECUTE stmt;
DROP PREPARE stmt;
END
Test tables:
CREATE TABLE table1 (val1 VARCHAR(8), val2 TEXT);
INSERT INTO table1 VALUES
('a_01_a','b_11_b'),
('c_211_c','d_311_d'),
('e_55_e','f_00_f');
CREATE TABLE table2 (val3 CHAR(6), field4 VARCHAR(64));
INSERT INTO table2 VALUES
('x_1123','ghjghj_11_tyuyu'),
('8901_t','sdf_SDF_sdf');
Call:
CALL search_tables('%val%', '%11%');
Output:
table.column value
table1.val1 c_211_c
table1.val2 b_11_b
table1.val2 d_311_d
table2.val3 x_1123
fiddle
Create a stored procedure to loop through meta data table INFORMATION_SCHEMA to fetch all tables with column_name of choice. Further dynamic SQL is used to scan each of the tables retrieved for columns having the value of choice.
DDL and DML for setting the data for testing :
create table TESTA(COLMNA char(255),COLMNC char(255));
create table TESTB(COLMNA char(255),COLMNB char(255));
create table TESTC(COLMND char(255),COLMNA char(255));
insert into TESTA values('value0','someothercolmn');
insert into TESTB values('value0','anothersomeothercolmn');
insert into TESTB values('value1','Yetanothercolumn');
Test is to search all tables having column_name as COLMNA with value as value0. The procedure will accept column_name and column_Value, hence can be used across the database, just need to pass values as appropriate.
CREATE PROCEDURE Findtables( colmn_name VARCHAR(64),colmn_value VARCHAR(64) )
BEGIN
DECLARE tablename CHAR(64);
DECLARE c1 CURSOR FOR
SELECT table_name
FROM information_Schema
WHERE column_name = colmn_name;
OPEN c1;
lable_loop:LOOP
FETCH c1 INTO tablename;
select tablename;
SET #sql = CONCAT('SELECT * FROM ', tablename, ' WHERE ',colmn_name,' = "',colmn_value ,'" ;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP lable_loop;
CLOSE c1;
END;
Call the stored procedure :
CALL Findtables('COLMNA','value0');
Output :
tablename
TESTA
COLMNA COLMNC
value0 someothercolmn
tablename
TESTB
COLMNA COLMNB
value0 anothersomeothercolmn
tablename
TESTC
COLMND COLMNA
Demonstration of the solution can be found in DBFIDDLE link [https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=4888a6160faf97fb75665832d6610293][1]
PS : I have to create INFORMATION_SCHEMA table in dbfiddle as metadata tables are not accessible.

MySQL split multivalued strings from same table in different column into new table

I want to split multi valued strings which are from one table into a new table consisting of a primary key and the split strings result.
Example strings:
table1.field1 (primary key) = 100 , table1.field2 = 'abc,def,ghi'
In the new table (table2), the result should be like this:
**column1** **column2**
**row1** 100 'abc'
**row2** 100 'def'
**row3** 100 'ghi'
**row4** etc etc
I know how to split table1.field2, but since the data was so large, I need to insert the result automatically into table2. If I do it manually, it will take so long. Could anyone help me?
Here's a solution using a prepared statement:
DROP TABLE IF EXISTS concatenatedVals;
CREATE TABLE concatenatedVals(`key` INTEGER UNSIGNED, concatenatedValue CHAR(255));
DROP TABLE IF EXISTS splitVals;
CREATE TABLE splitVals(`key` INTEGER UNSIGNED, splitValue CHAR(255));
INSERT INTO concatenatedVals VALUES (100, 'abc,def,ghi'), (200, 'jkl,mno,pqr');
SELECT * FROM concatenatedVals;
SET #VKey := '';
SET #VExec := (SELECT CONCAT('INSERT INTO splitVals VALUES', TRIM(TRAILING ',' FROM GROUP_CONCAT(CONCAT('(', #VKey:= `key`, ', \'', REPLACE(concatenatedValue, ',', CONCAT('\'), (', #VKey, ', \'')), '\'),') SEPARATOR '')), ';') FROM concatenatedVals);
PREPARE stmt FROM #VExec;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SELECT * FROM splitVals;
Outputs:
SELECT * FROM splitVals;
+------+------------+
| key | splitValue |
+------+------------+
| 100 | abc |
| 100 | def |
| 100 | ghi |
| 200 | jkl |
| 200 | mno |
| 200 | pqr |
+------+------------+
6 rows in set (0.00 sec)
Let me know if you have any questions.
Regarding the question, how can I account for scenarios where the number of rows in my source table means the prepared statement exceeds the max-concat length, see the following example. As this uses a WHILE loop it must be inside a stored procedure. This could be adapted to allow table names and column names as arguments using further CONCATAND prepared statements to build up and execute commands dynamically. For now however, please change the table and column names from those in my example to match your data and it should work fine.
DROP TABLE IF EXISTS concatenatedVals;
CREATE TABLE concatenatedVals(`key` INTEGER UNSIGNED, concatenatedValue CHAR(255));
DROP TABLE IF EXISTS splitVals;
CREATE TABLE splitVals(`key` INTEGER UNSIGNED, splitValue CHAR(255));
INSERT INTO concatenatedVals VALUES (100, 'abc,def,ghi'), (200, 'jkl,mno,pqr'),(300, 'abc,def,ghi'), (400, 'jkl,mno,pqr'),(500, 'abc,def,ghi'), (600, 'jkl,mno,pqr'),(700, 'abc,def,ghi'), (800, 'jkl,mno,pqr'),(900, 'abc,def,ghi'), (1000, 'jkl,mno,pqr');
SELECT * FROM concatenatedVals;
DELIMITER $
DROP PROCEDURE IF EXISTS loopStringSplit$
CREATE PROCEDURE loopStringSplit()
BEGIN
DECLARE VKeyMaxLength, VConcatValMaxLength, VFixedCommandLength, VVariableCommandLength, VSelectLimit, VRowsToProcess, VRowsProcessed INT;
SET VFixedCommandLength = CHAR_LENGTH(CONCAT('INSERT INTO splitVals VALUES;'));
SET VKeyMaxLength = (SELECT MAX(CHAR_LENGTH(`key`)) FROM concatenatedVals);
SET VConcatValMaxLength = (SELECT MAX(CHAR_LENGTH(concatenatedValue)) FROM concatenatedVals);
SET VVariableCommandLength = CHAR_LENGTH('(,\'\')');
SET VSelectLimit = FLOOR((##group_concat_max_len - VFixedCommandLength) / (VKeyMaxLength + VConcatValMaxLength + VVariableCommandLength));
SET VRowsToProcess := (SELECT COUNT(*) FROM concatenatedVals);
SET VRowsProcessed = 0;
SELECT VRowsProcessed, VRowsToProcess, VSelectLimit;
WHILE VRowsProcessed < VRowsToProcess DO
SET #VKey := '';
SET #VExec := (SELECT CONCAT('INSERT INTO splitVals VALUES', TRIM(TRAILING ',' FROM GROUP_CONCAT(CONCAT('(', #VKey:= `key`, ', \'', REPLACE(concatenatedValue, ',', CONCAT('\'), (', #VKey, ', \'')), '\'),') SEPARATOR '')), ';') FROM (SELECT * FROM concatenatedVals LIMIT VRowsProcessed, VSelectLimit) A);
SELECT #VExec;
PREPARE stmt FROM #VExec;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET VRowsProcessed = VRowsProcessed + VSelectLimit;
SELECT CONCAT('Processed rows: ', VRowsProcessed);
END WHILE;
END$
DELIMITER ;
CALL loopStringSplit();
SELECT * FROM splitVals;
Regards,
James

MySQL - Stored Procedure

I need to fetch data from the stored procedure and present it in a dashboard. So the background for this is.
For every select query in the Stored procedure will return only one record I've around 10 queries like that. Apart from that I've also a query which will return say some 5 rows in the same stored procedure.
I want this 5 records to be presented as 1 line along with the other queries in the same stored procedure.
Example:
CALL dashboard_proc() Will return
Date Amount total_orders
1-1-2017 500.00 50
A part from above output I want to append another select query to the same stored procedure (dashboard_proc). The other query which will return
Place Total
Hyderabad 100
Bangalore 100
Chennai 200
Mumbai 100
And I need below
Date Amount total_orders Hyderabad Bangalore Chennai Mumbai
1-1-2016 500.00 50 100 100 200 100
Based on your inputs:
First Part of the report has only one record so I have used the below structure
CREATE TABLE `ResultPart1` (
`ReportDate` date NOT NULL,
`Amount` float DEFAULT NULL,
`total_orders` int(11) DEFAULT NULL
)
insert into ResultPart1 select '2017-01-01', 500.00 , 50;
2. Second Part of the report will have distinct Places, here the places used are single word and no keyword used.
CREATE TABLE `ResultPart2` (
`Place` varchar(50) NOT NULL,
`Total` int(11) DEFAULT NULL
)
insert into ResultPart2()
Select 'Hyderabad', 100 union
Select 'Bangalore' , 100 union
Select 'Chennai' , 200 union
Select 'Mumbai' , 100;
3. Create Stored Procedure
CREATE PROCEDURE `new_procedureDashboard`()
BEGIN
Declare vPlaceCount int;
Declare vquery varchar(1000);
declare vPlace varchar(1000);
declare vPlaceTotal int;
-- select * from ResultPart1;
-- Select * from ResultPart2;
CREATE TEMPORARY TABLE IF NOT EXISTS Table_ResultPart2
(
Id int NOT NULL AUTO_INCREMENT,
Place varchar(100),
PlaceTotal int ,
PRIMARY KEY (id)
);
insert into Table_ResultPart2(Place,PlaceTotal)
Select distinct Place,Total from ResultPart2;
SET vPlaceCount=(Select count(*)from Table_ResultPart2);
WHILE vPlaceCount>0 DO
SET vPlace=(Select Place from Table_ResultPart2 where Id=vPlaceCount);
SET vPlaceTotal=(Select PlaceTotal from Table_ResultPart2 where Id=vPlaceCount);
SET vquery=concat("", "alter table ResultPart1 add " ,vPlace ," int ;");
Select vquery into #AddColumn;
PREPARE stmt FROM #AddColumn;
-- Select #AddColumn;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET vquery="";
-- Select vPlace,vPlaceTotal;
-- Update
SET SQL_SAFE_UPDATES = 0;
SET vquery=concat("", "update ResultPart1 SET " ,vPlace ," = " ,vPlaceTotal ," where 1=1 ;");
Select vquery into #UpdateColumn;
-- select #UpdateColumn;
PREPARE stmt1 FROM #UpdateColumn;
-- Select #AddColumn;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
SET SQL_SAFE_UPDATES = 1;
SET vPlaceCount=vPlaceCount-1;
SET vPlace="" ;
END WHILE;
Select * from ResultPart1;
drop TEMPORARY TABLE IF EXISTS Table_ResultPart2;
END
Explanation:
Added the required column in the main resultant table using loop, and updated the values of the newly place(added) column using the loop. You will require to make changes in your stored procedure as I have used permanent tables (ResultPart1 and ResultPart2 ) .
4. Result
Alter Statements to drop column Just in case you want to rerun the stored procedure.
alter table ResultPart1 drop column Hyderabad;
alter table ResultPart1 drop column Bangalore;
alter table ResultPart1 drop column Chennai;
alter table ResultPart1 drop column Mumbai;

MySQL sorting table by column names

I have already built a table with field names in arbitrary order. I want those field names to be in alphabetical order so that I can use them in my dropdown list. Is it possible with a query?
Select columns from a specific table using INFORMATION_SCHEMA.COLUMNS and sort alphabetically with ORDER BY:
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = '[schemaname]'
AND table_name = '[tablename]'
ORDER BY column_name
Note: The following code will alter the specified table and reorder the columns in alphabetical order
This should do the trick. It's a bit messy and lengthy, and you'll have to change the database name and table name, but for this one, the only requirement is that there is a database named "test" and that you are running these commands in it:
Let's create the tables we need:
-- CREATE TESTING TABLE IN A DATABASE NAMED "test"
DROP TABLE IF EXISTS alphabet;
CREATE TABLE alphabet (
d varchar(10) default 'dee' not null
, f varchar(21)
, e tinyint
, b int NOT NULL
, a varchar(1)
, c int default '3'
);
-- USE A COMMAND STORAGE TABLE
DROP TABLE IF EXISTS loadcommands;
CREATE TABLE loadcommands (
id INT NOT NULL AUTO_INCREMENT
, sqlcmd VARCHAR(1000)
, PRIMARY KEY (id)
);
Now let's create the two stored procedures required for this to work:
Separating them since one will be responsible for loading the commands, and including a cursor to immediately work with it isn't plausible (at least for me and my mysql version):
-- PROCEDURE TO LOAD COMMANDS FOR REORDERING
DELIMITER //
CREATE PROCEDURE reorder_loadcommands ()
BEGIN
DECLARE limitoffset INT;
SET #rank = 0;
SET #rankmain = 0;
SET #rankalter = 0;
SELECT COUNT(column_name) INTO limitoffset
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = 'test'
AND table_name = 'alphabet';
INSERT INTO loadcommands (sqlcmd)
SELECT CONCAT(t1.cmd, t2.position) AS commander FROM (
SELECT #rankalter:=#rankalter+1 AS rankalter, CONCAT('ALTER TABLE '
, table_name, ' '
, 'MODIFY COLUMN ', column_name, ' '
, column_type, ' '
, CASE
WHEN character_set_name IS NOT NULL
THEN CONCAT('CHARACTER SET ', character_set_name, ' COLLATE ', collation_name, ' ')
ELSE ' '
END
, CASE
WHEN is_nullable = 'NO' AND column_default IS NULL
THEN 'NOT NULL '
WHEN is_nullable = 'NO' AND column_default IS NOT NULL
THEN CONCAT('DEFAULT \'', column_default, '\' NOT NULL ')
WHEN is_nullable = 'YES' THEN 'DEFAULT NULL '
END
) AS cmd
, column_name AS columnname
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = 'test'
AND table_name = 'alphabet'
ORDER BY columnname
) t1
INNER JOIN (
SELECT #rankmain:=#rankmain+1 AS rownum, position FROM (
SELECT 0 AS rownum, 'FIRST' AS position
, '' AS columnname
UNION
SELECT #rank:=#rank+1 AS rownum, CONCAT('AFTER ', column_name) AS position
, column_name AS columnname
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_schema = 'test'
AND table_name = 'alphabet'
ORDER BY columnname
LIMIT limitoffset
) inner_table
) t2 ON t1.rankalter = t2.rownum
;
END//
DELIMITER ;
If anyone thinks/sees that I'm missing to include any important column attributes in the ALTER command, please hesitate not and mention it! Now to the next procedure. This one just executes the commands following the order of column id from the loadcommands table. :
-- PROCEDURE TO RUN EACH REORDERING COMMAND
DELIMITER //
CREATE PROCEDURE reorder_executecommands ()
BEGIN
DECLARE sqlcommand VARCHAR(1000);
DECLARE isdone INT DEFAULT FALSE;
DECLARE reorderCursor CURSOR FOR
SELECT sqlcmd FROM loadcommands ORDER BY id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET isdone = TRUE;
OPEN reorderCursor;
read_loop:LOOP
FETCH reorderCursor INTO sqlcommand;
IF isdone THEN
LEAVE read_loop;
END IF;
SET #sqlcmd = sqlcommand;
PREPARE stmt FROM #sqlcmd;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP read_loop;
CLOSE reorderCursor;
END//
DELIMITER ;
The SQL is long, so if someone can point out ways (and has tested them) to make this shorter I'd gladly do it, but for now, this at least works on my end. I also didn't need to put dummy data in the alphabet table. Checking the results can be done using the SHOW... command.
The last part:
-- TO TEST; AFTER RUNNING DDL COMMANDS:
SHOW CREATE TABLE alphabet; -- SEE ORIGINAL ORDER
CALL reorder_loadcommands(); -- PREPARE COMMANDS
CALL reorder_executecommands(); -- RUN COMMANDS
SHOW CREATE TABLE alphabet; -- SEE NEW ORDER
Perhaps later on I could make reorder_loadcommands dynamic and accept table and schema parameters, but I guess this is all for now..