MYSQL concat char to create table name with select - mysql

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

Related

How to reference an input in SQL functions?

I have a table like this:
+----+--------+
| Id | Salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+
I am writing a function with mySQL to get the n th largest value in Salary.
Here is the function:
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
# Write your MySQL query statement below.
select DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1 offset (N - 1)
#FETCH NEXT 1 ROWS ONLY
);
END
But I got the an error near (N-1).
if I change (N-1) to 1 :
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
# Write your MySQL query statement below.
select DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1 offset 1
#FETCH NEXT 1 ROWS ONLY
);
END
It runs correctly.
So the question is how to reference input in SQL function? It seems it can be directly called as an argument as we do in other languages.
LIMIT argument cannot be a variable. Use prepared statement - in it the LIMIT parameter may be taken from a variable. But dynamic SQL is not allowed in the function - use stored procedure:
CREATE PROCEDURE getNthHighestSalary(N INT)
BEGIN
SET #sql := 'SELECT DISTINCT Salary
INTO #output
FROM Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET ?';
PREPARE stmt FROM #sql;
SET #output := N-1;
EXECUTE stmt USING #output;
DROP PREPARE stmt;
SELECT #output;
END
fiddle
It seems I need to declare and set a variable before reference it in SQL query:
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
DECLARE M INT;
SET M=N-1;
RETURN (
# Write your MySQL query statement below.
select DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET M
#FETCH NEXT 1 ROWS ONLY
);
END

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 Recursive Query Alternative? [duplicate]

I have the following table:
id | parent_id | quantity
-------------------------
1 | null | 5
2 | null | 3
3 | 2 | 10
4 | 2 | 15
5 | 3 | 2
6 | 5 | 4
7 | 1 | 9
Now I need a stored procedure in mysql that calls itself recursively and returns the computed quantity.
For example the id 6 has 5 as a parent which as 3 as a parent which has 2 as a parent.
So I need to compute 4 * 2 * 10 * 3 ( = 240) as a result.
I am fairly new to stored procedures and I won't use them very often in the future because I prefer having my business logic in my program code rather then in the database. But in this case I can't avoid it.
Maybe a mysql guru (that's you) can hack together a working statement in a couple of seconds.
its work only in mysql version >= 5
the stored procedure declaration is this,
you can give it little improve , but this working :
DELIMITER $$
CREATE PROCEDURE calctotal(
IN number INT,
OUT total INT
)
BEGIN
DECLARE parent_ID INT DEFAULT NULL ;
DECLARE tmptotal INT DEFAULT 0;
DECLARE tmptotal2 INT DEFAULT 0;
SELECT parentid FROM test WHERE id = number INTO parent_ID;
SELECT quantity FROM test WHERE id = number INTO tmptotal;
IF parent_ID IS NULL
THEN
SET total = tmptotal;
ELSE
CALL calctotal(parent_ID, tmptotal2);
SET total = tmptotal2 * tmptotal;
END IF;
END$$
DELIMITER ;
the calling is like
(its important to set this variable) :
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
CALL calctotal(6, #total);
SELECT #total;
Take a look at Managing Hierarchical Data in MySQL by Mike Hillyer.
It contains fully worked examples on dealing with hierarchical data.
How about avoiding procedures:
SELECT quantity from (
SELECT #rq:=parent_id as id, #val:=#val*quantity as quantity from (
select * from testTable order by -id limit 1000000 # 'limit' is required for MariaDB if we want to sort rows in subquery
) t # we have to inverse ids first in order to get this working...
join
( select #rq:= 6 /* example query */, #val:= 1 /* we are going to multiply values */) tmp
where id=#rq
) c where id is null;
Check out Fiddle!
Note! this will not work if row's parent_id>id.
Cheers!
DELIMITER $$
CREATE DEFINER=`arun`#`%` PROCEDURE `recursivesubtree`( in iroot int(100) , in ilevel int(110) , in locid int(101) )
BEGIN
DECLARE irows,ichildid,iparentid,ichildcount,done INT DEFAULT 0;
DECLARE cname VARCHAR(64);
SET irows = ( SELECT COUNT(*) FROM account WHERE parent_id=iroot and location_id=locid );
IF ilevel = 0 THEN
DROP TEMPORARY TABLE IF EXISTS _descendants;
CREATE TEMPORARY TABLE _descendants (
childID INT, parentID INT, name VARCHAR(64), childcount INT, level INT
);
END IF;
IF irows > 0 THEN
BEGIN
DECLARE cur CURSOR FOR
SELECT
f.account_id,f.parent_id,f.account_name,
(SELECT COUNT(*) FROM account WHERE parent_id=t.account_id and location_id=locid ) AS childcount
FROM account t JOIN account f ON t.account_id=f.account_id
WHERE t.parent_id=iroot and t.location_id=locid
ORDER BY childcount<>0,t.account_id;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
OPEN cur;
WHILE NOT done DO
FETCH cur INTO ichildid,iparentid,cname,ichildcount;
IF NOT done THEN
INSERT INTO _descendants VALUES(ichildid,iparentid,cname,ichildcount,ilevel );
IF ichildcount > 0 THEN
CALL recursivesubtree( ichildid, ilevel + 1 );
END IF;
END IF;
END WHILE;
CLOSE cur;
END;
END IF;
IF ilevel = 0 THEN
-- Show result table headed by name that corresponds to iroot:
SET cname = (SELECT account_name FROM account WHERE account_id=iroot and location_id=locid );
SET #sql = CONCAT('SELECT CONCAT(REPEAT(CHAR(36),2*level),IF(childcount,UPPER(name),name))',
' AS ', CHAR(39),cname,CHAR(39),' FROM _descendants');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END IF;
END$$
DELIMITER ;

mysql stored procedure that calls itself recursively

I have the following table:
id | parent_id | quantity
-------------------------
1 | null | 5
2 | null | 3
3 | 2 | 10
4 | 2 | 15
5 | 3 | 2
6 | 5 | 4
7 | 1 | 9
Now I need a stored procedure in mysql that calls itself recursively and returns the computed quantity.
For example the id 6 has 5 as a parent which as 3 as a parent which has 2 as a parent.
So I need to compute 4 * 2 * 10 * 3 ( = 240) as a result.
I am fairly new to stored procedures and I won't use them very often in the future because I prefer having my business logic in my program code rather then in the database. But in this case I can't avoid it.
Maybe a mysql guru (that's you) can hack together a working statement in a couple of seconds.
its work only in mysql version >= 5
the stored procedure declaration is this,
you can give it little improve , but this working :
DELIMITER $$
CREATE PROCEDURE calctotal(
IN number INT,
OUT total INT
)
BEGIN
DECLARE parent_ID INT DEFAULT NULL ;
DECLARE tmptotal INT DEFAULT 0;
DECLARE tmptotal2 INT DEFAULT 0;
SELECT parentid FROM test WHERE id = number INTO parent_ID;
SELECT quantity FROM test WHERE id = number INTO tmptotal;
IF parent_ID IS NULL
THEN
SET total = tmptotal;
ELSE
CALL calctotal(parent_ID, tmptotal2);
SET total = tmptotal2 * tmptotal;
END IF;
END$$
DELIMITER ;
the calling is like
(its important to set this variable) :
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
CALL calctotal(6, #total);
SELECT #total;
Take a look at Managing Hierarchical Data in MySQL by Mike Hillyer.
It contains fully worked examples on dealing with hierarchical data.
How about avoiding procedures:
SELECT quantity from (
SELECT #rq:=parent_id as id, #val:=#val*quantity as quantity from (
select * from testTable order by -id limit 1000000 # 'limit' is required for MariaDB if we want to sort rows in subquery
) t # we have to inverse ids first in order to get this working...
join
( select #rq:= 6 /* example query */, #val:= 1 /* we are going to multiply values */) tmp
where id=#rq
) c where id is null;
Check out Fiddle!
Note! this will not work if row's parent_id>id.
Cheers!
DELIMITER $$
CREATE DEFINER=`arun`#`%` PROCEDURE `recursivesubtree`( in iroot int(100) , in ilevel int(110) , in locid int(101) )
BEGIN
DECLARE irows,ichildid,iparentid,ichildcount,done INT DEFAULT 0;
DECLARE cname VARCHAR(64);
SET irows = ( SELECT COUNT(*) FROM account WHERE parent_id=iroot and location_id=locid );
IF ilevel = 0 THEN
DROP TEMPORARY TABLE IF EXISTS _descendants;
CREATE TEMPORARY TABLE _descendants (
childID INT, parentID INT, name VARCHAR(64), childcount INT, level INT
);
END IF;
IF irows > 0 THEN
BEGIN
DECLARE cur CURSOR FOR
SELECT
f.account_id,f.parent_id,f.account_name,
(SELECT COUNT(*) FROM account WHERE parent_id=t.account_id and location_id=locid ) AS childcount
FROM account t JOIN account f ON t.account_id=f.account_id
WHERE t.parent_id=iroot and t.location_id=locid
ORDER BY childcount<>0,t.account_id;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
OPEN cur;
WHILE NOT done DO
FETCH cur INTO ichildid,iparentid,cname,ichildcount;
IF NOT done THEN
INSERT INTO _descendants VALUES(ichildid,iparentid,cname,ichildcount,ilevel );
IF ichildcount > 0 THEN
CALL recursivesubtree( ichildid, ilevel + 1 );
END IF;
END IF;
END WHILE;
CLOSE cur;
END;
END IF;
IF ilevel = 0 THEN
-- Show result table headed by name that corresponds to iroot:
SET cname = (SELECT account_name FROM account WHERE account_id=iroot and location_id=locid );
SET #sql = CONCAT('SELECT CONCAT(REPEAT(CHAR(36),2*level),IF(childcount,UPPER(name),name))',
' AS ', CHAR(39),cname,CHAR(39),' FROM _descendants');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END IF;
END$$
DELIMITER ;

How do you use the "WITH" clause in MySQL?

I am converting all my SQL Server queries to MySQL and my queries that have WITH in them are all failing. Here's an example:
WITH t1 AS
(
SELECT article.*, userinfo.*, category.*
FROM question
INNER JOIN userinfo ON userinfo.user_userid = article.article_ownerid
INNER JOIN category ON article.article_categoryid = category.catid
WHERE article.article_isdeleted = 0
)
SELECT t1.*
FROM t1
ORDER BY t1.article_date DESC
LIMIT 1, 3
MySQL prior to version 8.0 doesn't support the WITH clause (CTE in SQL Server parlance; Subquery Factoring in Oracle), so you are left with using:
TEMPORARY tables
DERIVED tables
inline views (effectively what the WITH clause represents - they are interchangeable)
The request for the feature dates back to 2006.
As mentioned, you provided a poor example - there's no need to perform a subselect if you aren't altering the output of the columns in any way:
SELECT *
FROM ARTICLE t
JOIN USERINFO ui ON ui.user_userid = t.article_ownerid
JOIN CATEGORY c ON c.catid = t.article_categoryid
WHERE t.published_ind = 0
ORDER BY t.article_date DESC
LIMIT 1, 3
Here's a better example:
SELECT t.name,
t.num
FROM TABLE t
JOIN (SELECT c.id
COUNT(*) 'num'
FROM TABLE c
WHERE c.column = 'a'
GROUP BY c.id) ta ON ta.id = t.id
Mysql Developers Team announced that version 8.0 will have Common Table Expressions in MySQL (CTEs). So it will be possible to write queries like this:
WITH RECURSIVE my_cte AS
(
SELECT 1 AS n
UNION ALL
SELECT 1+n FROM my_cte WHERE n<10
)
SELECT * FROM my_cte;
+------+
| n |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+------+
10 rows in set (0,00 sec)
In Sql the with statement specifies a temporary named result set, known as a common table expression (CTE). It can be used for recursive queries, but in this case, it specifies as subset. If mysql allows for subselectes i would try
select t1.*
from (
SELECT article.*,
userinfo.*,
category.*
FROM question INNER JOIN
userinfo ON userinfo.user_userid=article.article_ownerid INNER JOIN category ON article.article_categoryid=category.catid
WHERE article.article_isdeleted = 0
) t1
ORDER BY t1.article_date DESC Limit 1, 3
I followed the link shared by lisachenko and found another link to this blog:
http://guilhembichot.blogspot.co.uk/2013/11/with-recursive-and-mysql.html
The post lays out ways of emulating the 2 uses of SQL WITH. Really good explanation on how these work to do a similar query as SQL WITH.
1) Use WITH so you don't have to perform the same sub query multiple times
CREATE VIEW D AS (SELECT YEAR, SUM(SALES) AS S FROM T1 GROUP BY YEAR);
SELECT D1.YEAR, (CASE WHEN D1.S>D2.S THEN 'INCREASE' ELSE 'DECREASE' END) AS TREND
FROM
D AS D1,
D AS D2
WHERE D1.YEAR = D2.YEAR-1;
DROP VIEW D;
2) Recursive queries can be done with a stored procedure that makes the call similar to a recursive with query.
CALL WITH_EMULATOR(
"EMPLOYEES_EXTENDED",
"
SELECT ID, NAME, MANAGER_ID, 0 AS REPORTS
FROM EMPLOYEES
WHERE ID NOT IN (SELECT MANAGER_ID FROM EMPLOYEES WHERE MANAGER_ID IS NOT NULL)
",
"
SELECT M.ID, M.NAME, M.MANAGER_ID, SUM(1+E.REPORTS) AS REPORTS
FROM EMPLOYEES M JOIN EMPLOYEES_EXTENDED E ON M.ID=E.MANAGER_ID
GROUP BY M.ID, M.NAME, M.MANAGER_ID
",
"SELECT * FROM EMPLOYEES_EXTENDED",
0,
""
);
And this is the code or the stored procedure
# Usage: the standard syntax:
# WITH RECURSIVE recursive_table AS
# (initial_SELECT
# UNION ALL
# recursive_SELECT)
# final_SELECT;
# should be translated by you to
# CALL WITH_EMULATOR(recursive_table, initial_SELECT, recursive_SELECT,
# final_SELECT, 0, "").
# ALGORITHM:
# 1) we have an initial table T0 (actual name is an argument
# "recursive_table"), we fill it with result of initial_SELECT.
# 2) We have a union table U, initially empty.
# 3) Loop:
# add rows of T0 to U,
# run recursive_SELECT based on T0 and put result into table T1,
# if T1 is empty
# then leave loop,
# else swap T0 and T1 (renaming) and empty T1
# 4) Drop T0, T1
# 5) Rename U to T0
# 6) run final select, send relult to client
# This is for *one* recursive table.
# It would be possible to write a SP creating multiple recursive tables.
delimiter |
CREATE PROCEDURE WITH_EMULATOR(
recursive_table varchar(100), # name of recursive table
initial_SELECT varchar(65530), # seed a.k.a. anchor
recursive_SELECT varchar(65530), # recursive member
final_SELECT varchar(65530), # final SELECT on UNION result
max_recursion int unsigned, # safety against infinite loop, use 0 for default
create_table_options varchar(65530) # you can add CREATE-TABLE-time options
# to your recursive_table, to speed up initial/recursive/final SELECTs; example:
# "(KEY(some_column)) ENGINE=MEMORY"
)
BEGIN
declare new_rows int unsigned;
declare show_progress int default 0; # set to 1 to trace/debug execution
declare recursive_table_next varchar(120);
declare recursive_table_union varchar(120);
declare recursive_table_tmp varchar(120);
set recursive_table_next = concat(recursive_table, "_next");
set recursive_table_union = concat(recursive_table, "_union");
set recursive_table_tmp = concat(recursive_table, "_tmp");
# Cleanup any previous failed runs
SET #str =
CONCAT("DROP TEMPORARY TABLE IF EXISTS ", recursive_table, ",",
recursive_table_next, ",", recursive_table_union,
",", recursive_table_tmp);
PREPARE stmt FROM #str;
EXECUTE stmt;
# If you need to reference recursive_table more than
# once in recursive_SELECT, remove the TEMPORARY word.
SET #str = # create and fill T0
CONCAT("CREATE TEMPORARY TABLE ", recursive_table, " ",
create_table_options, " AS ", initial_SELECT);
PREPARE stmt FROM #str;
EXECUTE stmt;
SET #str = # create U
CONCAT("CREATE TEMPORARY TABLE ", recursive_table_union, " LIKE ", recursive_table);
PREPARE stmt FROM #str;
EXECUTE stmt;
SET #str = # create T1
CONCAT("CREATE TEMPORARY TABLE ", recursive_table_next, " LIKE ", recursive_table);
PREPARE stmt FROM #str;
EXECUTE stmt;
if max_recursion = 0 then
set max_recursion = 100; # a default to protect the innocent
end if;
recursion: repeat
# add T0 to U (this is always UNION ALL)
SET #str =
CONCAT("INSERT INTO ", recursive_table_union, " SELECT * FROM ", recursive_table);
PREPARE stmt FROM #str;
EXECUTE stmt;
# we are done if max depth reached
set max_recursion = max_recursion - 1;
if not max_recursion then
if show_progress then
select concat("max recursion exceeded");
end if;
leave recursion;
end if;
# fill T1 by applying the recursive SELECT on T0
SET #str =
CONCAT("INSERT INTO ", recursive_table_next, " ", recursive_SELECT);
PREPARE stmt FROM #str;
EXECUTE stmt;
# we are done if no rows in T1
select row_count() into new_rows;
if show_progress then
select concat(new_rows, " new rows found");
end if;
if not new_rows then
leave recursion;
end if;
# Prepare next iteration:
# T1 becomes T0, to be the source of next run of recursive_SELECT,
# T0 is recycled to be T1.
SET #str =
CONCAT("ALTER TABLE ", recursive_table, " RENAME ", recursive_table_tmp);
PREPARE stmt FROM #str;
EXECUTE stmt;
# we use ALTER TABLE RENAME because RENAME TABLE does not support temp tables
SET #str =
CONCAT("ALTER TABLE ", recursive_table_next, " RENAME ", recursive_table);
PREPARE stmt FROM #str;
EXECUTE stmt;
SET #str =
CONCAT("ALTER TABLE ", recursive_table_tmp, " RENAME ", recursive_table_next);
PREPARE stmt FROM #str;
EXECUTE stmt;
# empty T1
SET #str =
CONCAT("TRUNCATE TABLE ", recursive_table_next);
PREPARE stmt FROM #str;
EXECUTE stmt;
until 0 end repeat;
# eliminate T0 and T1
SET #str =
CONCAT("DROP TEMPORARY TABLE ", recursive_table_next, ", ", recursive_table);
PREPARE stmt FROM #str;
EXECUTE stmt;
# Final (output) SELECT uses recursive_table name
SET #str =
CONCAT("ALTER TABLE ", recursive_table_union, " RENAME ", recursive_table);
PREPARE stmt FROM #str;
EXECUTE stmt;
# Run final SELECT on UNION
SET #str = final_SELECT;
PREPARE stmt FROM #str;
EXECUTE stmt;
# No temporary tables may survive:
SET #str =
CONCAT("DROP TEMPORARY TABLE ", recursive_table);
PREPARE stmt FROM #str;
EXECUTE stmt;
# We are done :-)
END|
delimiter ;
'Common Table Expression' feature is not available in MySQL, so you have to go to make a view or temporary table to solve, here I have used a temporary table.
The stored procedure mentioned here will solve your need. If I want to get all my team members and their associated members, this stored procedure will help:
----------------------------------
user_id | team_id
----------------------------------
admin | NULL
ramu | admin
suresh | admin
kumar | ramu
mahesh | ramu
randiv | suresh
-----------------------------------
Code:
DROP PROCEDURE `user_hier`//
CREATE DEFINER=`root`#`localhost` PROCEDURE `user_hier`(in team_id varchar(50))
BEGIN
declare count int;
declare tmp_team_id varchar(50);
CREATE TEMPORARY TABLE res_hier(user_id varchar(50),team_id varchar(50))engine=memory;
CREATE TEMPORARY TABLE tmp_hier(user_id varchar(50),team_id varchar(50))engine=memory;
set tmp_team_id = team_id;
SELECT COUNT(*) INTO count FROM user_table WHERE user_table.team_id=tmp_team_id;
WHILE count>0 DO
insert into res_hier select user_table.user_id,user_table.team_id from user_table where user_table.team_id=tmp_team_id;
insert into tmp_hier select user_table.user_id,user_table.team_id from user_table where user_table.team_id=tmp_team_id;
select user_id into tmp_team_id from tmp_hier limit 0,1;
select count(*) into count from tmp_hier;
delete from tmp_hier where user_id=tmp_team_id;
end while;
select * from res_hier;
drop temporary table if exists res_hier;
drop temporary table if exists tmp_hier;
end
This can be called using:
mysql>call user_hier ('admin')//
That feature is called a common table expression
http://msdn.microsoft.com/en-us/library/ms190766.aspx
You won't be able to do the exact thing in mySQL, the easiest thing would to probably make a view that mirrors that CTE and just select from the view. You can do it with subqueries, but that will perform really poorly. If you run into any CTEs that do recursion, I don't know how you'd be able to recreate that without using stored procedures.
EDIT:
As I said in my comment, that example you posted has no need for a CTE, so you must have simplified it for the question since it can be just written as
SELECT article.*, userinfo.*, category.* FROM question
INNER JOIN userinfo ON userinfo.user_userid=article.article_ownerid
INNER JOIN category ON article.article_categoryid=category.catid
WHERE article.article_isdeleted = 0
ORDER BY article_date DESC Limit 1, 3
I liked #Brad's answer from this thread, but wanted a way to save the results for further processing (MySql 8):
-- May need to adjust the recursion depth first
SET ##cte_max_recursion_depth = 10000 ; -- permit deeper recursion
-- Some boundaries
set #startDate = '2015-01-01'
, #endDate = '2020-12-31' ;
-- Save it to a table for later use
drop table if exists tmpDates ;
create temporary table tmpDates as -- this has to go _before_ the "with", Duh-oh!
WITH RECURSIVE t as (
select #startDate as dt
UNION
SELECT DATE_ADD(t.dt, INTERVAL 1 DAY) FROM t WHERE DATE_ADD(t.dt, INTERVAL 1 DAY) <= #endDate
)
select * FROM t -- need this to get the "with"'s results as a "result set", into the "create"
;
-- Exists?
select * from tmpDates ;
Which produces:
dt |
----------|
2015-01-01|
2015-01-02|
2015-01-03|
2015-01-04|
2015-01-05|
2015-01-06|