I am completely new to mysql. Here I am trying to make a query in mysql which divides a column col1 into 4 different columns based on its category(col2) in sorted order as shown below. I have written a query like this till now:
select if(category = 'first',name ,NULL) as first,
if(category = 'second',name,NULL) as second,
if(category = 'third',name,NULL) as third,
if(category = 'fourth',name,NULL) as fourth
from 'table';
this code gives me the four columns but I am stuck now as I am not able to further filter this.
Given table:
name category
John first
Sunil third
Jenny third
Ashley fourth
Meera second
Abhay first
Required answer:
col1 col2 col3 col4
Abhay Meera Jenny Ashley
John NULL Sunil NULL
Note that all the columns in the answer are sorted.
Edit: I guess the question is not clear about the format of final answer. Thanks #philipxy for pointing out. Final answer should be adjusted in least number of rows (which is 2 in my case). All the columns should have equal number of rows and if some column has lesser values then that row will have NULL value in respective columns e.g col2 and col 4 above. Finally all the columns should be in sorted order where NULL will be in the last(if any)e.g Suppose there was an entry named Olly with category fourth then it should appear before NULL in col4 and after Ashley.
This is tricky. You are trying to stack lists vertically, rather than horizontally, which is not a "normal" SQL operation.
You can do what you want by using conditional aggregation. The problem is there is nothing to aggregate by. The solution is to introduce a variable column to calculate a sequence number for aggregation:
select max(case when category = 'first' then name end) as first,
max(case when category = 'second' then name end) as second,
max(case when category = 'third' then name end) as third,
max(case when category = 'fourth' then name end) as fourth
from (select t.*,
(#rn := if(#c = category, #rn + 1,
if(#c := category, 1, 1)
)
) as rn
from `table` t cross join
(select #c := '', #rn := 0) params
order by category
) t
group by rn;
If you want the values in a particular order in each column, then add a second sort key after category.
EDIT:
I should note that if you don't need multiple rows, but just the values, you can concatenate them together:
select group_concat(case when category = 'first' then name end) as firsts,
group_concat(case when category = 'second' then name end) as seconds,
group_concat(case when category = 'third' then name end) as thirds,
group_concat(case when category = 'fourth' then name end) as fourths
from`table` t
group by rn;
Related
I think there is no question like this.
I need to group rows by n records and get some values of this group.
I think is better to explain with a graphic example:
Is possible to do a query like this? if not my solution will be make an script to create another table with this but I donĀ“t like duplicate data at all.
Thanks!!!
set #counter=-1;
select xgroup,max(x) as mx, max(y) as my, avg(value3) as v3,
from
(
select (#counter := #counter +1) as counter,
#counter div 5 as xgroup,
currency, datetime, value1, value2,
case mod(#counter,5) when 0 then value1 else 00 end as x,
case mod(#counter,5) when 4 then value2 else 00 end as y,
mod(#counter,5) as xxx
FROM findata
) name1
group by xgroup;
#jms has the right approach, but you have to be very careful when using variables:
You should not assign a variable in one expression and then reference it in another in the same select.
To work in the most recent versions of MySQL, I would suggest ordering the data in a subquery.
In addition, there are some other values that you need:
select min(col1), min(col2),
max(case when mod(rn, 5) = 0 then col3 end),
max(col4), min(col5),
max(case when mod(rn, 5) or rn = #rn then col6 end),
max(case when mod(rn, 5) or rn = #rn then col7 end)
from (select (#rn := #rn + 1) as rn, t.*
from (select t.*
from t
order by col1, col2
) t cross join
(select #rn := -1) params
) t
group by (#rn div 5);
Note the logic is a bit arcane for the last values -- this is to take into account the final group that might not have exactly 5 rows.
You need a column that looks like(assuming you want to group every 5 rows)
dummy_table
1
1
1
1
1
2
2
2
2
2
...
You can do this by using generate_series() if you are using postgre sql by using
select t1 from (select generate_series(1,x)) t1, (select generate_series(1,5)) t2;
where you can replace x by (total rows/5) i.e. for 100 rows, x = 20. If you are using any other SQL platform, you can just work on creating this dummy table accordingly.
Once you get this dummy_table, join it with your table on row_number of your table with t1 column of dummy_table(not row_number of dummy_table). Syntax for accessing row number should be straightforward.
After the join, group by this t1 column and do the required aggregation. To do this in a single query, you can do the above in an inner query and do aggregation outside it. Hope this makes sense.
Ok, thanks you all guys for your answers, thanks to it I found the simple solution.
I simply add an autoincrement column, and then I can group results by integer division by 5.
And with this query:
SELECT id,
symbol,
datetime,
open,
MAX(high),
MIN(low),
SUBSTRING_INDEX( GROUP_CONCAT(CAST(close AS CHAR) ORDER BY datetime DESC), ',', 1 ) AS close
FROM `table`
GROUP BY (id-1) DIV 5
And the resulting is:
Thanks!
A solution is to introduce some field for grouping rows for aggregative operations.
It can be reached by introducing a user-variable and assigning values that will allow to group rows as required. For example, it can be a row counter divided by grouping chuck size and rounded to nearest upper ceil number:
SET #counter=0;
SELECT CEIL((#counter:=#counter+1)/5) AS chunk, MAX(high), MIN(low) FROM `table` GROUP BY chunk;
I have a table with 3 rows and 3 columns. For all the rows with the same name, I want to retrieve the one that has the minimum value in the position column. in this example here. The result should be (apple, red, 3) and (melon, big, null).
null value in the 'position' column means that fruit is not in the list.
name category position
apple fruit 5
apple red 3
melon big null
The null makes this tricky. I'm not sure if it should be considered "high" or "low". Let me assume "low":
select t.*
from t
where coalesce(t.position, -1) = (select min(coalesce(t2.position, -1))
from t t2
where t2.name = t.name
);
SELECT
f.*
FROM
(
SELECT
name,
MIN(IFNULL(position,0)) as min_position
FROM
fruits
GROUP BY
name
) tmp
LEFT JOIN
fruits f ON
f.name = tmp.name AND
IFNULL(f.position,0) = min_position
-- GROUP BY name
-- optional if multiple (name, position) are possible for example
-- [apple,fruit,5], [apple,red,5]
How to group rows in mysql to be as chunks with a specific size ?
I have a table called users
Which has these fields id name
I want to make a query which group users into chucks with the size of 3 names per row.
Sample table:
1 name1
2 name2
3 name4
4 name5
5 name5
6 name6
7 name7
result should be
name1,name2,name3
name4,name5,name6
name7
Usign GROUP_CONCAT of course to do that
http://sqlfiddle.com/#!9/a6b42/5
SELECT GROUP_CONCAT(name),
(IF(#i = 3 OR #i IS NULL, #i:=1, #i:=#i+1 ))as idx,
(IF(#gr_idx IS NULL, #gr_idx:=1, IF(#i = 1, #gr_idx:=#gr_idx+1,#gr_idx) )) as gr_idx
FROM users
GROUP BY gr_idx
You can use modulo arithmetic and conditional aggregation to get three columns:
select max(case when mod(id, 3) = 1 then name end),
max(case when mod(id, 3) = 2 then name end),
max(case when mod(id, 3) = 0 then name end)
from users u
group by floor((id - 1) / 3);
Alternatively, if you only want one column, use group_concat():
select group_concat(name) as names
from users u
group by floor((id - 1) / 3);
Both these solutions assume that id increases by 1 with no gaps. If not, there are ways to assign a sequential number using variables.
SELECT
GROUP_CONCAT(name SEPARATOR ',')
FROM users
GROUP BY
floor((id - 1) / 3);
Here is an SQL fiddle demonstrating this: http://sqlfiddle.com/#!9/f3158/2/0
If the IDs are not ascending or not succeeding then this query may be it:
SELECT names from (
SELECT GROUP_CONCAT(name) as names,
#rownum := #rownum + 1 AS rank
FROM users u,
(SELECT #rownum := 0) r
GROUP BY floor(#rownum / 3)
) _users ;
Fiddle again: http://sqlfiddle.com/#!9/f3158/13/0
Lets say we have a query
SELECT recordType,SUM(amount) FROM records GROUP BY recordType
Output
1: 50
2: 100
3: 150
4: 200
We have 4 different record types which are 1,2,3 and 4. The above query will obviously return values grouped for those 4 types. How can I adjust the query to show grouping based on their paired grouping. Like return the result of recordType 1 and recordType 2 in one row, and for 3 and 4 in second row.
Expected Output
Group 1: 150
Group 2: 350
Like return the result of recordType 1 and recordType 2 in one row, and for 3 and 4 in second row.
You could do this with a case statement. It won't perform very well, but it'll work. Depending on your actual values, the final query could look something like this:
group by case
when type in(1, 2) then 1
when type in(3, 4) then 2
when ...
else ...
end
Part of the problem is doing the grouping correctly, the other part is getting the names of the groups.
If you really have the numbers in question, you can do:
select concat('Group ', cast((recordType - 1)/2 as int)), count(*)
from records r
group by cast((recordType - 1)/2 as int)
If the values are actually not so arithmetically amenable, then a variable is possibly the simplest method:
select concat('Group ', #rn := #rn + 1), count(*)
from records r cross join (select #rn := 0) const
group by (case when type in (1, 2) then 1
when type in (3, 4) then 2
end)
I have a table that is sorted by id and value in descending order. I want to return all id's that match a group of keys in a specific order. So given (a5, a3) I want to return a and b but not d.
id value key
a 3 a5
a 2 a3
a 1 a4
b 4 a5
b 2 a3
c 6 a1
c 2 a2
d 4 a3
d 2 a5
The expected output would be
id
a
b
So far I've managed to match (a5, a3) but in any order. Here I'm returning all rows and fields that match in any order; not just the id.
SELECT tablename.*
FROM tablename, (SELECT * FROM tablename a
WHERE key IN ('a5', 'a3')
GROUP BY id
HAVING COUNT(*) >= 1) AS result
WHERE tablename.id = result.id
This is an example of a set-within-sets query, although it is a bit more complicated then most.
select id
from tablename t
group by id
having (max(case when "key" = 'a5' then value end) >
max(case when "key" = 'a3' then value end)
);
What this is doing is finding the value for "a5" and "a3" and directly comparing them. If neither is present, then the max(case . . .) will return NULL and the comparison will fail. If there is more than one value for either (or both), then it returns the largest value.
This should be pretty easy to generalize to additional keys in a particular order, by adding more similar clauses. This is why I like the aggregation with having approach to this sort of query -- it works in a lot of cases.
For the "nothing-in-between" case that you mention, I think this will work:
select id
from (select t.*, #rn := #rn + 1 as seqnum
from tablename t cross join (select #rn := 0) const
order by key, value
) t
group by id
having (max(case when "key" = 'a5' then seqnum end) = max(case when "key" = 'a3' then seqnum end) + 1
);
The appends a sequence number and then checks that they are consecutive for your two values.
For this you can use following query -
select distinct t_1.ID from tablname t_1, tablename t_2
where t_1.id = t_2.id
and t_1.key = 'a5' and t_2.key = 'a3'
and t_1.value > t_2.value