there are a lot of questions about Recursive SELECT query in Mysql, but most of answers is that "There NO solution for Recursive SELECT query in Mysql".
Actually there is a certain solution & I want to know it clearly, so this question is the following of the previous question that can be found at (how-to-do-the-recursive-select-query-in-mysql)
Suppose you have this table:
col1 - col2 - col3
1 - a - 5
5 - d - 3
3 - k - 7
6 - o - 2
2 - 0 - 8
& you want to find all the links that connect to value "1" in col1, i.e. you want to print out:
1 - a - 5
5 - d - 3
3 - k - 7
Then you can use this simple query:
select col1, col2, #pv:=col3 as 'col3' from table1
join
(select #pv:=1)tmp
where col1=#pv
Ok, good, however, if your table has 2 records containing "1" in col1 & 2 records containing "3" in col1, ex:
col1 - col2 - col3
1 - a - 5
1 - m - 9
5 - d - 3
3 - k - 7
6 - o - 2
3 - v - 10
2 - 0 - 8
Then, when users search for "1" in col1, it should show all the links connecting to 2 "1", i.e. it should show this expecting result:
col1 - col2 - col3
1 - a - 5
1 - m - 9
5 - d - 3
3 - k - 7
3 - v - 10
So, my question is how do we modify the above query so that it will show all the links as in the above expecting result?
EDIT: # Gordon,
but if we omit select distinct col1, col2 from then this query means something, can you work on this (since the childID got increased, so we can order the table1 ):
select col1, col2,
#pv:=(case when find_in_set(col3, #pv) then #pv else concat(#pv, ',', col3)
end) as 'col3'
from (select * from table1 order by col1) tb1 join
(select #pv:='1') tmp
on find_in_set(col1, #pv) > 0
In this case, we don't worry about the order, for example, if this is the data:
col1 - col2 - col3
4 - a - 5
1 - d - 2
1 - k - 4
2 - o - 3
6 - k - 8
8 - o - 9
the output will be:
col1 - col2 - col3
1 - d - 1,2
1 - k - 1,2,4
2 - o - 1,2,4,3
So we get this result 1,2,4,3 right? & we just select all records if the col1 is in 1,2,4,3. Then we can get the final expected result.
If that is the case, can you think of any special case that rules out the solution I just mentioned?
I keep wondering if something like this would work:
select distinct col1, col2
from (select col1, col2,
#pv:=(case when find_in_set(col3, #pv) then #pv else concat(#pv, ',', col3)
end) as 'col3'
from table1 join
(select #pv:='1') tmp
on find_in_set(col1, #pv) > 0
) t
Something like this should work for small data sets. However, the idea of putting all the ids in a string is limited to the capacity of a string.
In my limited deep of hierarchy-levels, I used the following:
parents:
select * from mytable
join (
select A.id Aid,B.id Bid, C.id Cid, D.id Did, E.id Eid, F.id Fid,G.id Gid, H.id Hid from mytable A
left join mytable B on B.id=A.parent
left join mytable C on C.id=B.parent
left join mytable D on D.id=C.parent
left join mytable E on E.id=D.parent
left join mytable F on F.id=E.parent
left join mytable G on G.id=F.parent
left join mytable H on H.id=G.parent
where A.id=9
) X
where id in (Aid,Bid,Cid,Did,Eid,Fid,Gid,Hid);
children:
select * from mytable where id in (
select distinct id from mytable
join (
select A.id Aid,B.id Bid, C.id Cid, D.id Did, E.id Eid, F.id Fid,G.id Gid, H.id Hid FROM mytable A
left join mytable B on B.parent=A.id
left join mytable C on C.parent=B.id
left join mytable D on D.parent=C.id
left join mytable E on E.parent=D.id
left join mytable F on F.parent=E.id
left join mytable G on G.parent=F.id
left join mytable H on H.parent=G.id
Where A.id=1
) X
where id in (Aid,Bid,Cid,Did,Eid,Fid,Gid,Hid)
);
Had more of a play. Can't get it to work using the user variables due to the ordering of items.
However if you have a reasonable maximum number of levels then you can do something like this:-
SELECT CONCAT_WS('-', a.allCols, b.allCols, c.allCols, d.allCols, e.allCols, f.allCols, g.allCols, h.allCols, i.allCols, j.allCols, k.allCols, l.allCols, m.allCols)
FROM (SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) a
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) b ON a.col3 = b.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) c ON b.col3 = c.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) d ON c.col3 = d.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) e ON d.col3 = e.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) f ON e.col3 = f.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) g ON f.col3 = g.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) h ON g.col3 = h.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) i ON h.col3 = i.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) j ON i.col3 = j.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) k ON j.col3 = k.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) l ON k.col3 = l.col1
LEFT OUTER JOIN(SELECT col1, col3, CONCAT(col1, col2, col3) AS allCols FROM table1) m ON l.col3 = m.col1
WHERE a.col1 = 1
This is coping with up to 13 levels (OK, only a couple used in your test data), and will give a comma separated bit for each column, with each row joined with a dash (-).
Stored procedure is the best way to do it. Because Gordon's solution would work only if the data follows the same order.
If we have a table structure like this
col1 - col2 - col3
3 - k - 7
5 - d - 3
1 - a - 5
6 - o - 2
2 - 0 - 8
It wont work.
Here is a sample procedure code to achieve the same.
delimiter //
CREATE PROCEDURE chainReaction
(
in inputNo int
)
BEGIN
declare final_id int default NULL;
SELECT col3 into final_id from table1
where col1 = inputNo;
if( final_id is not null) then
insert into results(select col1, col2, col3 from table1 where col1 = inputNo);
CALL chainReaction(final_id);
end if;
END//
delimiter ;
call chainReaction(1);
select * from results;
drop table if exists results;
Related
I have a table of two columns
Col1 Col2
A 1
A 2
A 3
B 1
B 2
B 3
Output I need is like this
Col1 Col2
A 1
A 1,2
A 1,2,3
B 1
B 1,2
B 1,2,3
Thank you in advance.
Here is a solution which would work for MySQL. It uses a correlated subquery in the select clause to group concatenate together Col2 values. The logic is that we only aggregate values which are less than or equal to the current row, for a given group of records sharing the same Col1 value.
SELECT
Col1,
(SELECT GROUP_CONCAT(t2.Col2 ORDER BY t2.Col2) FROM yourTable t2
WHERE t2.Col2 <= t1.Col2 AND t1.Col1 = t2.Col1) Col2
FROM yourTable t1
ORDER BY
t1.Col1,
t1.Col2;
Demo
Here is the same query in Oracle:
SELECT
Col1,
(SELECT LISTAGG(t2.Col2, ',') WITHIN GROUP (ORDER BY t2.Col2) FROM yourTable t2
WHERE t2.Col2 <= t1.Col2 AND t1.Col1 = t2.Col1) Col2
FROM yourTable t1
ORDER BY
t1.Col1,
t1.Col2;
Demo
Note that the only real change is substituting LISTAGG for GROUP_CONCAT.
with s (Col1, Col2) as (
select 'A', 1 from dual union all
select 'A', 2 from dual union all
select 'A', 3 from dual union all
select 'B', 1 from dual union all
select 'B', 2 from dual union all
select 'B', 3 from dual)
select col1, ltrim(sys_connect_by_path(col2, ','), ',') path
from s
start with col2 = 1
connect by prior col2 = col2 - 1 and prior col1 = col1;
C PATH
- ----------
A 1
A 1,2
A 1,2,3
B 1
B 1,2
B 1,2,3
6 rows selected.
In MySQL, I would like to have an extra column showing the sum of values of a particular column. However, the numbers I would like to sum come from a subquery and are not stored in a separate table, something like this:
(SELECT a.ID, MAX(a.COUNT_ID) AS MAX_COUNT FROM
(SELECT ID, COUNT(*) AS COUNT_ID
FROM my_table
GROUP BY COL1, COL2) a
GROUP BY COL1, COL2) b
And this would output something like:
ID MAX_COUNT
ABC 1
DEF 2
GHI 3
And now, I want an extra column showing the sum of MAX_COUNT, like this (repeated over all rows):
ID MAX_COUNT SUM_MAX_COUNT
ABC 1 6
DEF 2 6
GHI 3 6
The actual goal is actually to show the percentage MAX_COUNT of the total MAX_COUNT, so 1/6, 2/6 and 3/6.
How do I do this? I already tried doing a CROSS JOIN but it doesn't work:
SELECT * FROM
((SELECT a.ID, MAX(a.COUNT_ID) AS MAX_COUNT FROM
(SELECT ID, COUNT(*) AS COUNT_ID
FROM my_table
GROUP BY COL1, COL2) a
GROUP BY COL1, COL2) b
CROSS JOIN (SELECT SUM(b.MAX_COUNT)) AS c
Error: Unknown table 'b'
EXAMPLE
Example table:
CREATE TABLE TABLE1 (
COL1 varchar(255),
COL2 varchar(255),
DAY int,
HOUR int);
INSERT INTO TABLE1 VALUES
('X','Y',1,12),
('X','Y',1,13),
('X','Y',1,13),
('A','B',2,19),
('X','B',3,13),
('X','B',3,13);
Now I want to have, for each combination of COL1 and COL2, the number of lines in this table for each hour:
SELECT COL1, COL2, HOUR, COUNT(*) AS COUNT_LINES
FROM TABLE1
GROUP BY DAY, HOUR, COL1, COL2;
Which outputs this:
COL1 COL2 HOUR COUNT_LINES
X Y 12 1
X Y 13 2
A B 19 1
X B 13 2
Now I want, for each combination of COL1 and COL2, the maximum of COUNT_LINES, so I use the query above in a subquery:
SELECT a.COL1, a.COL2, MAX(a.COUNT_LINES)
FROM
(SELECT COL1, COL2, HOUR, COUNT(*) AS COUNT_LINES
FROM TABLE1
GROUP BY DAY, HOUR, COL1, COL2) a
GROUP BY COL1, COL2;
Which outputs this:
COL1 COL2 MAX(COUNT_LINES)
A B 1
X B 2
X Y 2
Now in the last step, I want the SUM of MAX(COUNT_LINES) column in a separate column, like this:
COL1 COL2 MAX(COUNT_LINES) SUM(MAX(COUNT_LINES))
A B 1 5
X B 2 5
X Y 2 5
But that is the part I don't know how to do.
Try this:
SELECT COL1,Max(COUNT_ID),Sum(SUM_MAX_COUNT) FROM(
SELECT COL1,
Count(*) AS COUNT_ID,
(SELECT Sum(Count(*))
FROM TABLE_NAME
GROUP BY COL1) AS SUM_MAX_COUNT
FROM TABLE_NAME
GROUP BY COL1)
GROUP BY COL1;
Use CTE for this
with q as
(
SELECT a.COL1, a.COL2, MAX(a.COUNT_LINES) cmax
FROM
(SELECT COL1, COL2, HOUR, COUNT(*) AS COUNT_LINES
FROM TABLE1
GROUP BY DAY, HOUR, COL1, COL2) a
GROUP BY COL1, COL2
)
SELECT q.*, (SELECT SUM(q.cmax) from q)
FROM q
dbfiddle demo
which can be rewritten (for older MySQL) as
SELECT q.*,
(
SELECT SUM(cmax) from
(
SELECT a.COL1, a.COL2, MAX(a.COUNT_LINES) cmax
FROM
(SELECT COL1, COL2, HOUR, COUNT(*) AS COUNT_LINES
FROM TABLE1
GROUP BY DAY, HOUR, COL1, COL2) a
GROUP BY COL1, COL2
) q
) as max_sum
FROM
(
SELECT a.COL1, a.COL2, MAX(a.COUNT_LINES) cmax
FROM
(SELECT COL1, COL2, HOUR, COUNT(*) AS COUNT_LINES
FROM TABLE1
GROUP BY DAY, HOUR, COL1, COL2) a
GROUP BY COL1, COL2
) q
dbfiddle demo
This seems to give the right answer:
mysql-sql> select
... x.col1,
... x.col2,
... x.max_count_lines,
... y.sum_max_count_lines
... from
... (
... select
... a.col1,
... a.col2,
... max(a.count_lines) as max_count_lines
... from
... (
... select
... col1,
... col2,
... hour,
... count(*) as count_lines
... from
... table1
... group by
... day,
... hour,
... col1,
... col2
... ) a
... group by
... col1,
... col2
... ) x,
... (
... select
... sum(max_count_lines) as sum_max_count_lines
... from
... (
... select
... b.col1,
... b.col2,
... max(b.count_lines) as max_count_lines
... from
... (
... select
... col1,
... col2,
... hour,
... count(*) as count_lines
... from
... table1
... group by
... day,
... hour,
... col1,
... col2
... ) b
... group by
... col1,
... col2
... ) c
... ) y;
+------+------+-----------------+---------------------+
| col1 | col2 | max_count_lines | sum_max_count_lines |
+------+------+-----------------+---------------------+
| A | B | 1 | 5 |
| X | B | 2 | 5 |
| X | Y | 2 | 5 |
+------+------+-----------------+---------------------+
3 rows in set (0.00 sec)
Using SQL Server 2008 tsql, I'm trying to remove ALL the records in a table when a set of values recur. So if my table looked like this:
idcol1col2
1 A 1
2 A 1
3 A 2
4 B 1
5 B 1
6 B 2
Rows 1, 2, 4, 5 would all be deleted.
;WITH d AS
(
SELECT col1, col2, c = COUNT(*) OVER
(PARTITION BY col1, col2 ORDER BY col1)
FROM dbo.yourtable
)
DELETE d WHERE c > 1;
In fact it can be slightly tidier:
;WITH d AS
(
SELECT id, c = COUNT(*) OVER
(PARTITION BY col1, col2 ORDER BY col1)
FROM dbo.yourtable
)
DELETE d WHERE c > 1;
And I'll fess up, I tested the above on SQL Server 2012, however I forgot to change the fiddle to SQL Server 2008. For versions prior to SQL Server 2012, here is one variation:
;WITH d AS
(
SELECT col1, col2
FROM dbo.yourtable AS t
GROUP BY col1, col2
HAVING COUNT(*) > 1
)
DELETE t --*
FROM dbo.yourtable AS t
WHERE EXISTS
(
SELECT 1 FROM d
WHERE col1 = t.col1 AND col2 = t.col2
);
You'd think it would be sufficient to just DELETE d; here but you get:
Msg 4403, Level 16, State 1, Line 2
Cannot update the view or function 'd' because it contains aggregates, or a DISTINCT or GROUP BY clause, or PIVOT or UNPIVOT operator.
Try this:
DELETE t
FROM dbo.yourTabe t
JOIN (
SELECT col1,col2,COUNT(1) cnt
FROM dbo.YourTable
GROUP BY col1, col2
HAVING COUNT(1)>1
) s
ON t.col1 = s.col1
AND t.col2 = s.col2
I have table like:
id col1 col2
1 a 55
2 b 77
In result i want to see:
id col1 col2 MIN(col2)
1 a 55 55
2 b 77
Something like that, or in other case, how i can get one minimum value with whole table.
You can use a CROSS JOIN with a subquery which will select the min(col2) value for the entire table:
select t1.id,
t1.col1,
t1.col2,
t2.minCol2
from yourtable t1
cross join
(
select min(col2) minCol2
from yourtable
) t2
See SQL Fiddle with Demo.
If you want to expand this to only show the min(col2) value on the first row, then you could use user-defined variables:
select id,
col1,
col2,
case when rn = 1 then mincol2 else '' end mincol2
from
(
select t1.id,
t1.col1,
t1.col2,
t2.minCol2,
#row:=case when #prev:=t1.id then #row else 0 end +1 rn,
#prev:=t1.id
from yourtable t1
cross join
(
select min(col2) minCol2
from yourtable
) t2
cross join (select #row:=0, #prev:=null) r
order by t1.id
) d
order by id
See SQL Fiddle with Demo
If you had more than one column that you want to compare, then you could unpivot the data using a UNION ALL query and then select the min value for the result:
select t1.id,
t1.col1,
t1.col2,
t2.MinCol
from yourtable t1
cross join
(
select min(col) MinCol
from
(
select col2 col
from yourtable
union all
select col3
from yourtable
) src
) t2
See SQL Fiddle with Demo
You can't. The number of columns is fixed, so you can get the minimum value on all the rows as described by #bluefeet.
You could get it on a smaller number of rows (typically 1) by using the logic:
(case when t2.minCol2 = t1.col2 then t2.minCol2 end)
But this would put NULLs on the other rows.
I have a table in MySQL:
Col1 | Col2
a A
a B
c C
a B
i want to create a table like this:
col1 | col2 | freq
a A 0.33
a B 0.67
col1 is a specified item in Col1. col2 is distinct item that has occured with the specified item(i.e. a). freq column is the frequency of appearence of item in col2.
Can someone give me a hint of how to create such a query? Thanks a lot.
try this:
Select A.Col1, A.Col2, A.Count1 * 1.0 / B.Count2 As Freq
From (
Select Col1, Col2, Count(*) As Count1
From YourTableName
Group By Col1, Col2
) As A
Inner Join (
Select Col1, Count(*) As Count2
From YourTableName
Group By Col1
) As B
On A.Col1 = B.Col1
You can also use this which is coded in SQL server
DECLARE #Count INT;
SELECT #Count = COUNT(1) FROM YourTableName WHERE Col1 = 'a'
SELECT Col1, Col2, CAST(COUNT(1)* 1.00 /#Count AS DECIMAL(4,2) ) AS Frequency
FROM YourTableName
WHERE Col1 = 'a'
GROUP BY Col1, Col2
this way you have a better performance
With window functions
SELECT Col1, Col2, Count1*1.0 / Count2 AS freq
FROM (
SELECT
Col1,
Col2,
COUNT() OVER(PARTITION BY Col1, Col2) AS Count1,
COUNT() OVER(PARTITION BY Col1) AS Count2
FROM YourTableName
)
GROUP BY Col1, Col2