SQL hierarchical query performance issue - mysql

I have the following table 1 which depicts the relationships between each parent node and its immediate child nodes. For example, A->C, B->D, C->E etc.
TABLE 1 :
NAME
TYPE
NODE_NAME
NODE_TYPE
A
X1
C
X2
B
X1
D
X0
C
X2
E
X0
D
X0
NULL
NULL
E
X0
NULL
NULL
TABLE 2 :
NAME
TYPE
NODE_NAME
NODE_TYPE
A
X1
C
X2
A
X1
E
X0
B
X1
D
X0
C
X2
E
X0
I would like to transform table 1 -> table 2 as shown above.
Basically, it list all the child nodes of parent A (for example : A->C, A->E)
How can I optimize my code? It takes forever to run on 7600+ rows due to cycles i believe
Context : Mysql doesn't support NOCYCLE, what are my options?
SELECT
NAME,
TYPE,
CONNECT_BY_ROOT NODE_NAME,
CONNECT_BY_ROOT NODE_TYPE
FROM TABLE_1
CONNECT BY
NODE_NAME = PRIOR NAME
AND NODE_TYPE = PRIOR TYPE
AND PRIOR NODE_NAME <> NAME
AND PRIOR NODE_TYPE <> TYPE
ORDER BY NODE_TYPE)
;

You can use:
WITH RECURSIVE rcte (name, type, node_name, node_type) AS (
SELECT *
FROM TABLE1
WHERE node_name IS NOT NULL
AND node_type IS NOT NULL
UNION ALL
SELECT r.name, r.type, t.node_name, t.node_type
FROM rcte AS r
INNER JOIN TABLE1 t
ON (r.node_name = t.name AND r.node_type = t.type)
WHERE t.node_name IS NOT NULL
AND t.node_type IS NOT NULL
)
SELECT *
FROM rcte;
Which, for the sample data:
CREATE TABLE TABLE1(
NAME VARCHAR(1),
TYPE VARCHAR(2),
NODE_NAME VARCHAR(1) REFERENCES TABLE1(name),
NODE_TYPE VARCHAR(2) REFERENCES TABLE1(type)
);
INSERT INTO TABLE1 (name, type, node_name, node_type)
SELECT 'A', 'X1', 'C', 'X2' UNION ALL
SELECT 'B', 'X1', 'D', 'X0' UNION ALL
SELECT 'C', 'X2', 'E', 'X0' UNION ALL
SELECT 'D', 'X0', NULL, NULL UNION ALL
SELECT 'E', 'X0', NULL, NULL;
Outputs:
name
type
node_name
node_type
A
X1
C
X2
B
X1
D
X0
C
X2
E
X0
A
X1
E
X0
db<>fiddle here

Related

How to repeat a row N times based on a value within the row

I have a csv file containing three columns, class,malecount and femalecount as an input table.
My output should contain two columns named Class and Gender.
The malecount and femalecount values indicates how many times a row should be repeated. i.e. for Class = A and malecount=2, the row (A,M) should appear twice, and for Class = C and femalecount=3, the row (C,F) should appear three times. Check the following image to see the full output.
enter image description here
DDL & DML for the table:
create table mytable (class text, malecount int, femalecount int);
insert into mytable (class, malecount, femalecount) values
( 'A',2,1),
('B',3,1),
('C',0,3),
('D',2,4);
WITH RECURSIVE
-- define maximal amount of rows per class per gender to be generated
cte1 AS ( SELECT MAX(GREATEST(malecount, femalecount)) max_count
FROM test),
-- generate natural numbers till max. amount found above
cte2 AS ( SELECT 1 num
UNION ALL
SELECT num+1
FROM cte1
CROSS JOIN cte2
WHERE cte2.num <= cte1.max_count)
-- generate rows for male
SELECT test.class, 'm' gender
FROM test
JOIN cte2 ON cte2.num <= test.malecount
UNION ALL
-- generate rows for female
SELECT test.class, 'f'
FROM test
JOIN cte2 ON cte2.num <= test.femalecount
-- final sorting
ORDER BY gender DESC, class
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=694dbb214e4c0cd5524800c56a02dc65
I used the LPAD function and then trimmed off the last comma
from there I had a comma delimited string like M,M,M and F,F
then I used json_table to extract the M,M,M into three rows and the F,F into two rows etc.
here is the fiddle https://www.db-fiddle.com/f/jEXes6AttKvc9GKx1mKY2/1
Schema (MySQL v8.0)
create table mytable (class text, malecount int, femalecount int);
insert into mytable (class, malecount, femalecount) values
( 'A',2,1),
('B',3,1),
('C',0,3),
('D',2,4);
Query #1
with t as (select class,
LPAD(' ', malecount * 2 + 1, 'M,') malecount,
LPAD(' ', femalecount * 2 + 1, 'F,') femalecount
from mytable),
t2 as(
select class,
LEFT(malecount,length(malecount)-1) malecount,
LEFT(femalecount,length(malecount)-1) femalecount
from t)
select t2.class, j.name
from t2
join json_table(
replace(json_array(t2.malecount), ',', '","'),
'$[*]' columns (name varchar(50) path '$')
) j where j.name = 'M'
union all
select t2.class, k.name
from t2
join json_table(
replace(json_array(t2.femalecount), ',', '","'),
'$[*]' columns (name varchar(50) path '$')
) k where k.name = 'F';
class
name
A
M
A
M
B
M
B
M
B
M
D
M
D
M
A
F
B
F
D
F
D
F
View on DB Fiddle
You can use Recursive CTE as the following:
with recursive cte as
(
select *,0 as repeats from
(select class, malecount as cnt, 'M' as Gender from Tbl
union
select class, femalecount as cnt, 'F' as Gender from Tbl
) D
union all
select class,cnt,Gender, repeats+1 from cte
where repeats<cnt-1
)
select class, gender from cte
where cnt>0
order by gender desc,class;
See a demo from db-fiddle.
You can use the function,the expression:
CASE input_expression
WHEN when_expression THEN
result_expression [...n ] [
ELSE
else_result_expression

modify the values achieved through group_Concat in MySql?

I have the following [table a]
id res1 res2
1 a f
1 b f
1 b f
1 c f
2 e g
2 e g
2 e g
2 f g
I'm getting the following after doing a group_concat
select
id,
group_concat(case when cnt = 1 then res1 else concat(cnt, ' ', res1) end) as r1,
group_concat(case when cnt = 1 then res2 else concat(cnt, ' ', res2) end) as r2
from
(
select id, res1,res2, count(*) as cnt
from [table a]
group by id, res1,res2
) t
group by id;
id r1 r2
1 a,2 b,c f,2 f,f
2 3 e,f 3 g,g
The res1 column is coming fine BUT res2 column is duplicating the res1 column.
Basically i want to print the value of how many times a character occurs before the character.
.I want in the following format..
id r1 r2
1 a,2 b,c 4 f
2 3 e,f 4 g
How can I achieve it ?
The way I would approach this is to do two rollups/aggregations, using two separate subqueries for the res1 and res2 columns. The first aggregation is over id and res1 (or res2), and obtains the counts for each letter or word. Then, aggregate again, this time only over the id, to obtain a comma separated string for each id. Finally, join these subqueries together to obtain the final result.
SELECT
t1.id, t1.r1, t2.r2
FROM
(
SELECT t.id, GROUP_CONCAT(res1agg ORDER BY res1) AS r1
FROM
(
SELECT
id,
res1,
CASE WHEN COUNT(*) = 1 THEN res1
ELSE CONCAT(CAST(COUNT(*) AS CHAR(50)), res1) END AS res1agg
FROM yourTable
GROUP BY id, res1
) t
GROUP BY t.id
) t1
INNER JOIN
(
SELECT t.id, GROUP_CONCAT(res2agg ORDER BY res2) AS r2
FROM
(
SELECT
id,
res2,
CASE WHEN COUNT(*) = 1 THEN res2
ELSE CONCAT(CAST(COUNT(*) AS CHAR(50)), res2) END AS res2agg
FROM yourTable
GROUP BY id, res2
) t
GROUP BY t.id
) t2
ON t1.id = t2.id;
Output:
Demo here:
Rextester
Just added 2 more conditions in your query without using more inner queries.
Try this:-
input:-
CREATE TABLE Table1 (id INT, res1 varchar(20), res2 varchar(20));
insert into Table1 values(1, 'a', 'f');
insert into Table1 values(1, 'b', 'f');
insert into Table1 values(1, 'b', 'f');
insert into Table1 values(1, 'c', 'f');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'e', 'g');
insert into Table1 values(2, 'f', 'g');
Query:-
Select t.id,group_concat(case when cnt = 1 then res1 else concat(cnt, ' ', res1) end) as r1,
case when id=1 then trim(concat(sum(case when id = 1 then cnt end),' ',res2))
else trim(concat(sum(case when id = 2 then cnt end),' ',res2)) end as r2
from
(
select id, res1,res2,count(*) as cnt
from table1 a
group by id, res1,res2
) t
group by t.id
My Output:-
id r1 r2
1 a,c,2 b 4 f
2 f,3 e 4 g
Let me know if you have any questions

Reassemble concatenated list with values from lookup table

I have 2 tables, T1 and T2. I want to join these 2 tables and return only 2 rows of data, replacing the integers in Item with their lookup values from T2.
Table T1
Item Date
------ ---------
1;4;5; 3/13/2013
1;2;3; 3/13/2013
Table T2
ID Desc
---- ------
1 Tree
2 Grass
3 Sand
4 Water
5 Bridge
Expected results:
Item Date
------------------ ---------
Tree;Water;Bridge; 3/13/2013
Tree;Grass;Sand; 3/13/2013
First, create a Split function which returns an integer and an order-preserving sequence number. Here is one example:
ALTER FUNCTION dbo.SplitInts
(
#List VARCHAR(MAX),
#Delimiter VARCHAR(32)
)
RETURNS TABLE
AS
RETURN
(
SELECT rn = ROW_NUMBER() OVER (ORDER BY Number),
Item = CONVERT(INT, Item)
FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(#List, Number,
CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number)))
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects) AS n(Number)
WHERE Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, 1) = #Delimiter
) AS y
);
GO
Then the following query does what you're after:
DECLARE #t1 TABLE
(
Item VARCHAR(MAX),
[Date] DATE -- terrible column name!
);
INSERT #t1 VALUES('1;4;5;','20130313'),('1;2;3;','20130313');
-- please use unambiguous date formats!
DECLARE #t2 TABLE
(
ID INT, -- another bad column name - what kind of ID?
[Desc] VARCHAR(255) -- another bad column name, this is a keyword!
);
INSERT #t2 VALUES(1,'Tree'),(2,'Grass'),
(3,'Sand'),(4,'Water'),(5,'Bridge');
;WITH x AS
(
SELECT t1.Item, Date, t2ID = i.Item, i.rn, n = t2.[Desc]
FROM #t1 AS t1 CROSS APPLY dbo.SplitInts(t1.Item, ';') AS i
INNER JOIN #t2 AS t2 ON i.Item = t2.ID
)
SELECT DISTINCT Item = (
SELECT n + ';' FROM x AS x2
WHERE x.Item = x2.Item
ORDER BY x2.rn FOR XML PATH,
TYPE).value(N'./type()[1]', N'varchar(max)'), [Date]
FROM x;
Strongly recommend you research normalization. A semi-colon-separated list is a terrible way to cram together independent values.

Unable to find the matching child record using recursive CTE

I have the following table #t:
ParentId SkuName ChildId
P1 X1 C1
C1 X2 C2
C2 X3 C2
If I pass the ParentId = P1, the desired output is x3
i.e. the stopping condition is the last row matching record and get the sku name for that
row. If no row matched, then return null
My attempt (no working though)
DECLARE #t TABLE (ParentId Varchar(100), Name VARCHAR(20), ChildId Varchar(100))
INSERT INTO #t(ParentId, Name, ChildId)
SELECT 'P1', 'X1', 'C1' UNION ALL
SELECT 'C1', 'X2', 'C2' UNION ALL
SELECT 'C2', 'X3', 'C2'
Declare #ParentId Varchar(10) = 'P1'
;With CTE As
(
Select
Rn = ROW_NUMBER() Over(Order By (Select 1))
,ParentId
, Name
, ChildId
From #t Where ParentId = #ParentId
Union All
Select
Rn + 1
,pc.ParentId as Parents
,pc.Name
,pc.ChildId
FROM #t pc
JOIN CTE gp on pc.Childid = gp.Parentid
)
Select *
From CTE
Please help
The problem is with the JOIN in your second UNION query. You are joining on pc.Childid = gp.Parentid. You should be joining on pc.ParentId = gp.Childid.
Also, since your data has both the child and the parent as the same value, you will end up with infinite recursion unless you specify that the recursion should stop when the ParentId equals the Childid. (WHERE gp.ParentId <> gp.Childid)
Is this the result you are looking for?
DECLARE #t TABLE (ParentId VARCHAR(100), Name VARCHAR(20), ChildId VARCHAR(100))
INSERT INTO #t(ParentId, Name, ChildId)
SELECT 'P1', 'X1', 'C1' UNION ALL
SELECT 'C1', 'X2', 'C2' UNION ALL
SELECT 'C2', 'X3', 'C2'
DECLARE #ParentId VARCHAR(10) = 'P1'
;With CTE As
(
SELECT
Rn = 1
,ParentId
,Name
,ChildId
FROM #t WHERE ParentId = #ParentId
UNION All
SELECT
Rn + 1
,pc.ParentId as Parents
,pc.Name
,pc.ChildId
FROM #t pc
JOIN CTE gp
on pc.ParentId = gp.Childid
WHERE gp.ParentId <> gp.Childid
)
SELECT TOP 1 *
FROM CTE
ORDER BY Rn DESC
OPTION (MAXRECURSION 0);

creating sql query for selecting multiple data

i need a sql query for my table tbl1
sample contents of the table like this
serial ida idb
1 1 2
2 1 3
3 3 7
4 3 6
5 2 4
6 2 6
.
in the table tbl1 column ida and idb are related like 1 is related with 2 and 3 , 2 is related with 4 and 6
ida value 1 s related data is 2 and 3 and i want to select the related data of 1' s related data (2 and 3).
2 and 3 s related data is 7, 6 and 4, 6. so the output will be (7,6,4)
. i need a sql query to display this out put. can anyone share some idea how to do that ..
SELECT DISTINCT idb FROM tbl1 WHERE ida = 2 OR ida = 3;
Is this what you were looking for?
EDIT: Corrected determining child branches of the hierarchy.
This may be of some use:
-- Sample data.
declare #Table as table ( serial int identity, ida int, idb int )
insert into #Table ( ida, idb ) values
( 1, 2 ), ( 1, 3 ),
( 3, 7 ), ( 3, 6 ),
( 2, 4 ), ( 2, 6 )
select * from #Table
-- Demonstrate recursive query.
; with CTE as (
-- Start with ida = 1.
select serial, ida, idb, 1 as depth, path = cast( right( '000000' + cast( ida as varchar(6) ), 6 ) as varchar(1024) )
from #Table
where ida = 1
union all
-- Add each row related to the most recent selected row(s).
select T.serial, T.ida, T.idb, C.depth + 1, cast( C.path + right( '000000' + cast( T.ida as varchar(6) ), 6 ) as varchar(1024) )
from CTE as C inner join
#Table as T on T.ida = C.idb
)
-- Show everything.
select *
from CTE
-- Repeat the recursive query.
; with CTE as (
-- Start with ida = 1.
select serial, ida, idb, 1 as depth, path = cast( right( '000000' + cast( ida as varchar(6) ), 6 ) as varchar(1024) )
from #Table
where ida = 1
union all
-- Add each row related to the most recent selected row(s).
select T.serial, T.ida, T.idb, C.depth + 1, cast( C.path + right( '000000' + cast( T.ida as varchar(6) ), 6 ) as varchar(1024) )
from CTE as C inner join
#Table as T on T.ida = C.idb
)
-- Select only the deepest children.
select distinct idb
from CTE as C
where not exists ( select 42 from CTE where left( path, len( C.path ) ) = C.path and len( path ) > len( C.path ))
order by idb
Left as an exercise is pivoting the result.
select distinct idb from tbl1 where ida in (select idb from tbl1 where ida = 1)