joined table rows into columns with column titles from original rows - mysql

I have three MYSQL tables, simplified, with following columns:
ModuleEntries (MDE_ID)
ModuleFields (MDF_ID, MDF_Label)
ModuleEntryFieldValues (MFV_ID, MFV_MDE_ID, MFV_MDF_ID, MFV_Value)
As you can see from the simplified column list, ModuleEntryFieldValues is the table which states that "for an entry, this field has this value".
Now, I would like to retrieve this information in one "flat" recordset. I have managed to get things working, but not entirely to what i want.
With the help of a different SO article, I managed to get the dynamic, variable amount of columns to work, by using a cursor. declare finish int default 0;
declare fldid int;
declare str varchar(10000) default 'SELECT *,';
declare curs cursor for select MDF_ID from ModuleFields group by MDF_ID;
declare continue handler for not found set finish = 1;
open curs;
my_loop:loop
fetch curs into fldid;
if finish = 1 then
leave my_loop;
end if;
set str = concat(str, 'max(case when MFV_MDF_ID = ',fldid,' then MFV_Value else null end) as field_',fldid,',');
end loop;
close curs;
set str = substr(str,1,char_length(str)-1);
set #str = concat(str, ' from ModuleEntries LEFT join ModuleEntryFieldValues ON MDE_ID = MDF_MDE_ID GROUP BY MDE_ID');
prepare stmt from #str;
execute stmt;
deallocate prepare stmt;
What this code doesn't do, is allow me to put the column values of MDF_Label as the actual column headers of the rows. Now, the above code gives me "field_1, field_2, ..."
Is it possible to join these three tables, and have the MDF_Label as column header for the rows that are now columns in the joined table ?
I want this...
ModuleEntries | ModuleFields | ModuleEntryFieldValues
------------- | ------------------ | -----------------------------------
MDE_ID | MDF_ID - MDF_Label | MFV_MDE_ID - MFV_MDF_ID - MFV_Value
1 | 1 - Height | 1 - 1 - 120cms
| 2 - Width | 1 - 2 - 30cms
| 3 - Weight |
into this
Recordset
---------
MDE_ID - Height - Width - Weight
1 - 120cms - 30cms - null
I hope my question was clear enough. If not please comment and I will give more information where needed, if I can.

I'd just use GROUP_CONCAT() instead of cursors to generate the prepared statement:
SELECT CONCAT(
' SELECT MDE_ID,'
, GROUP_CONCAT(
't', MDF_ID, '.MFV_Value AS `', REPLACE(MDF_Label, '`', '``'), '`'
)
,' FROM ModuleEntries '
, GROUP_CONCAT(
'LEFT JOIN ModuleEntryFieldValues AS t', MDF_ID, '
ON t', MDF_ID, '.MFV_MDE_ID = ModuleEntries.MDE_ID
AND t', MDF_ID, '.MFV_MDF_ID = ', MDF_ID
SEPARATOR ' ')
) INTO #qry FROM ModuleFields;
Save for whitespace editing to make it more readable, with your sample data #qry would then contain:
SELECT MDE_ID,
t1.MFV_Value AS `Height`,
t2.MFV_Value AS `Width`,
t3.MFV_Value AS `Weight`
FROM ModuleEntries
LEFT JOIN ModuleEntryFieldValues AS t1
ON t1.MFV_MDE_ID = ModuleEntries.MDE_ID AND t1.MFV_MDF_ID = 1
LEFT JOIN ModuleEntryFieldValues AS t2
ON t2.MFV_MDE_ID = ModuleEntries.MDE_ID AND t2.MFV_MDF_ID = 2
LEFT JOIN ModuleEntryFieldValues AS t3
ON t3.MFV_MDE_ID = ModuleEntries.MDE_ID AND t3.MFV_MDF_ID = 3
One can then prepare and execute that statement:
PREPARE stmt FROM #qry;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #qry = NULL;
Which yields the following results:
MDE_ID Height Width Weight
1 120cms 30cms (null)
See it on sqlfiddle.

Related

SQL/MYSQL /Transpose table / Columns into rows and rows sum as column with new header

i have a table like this
A B C
1 4 7
2 5 8
3 6 9
And i want result like this
Columns Values
A sum(A) = 6
B sum(B) = 15
C sum(C) = 24
Its simple in Excel sheets but im stuck in MySql
Appreciate the help
Thanks
-- SeasonType,Sacks,SacksYards are columns
select SeasonType,
MAX(IF(SeasonType = '1', Sacks, null)) AS 'Q1',
MAX (IF(SeasonType = '1', SacksYards, null)) AS 'Q2'
from t3 GROUP BY SeasonType
-- union all attempt column sacks,sacksyards table --
-- fantasydefencegame
select 'Sacks' as Sacks, 'SackYards' as SackYards, 0 as SortOrder
union all
select Sum(Sacks) total from fantasydefensegame
union
select Sum(SackYards) from fantasydefensegame
union
select sum(PointsAllowed) from fantasydefensegame
group by SeasonType
select sum(Sacks) sacks from t3
union all
select sum(SackYards) sackyards from t3 group by SeasonType
**-- Another rough Attempt on Temp table**
Select sum(Sacks),sum(Sackyards) from t5
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'max(case when Season = '2009' ''',
Season,
''' then field_value end) ',
Season
)
) INTO #sql
FROM
t5;
SET #sql = CONCAT('SELECT Sacks, ', #sql, '
FROM t5
GROUP BY Season');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
This should fairly give you some ideas. Supposing we are using a test database named testdb and your original table is named test which has 3 columns i.e a,b,c . The three rows in the table are just like what you provided before. Next we can proceed to create a stored procedure. Note: The reason behind using a prepared statement to get the sum value for each column is due to the rules that column names have to be hardcoded , which can not be replaced with variables. e.g select sum(a) from test; can not be written as select sum(#column_name) from test;. By using a prepared statement, we can hardcode the column name dynamically.
delimiter //
drop procedure if exists table_sum//
create procedure table_sum (db_name varchar(20),tb_name varchar(20))
begin
declare col_name varchar(10);
declare fin bool default false;
declare c cursor for select column_name from information_schema.columns where table_schema=db_name and table_name=tb_name;
declare continue handler for not found set fin=true;
drop temporary table if exists result_tb;
create temporary table result_tb (`Columns` varchar(10),`Values` varchar(25));
open c;
lp:loop
fetch c into col_name;
if fin=true then
leave lp;
end if;
set #stmt=concat('select sum(',col_name,') into #sum from test ;');
prepare stmt from #stmt;
execute stmt;
deallocate prepare stmt;
set #val=concat('sum(',col_name,') = ',#sum);
insert result_tb values(col_name,#val);
end loop lp;
close c;
select * from result_tb;
end//
delimiter ;
Finally we call the procedure to get the desired output:
call table_sum('testdb','test');
I would avoid prepared statements and dynamic sql unless I really need it. And I would use such powerful tool when I need to generalize on a value that increases, on a large set of columns.
In your specific case of the shared columns, you could use a simple approach that does the union of the three columns with their respective sum.
SELECT 'A' AS `Columns`,
SUM(A) AS `Values`
FROM tab
UNION
SELECT 'B' AS `Columns`,
SUM(B) AS `Values`
FROM tab
UNION
SELECT 'C' AS `Columns`,
SUM(C) AS `Values`
FROM tab
Check the demo here.

MySQL / Mariadb - Select * from Stored Procedure

I am attempting to use UF Dashbuilder which adds syntax to my SQL query and I don't know how to correct it.
The call that works is similar to:
call database.report ('1234', 'txt');
Dashbuilder turns it into this which does not work:
SELECT * FROM (call database.report ('1234', 'txt');) AS `dbSQL` LIMIT 1
I could also use the code below from the end of the stored procedure to store the results in a table and then SELECT * FROM TABLE in Dashbuilder, but I don't know how to store the results in a table (dynamic number of columns).
Can you please tell me how I can make a stored procedure work with SELECT * added or how I can store the results from this code in a table?
SET #sql = NULL;
SET SESSION GROUP_CONCAT_MAX_LEN = 1000000; -- default is 1024
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(question = ''', REPLACE(question,"'", "\\'"), ''', answer, NULL)) AS ''', REPLACE(question,"'", "\\'"), ''''
)
) INTO #sql
FROM tmp2;
SET #sql = CONCAT('SELECT id, datestamp, ', #sql, ' FROM tmp2 GROUP BY id');
-- SELECT #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE 1.
COMMENTS TO ANSWER 1:
Results from original #SQL:
`SET #sql = CONCAT('SELECT id, datestamp, ', #sql, ' FROM tmp2 GROUP BY id');`
SELECT id, datestamp, MAX(IF(question = '1. Our records show that you got care from the provider named below in the last 6 months. {Provider}.  Is that right?', answer, NULL))
AS '1. Our records show that you got care from the provider named below in the last 6 months. {Provider}.  Is that right?',
MAX(IF(question = '2. Is this the provider you usually see if you need a check-up, want advice about a health problem, or get sick or hurt?', answer, NULL))
AS '2. Is this the provider you usually see if you need a check-up, want advice about a health problem, or get sick or hurt?',
MAX(IF(question = 'Area', answer, NULL)) AS 'Area',MAX(IF(question = 'Encounter', answer, NULL)) AS 'Encounter' FROM tmp2 GROUP BY id
Results from #SQL:
ERROR: Error Code: 1166. Incorrect column name '1. Our records show that you got care from the provider named below in the last 6 months. {Provider}'
I guess there is a character that it does not like such as the single quotes?
SET #tableName = 'myreport';
SET #sql = CONCAT('CREATE TABLE ', #tableName, ' AS SELECT id, datestamp, ', #sql, ' FROM tmp2 GROUP BY id');
CREATE TABLE myreport AS SELECT id, datestamp, MAX(IF(question = '1. Our records show that you got care from the provider named below in the last 6 months. {Provider}.  Is that right?', answer, NULL))
AS '1. Our records show that you got care from the provider named below in the last 6 months. {Provider}.  Is that right?',
MAX(IF(question = '2. Is this the provider you usually see if you need a check-up, want advice about a health problem, or get sick or hurt?', answer, NULL))
AS '2. Is this the provider you usually see if you need a check-up, want advice about a health problem, or get sick or hurt?',
MAX(IF(question = 'Area', answer, NULL)) AS 'Area',MAX(IF(question = 'Encounter', answer, NULL)) AS 'Encounter' FROM tmp2 GROUP BY id
UPDATE 2:
THIS WORKS!!! THANKS!
I have to reduce the length of the column name as shown below. Then I can run the stored procedure twice per day and select * from this table in Dashbuilder.
CREATE TABLE myreport AS SELECT id, datestamp, MAX(IF(question = '1. Our records show that you got care from the provider named below in the last 6 months. {Provider}.  Is that right?', answer, NULL))
AS '1.',
MAX(IF(question = '2. Is this the provider you usually see if you need a check-up, want advice about a health problem, or get sick or hurt?', answer, NULL))
AS '2.',
MAX(IF(question = 'Area', answer, NULL)) AS 'Area',MAX(IF(question = 'Encounter', answer, NULL)) AS 'Encounter' FROM tmp2 GROUP BY id
UPDATE 3: This works! Thanks!
SET #t = CONCAT('DROP TABLE IF EXISTS ', survey_report );
PREPARE stmt FROM #t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #sql = NULL;
SET SESSION GROUP_CONCAT_MAX_LEN = 1000000; -- default is 1024
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'MAX(IF(question = ''', REPLACE(question,"'", "\\'"), ''', answer, NULL)) AS ''', REPLACE(udf_clean_column_name(15, udf_remove_tags(question)),"'", "\\'"), ''''
)
) INTO #sql
FROM tmp2;
SET #sql = CONCAT('CREATE TABLE ', survey_report, ' AS SELECT id, datestamp, ipaddr, ', #sql, ' FROM tmp2 GROUP BY id');
-- SELECT #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
DROP TEMPORARY TABLE IF EXISTS `survey_lookup`;
DROP TEMPORARY TABLE IF EXISTS `tmp`;
DROP TEMPORARY TABLE IF EXISTS `tmp2`;
Mysql Function to remove spaces, etc. from the column names.
CREATE FUNCTION `udf_clean_column_name`(col_name_len INT, str varchar(200)) RETURNS varchar(200)
BEGIN
SET str = SUBSTRING(str,1,col_name_len);
SET str = TRIM(str);
SET str = Replace(str,' ','_');
SET str = Replace(str,'-','');
SET str = Replace(str,'?','');
SET str = Replace(str,',','');
SET str = Replace(str,'.','');
RETURN str;
END;
Mysql function to remove tags (I don't recall where I got this function).
CREATE FUNCTION `udf_remove_tags`(Dirty varchar(4000))
RETURNS varchar(4000)
DETERMINISTIC
BEGIN
DECLARE iStart, iEnd, iLength int;
WHILE Locate( '<', Dirty ) > 0 And Locate( '>', Dirty, Locate( '<', Dirty )) > 0 DO
BEGIN
SET iStart = Locate( '<', Dirty ), iEnd = Locate( '>', Dirty, Locate('<', Dirty ));
SET iLength = ( iEnd - iStart) + 1;
IF iLength > 0 THEN
BEGIN
SET Dirty = Insert( Dirty, iStart, iLength, '');
set Dirty = Replace(Dirty,' ',''); #No space between & and nbsp;
set Dirty = Replace(Dirty,'\r','');
set Dirty = Replace(Dirty,'\n','');
END;
END IF;
END;
END WHILE;
RETURN Dirty;
END;
I don't see any mention of stored procedures in the UF DashBuilder documentation, so it looks like they don't have a way around this.
You can create a table from a SELECT query. If you omit the column specifications, they'll be derived automatically from the select list of the query.
SET #sql = CONCAT('CREATE TABLE ', tableName, ' AS
SELECT id, datestamp, ', #sql, ' FROM tmp2 GROUP BY id');
PREPARE stmt FROM #sql
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

Mysql transpose complex sql query table dynamically

I have a table with two columns, following is the schema:
create table scan( `application_name` varchar(255) NOT NULL, `defect_type` varchar(255) NOT NULL);
And the data is populated accordingly. The table stores data for "Application" and its corresponding "Defect Type". I want to perform following 2 actions on this table:
Get the Top 3 "Defect Type" of a particular application in terms of percent.
Transpose the output from above where the values in "Defect Type" (defect_type) become the columns and its corresponding percent (finalPercent) as the its value.
I am able to achieve 1, following is the SQL Fiddle:
SQLFiddle
However, I am not able to transpose the output as per the requirement. Ideally there should be a single row as follows after both 1 & 2 together:
application_name | CrossSide | CSS | XML
A | 33.33 | 33.33 | 16.67
Thank you!
You can build a dynamic pivot query with group_concat and then execute it:
set #sql = null;
set #total = (select count(*) as totalcount from scan where application_name = "a");
select group_concat(distinct
concat(
'round(((count(case when defect_type = ''',
defect_type,
''' then application_name end)/ #total ) * 100 ), 2) as `',
defect_type, '`'
)
) into #sql
from ( select defect_type
from scan
where application_name = "a"
group by defect_type
order by count(defect_type) desc
limit 3) t;
set #sql = concat(
'select application_name, ', #sql,
' from scan
where application_name = "a"
group by application_name'
);
prepare stmt from #sql;
execute stmt;
deallocate prepare stmt;
SQLFiddle

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.

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|