Concatenating tablenames and columns in joined SQL query - mysql

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

Related

MYSQL concat char to create table name with select

I tried to use such code
select
price
from
(select concat("_",`id`) -- to set table name
as
dta
from
druk
where
date >= '2021-02-01' and date < '2021-03-01') d
If I put * instead price I get for example "_5438" - table name. One or more. In this way I can't get price.
I tried to use variables from examples I found, but some of them mysql do'es not recognize. What should I do to make proper, dynamic table name with concat?
You can use dynamic sql for this.
First you get the table nane into #dta and after that constructs the actual query which you run as prepare statements
But this onlöy works, if your forwst select only goves back 1 eow as result, that is why it is LIMIT 1
SELECT concat("_",`id`)
INTO
#dta
FROM
druk
WHERE
date >= '2021-02-01' AND `date` < '2021-03-01' LIMIT 1;
SET #sql := CONCAT('SELECT price FROM ',#dta );
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Still this does look like a poor design. to have many tables with the same design
For multiple ids you need a stored procedure, it can look like this).
But i didn_'t test it
CREATE TABLE druk(`id` INT , `date` date)
INSERT INTO druk VALUES (1,now()),(2,now())
CREATE TABLE _1 (price DECIMAL(8,3))
INSERT INTO _1 VALUE (2.3),(4.6),(7.9)
CREATE TABLE _2 (price DECIMAL(8,3))
INSERT INTO _2 VALUE (0.3),(1.6),(3.9)
CREATE PROCEDURE createpricelist( IN _datefrom varchar(29),IN _dateto varchar(20)
)
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE _id VARCHAR(29);
DEClARE curid
CURSOR FOR
SELECT concat("_",`id`) -- to set table name
FROM
druk
WHERE
`date` >= _datefrom AND `date` < _dateto;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
SET #datefrom := _datefrom;
SET #dateto := _dateto;
DROP TEMPORARY TABLE IF EXISTS tableprice;
CREATE TEMPORARY TABLE tableprice (price DECIMAL(8,2));
OPEN curid;
getprice: LOOP
FETCH curid INTO _id;
IF finished = 1 THEN
LEAVE getprice;
END IF;
SET #od = _id;
SET #sql := CONCAT('INSERT INTO tableprice (price) select price from ',_id);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP getprice;
CLOSE curid;
-- Diplay temporary table you can use it outside the procedure
SELECT * FROM tableprice;
END
✓
CALL createpricelist(NOW() -INTERVAL 1 DAY,NOW())
| price |
| ----: |
| 2.30 |
| 4.60 |
| 7.90 |
| 0.30 |
| 1.60 |
| 3.90 |
✓
db<>fiddle here

Update multiple fields in mysql by publishing single message to kafka

I have an requirement to update status as De Active in Mysql 'Table1' for last 10 days records through kafka connect.
how would I achieve to publish one record to kafka topic because mysql provides to perform select and update in single query.
DEMO example.
-- create stored procedure (once)
CREATE PROCEDURE execute_many_queries (queries_text TEXT)
BEGIN
REPEAT
SET #sql := SUBSTRING_INDEX(queries_text, ';', 1);
SET queries_text := TRIM(LEADING ';' FROM TRIM(LEADING #sql FROM queries_text));
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
UNTIL queries_text = '' END REPEAT;
END
-- create testing table
CREATE TABLE test (id INT, val INT);
-- execute 3 queries by 1 statement
CALL execute_many_queries ('INSERT INTO test VALUES (1,11), (2,22); UPDATE test SET val = 222 WHERE id = 2; SELECT * FROM test;');
id | val
-: | --:
1 | 11
2 | 222
-- execute more 2 queries by 1 statement
CALL execute_many_queries ('UPDATE test SET val = 111 WHERE id = 1; SELECT * FROM test;');
id | val
-: | --:
1 | 111
2 | 222
db<>fiddle here
Use with caution! no checks in SP - the queries must be error-free. And injection is possible.

Use string from select to select a specific column in 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)

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

CALL in stored procedure doesn't work

I have next data set in table rel_user_article
+---------+------------+
| user_id | article_id |
+---------+------------+
| 1 | -1 |
| 97 | 153 |
+---------+------------+
This table implies next logic: each author must have at least 1 article and each article must have at least 1 author. When author hasn't articles, than table must have fake relation:(uid, -1)
When author adds his first article than this fake relation must be deleted.
I have stored procedures for creating new relation and deleting fake ones.
Deleting fake relations looks like this:
CREATE PROCEDURE `rel_delete_fake`(IN `ids` TEXT, IN `tbl` VARCHAR(255),
IN `fake_f` VARCHAR(255), IN `real_f` VARCHAR(255))
MODIFIES SQL DATA
proc: begin
if (`ids` = '') then
leave proc;
end if;
set #s = concat('DELETE FROM ', `tbl`, ' WHERE (', `fake_f`,
' = "-1" AND ', `real_f`, ' IN (', `ids`, '))');
prepare qr from #s;
execute qr;
deallocate prepare qr;
end proc
Creating "real" relations looks like this:
CREATE DEFINER=`root`#`localhost` PROCEDURE `rel_create`(IN `ids1` TEXT,
IN `ids2` TEXT, IN `tbl` VARCHAR(255), IN `field1` VARCHAR(255),
IN `field2` VARCHAR(255))
MODIFIES SQL DATA
proc: begin
set #cur = 0;
set #id1_cur = 0;
set #id2_cur = 0;
set #id1_cur_old = NULL;
set #id2_cur_old = NULL;
if (`ids1` = '' or `ids2` = '') then
leave proc;
end if;
set #sql_str = concat('insert into ', `tbl`, ' (', `field1`, ', ', `field2`, ") values (?, ?)");
prepare qr from #sql_str;
loop1: loop
set #cur = #cur + 1;
set #id1_cur = substring_index(substring_index(`ids1`, ',', #cur), ',', -1);
set #id2_cur = substring_index(substring_index(`ids2`, ',', #cur), ',', -1);
if (#id1_cur = #id1_cur_old and #id2_cur = #id2_cur_old) then
leave proc;
end if;
execute qr using #id1_cur, #id2_cur;
set #id1_cur_old = #id1_cur;
set #id2_cur_old = #id2_cur;
end loop loop1;
deallocate prepare qr;
-- deleting fake records that became needless
-- even this doesn't work
call `rel_delete_fake`('1','rel_user_article','article_id','user_id');
leave proc;
-- deleting fake records that became needless
call `rel_delete_fake`(`ids1`, `tbl`, `field2`, `field1`);
call `rel_delete_fake`(`ids2`, `tbl`, `field1`, `field2`);
end proc
Procedure for deleting fake relations works. Procedure for creating relations only creates new record but don't even call rel_delete_fake procedure.
For test I'm issuing next call:
call `rel_create`('1','153','rel_user_article','user_id','article_id');
and have in result:
+---------+------------+
| user_id | article_id |
+---------+------------+
| 1 | -1 |
| 1 | 153 |
| 97 | 153 |
+---------+------------+
Why this (1, -1) not deleted?
I found mistake. There must be "leave loop1;" in place of "leave proc;" in loop.