Sort a table but keep groups of rows together - mysql

How do I sort a table by it's minimum value per group but at the same time keep a group of rows together. Below a simple example of what i am trying to accomplish. The table is sorted by the lowest group value, but the group remains together. I am pretty sure this question has been asked already but i could not find an answer.
+---------+-------+
| Group | value |
+---------+-------+
| 1 | 3.99 |
| 1 | 10.99 |
| 3 | 12.69 |
| 1 | 20.95 |
| 2 | 19.95 |
| 3 | 10.09 |
+---------+-------+
Desired output
+---------+-------+
| Group | value |
+---------+-------+
| 1 | 3.99 |
| 1 | 10.99 |
| 1 | 20.95 |
| 3 | 10.69 |
| 3 | 12.09 |
| 2 | 19.95 |
+---------+-------+

If you are running MySQL 8.0, you can sort with window functions:
select t.*
from mytable t
order by min(value) over(partition by grp), value
In earlier versions, one option is to join an aggregate subquery:
select t.*
from mytable t
inner join (
select grp, min(value) min_value from mytable group by grp
) m on m.grp = t.grp
order by m.min_value, t.value

SELECT *,RN = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY VALUE,ID) FROM TEMP

Related

SELECT query where LIMIT is a distinct count of repeating key

I have a problem with selecting specific amount of data. The problem is that one of the keys have the same repeated value.
--------------------
| id | name | key |
--------------------
| 1 | alfa | a |
| 2 | alfa | b |
| 3 | alfa | c |
| 4 | beal | a |
| 5 | beal | b |
| 6 | gala | c |
| 7 | gala | d |
| 8 | delt | a |
| 9 | ceta | a |
--------------------
In this situation I want to select three individual names. For example I want to limit distinct name to 3 positions to get this result:
SAMPLE DUMP CODE:
SELECT * in Table
WHERE `name` LIKE '%al%'
LIMIT BY DISTINCT
`name`, 3
------ RESULT ------
| 1 | alfa | a |
| 2 | alfa | b |
| 3 | alfa | c |
| 4 | beal | a |
| 5 | beal | b |
| 6 | gala | c |
| 7 | gala | d |
--------------------
I will be glad for help.
Without window functions:
select *
from (
select distinct name
from mytable
where `name` like '%al%'
order by name
limit 3
) n
natural join mytable
db-fiddle
If you don't like NATURAL JOINs you can also use
select t.*
from (
select distinct name
from mytable
where `name` like '%al%'
order by name
limit 3
) n
join mytable t on t.name = n.name
If window functions are supported, you can use DENSE_RANK():
with cte as (
select *,
dense_rank() over (order by name) as dr
from mytable
where `name` like '%al%'
)
select id, name, `key`
from cte
where dr <= 3
db-fiddle
I prefer the LIMIT 3 subquery, since it can stop the index scan (depending on optimizer) after three distinct names are found.
MySQL 8.0 solution utilizing Window functions is as follows:
SELECT
dt.id, dt.name, dt.`key`
FROM
(
SELECT
ROW_NUMBER() OVER (PARTITION BY name ORDER BY id) AS rn,
id,
name,
`key`
FROM your_table_name
WHERE name LIKE '%al%'
) AS dt
WHERE dt.rn <= 3
ORDER BY dt.id
Explanation:
In a Derived table (subquery), determine Row_Number() within a partition (group) of specific name, ordered by id in ascending order. We will consider only names matching %al% condition.
Now, use the subquery result to SELECT only the rows having row number upto 3 (basically limiting to 3 rows per name).
By the way, key is a Reserved Keyword in MySQL. You should consider renaming column to something else; otherwise you will need to use backticks around it.
Result
| id | name | key |
| --- | ---- | --- |
| 1 | alfa | a |
| 2 | alfa | b |
| 3 | alfa | c |
| 4 | beal | a |
| 5 | beal | b |
| 6 | gala | c |
| 7 | gala | d |
View on DB Fiddle

Mysql - Get distinct value on one field ordered by another field

Let's assume the given MYSQL table structure
+----+-------+-------+
| id | group | count |
+----+-------+-------+
| 1 | cat | 11 |
| 2 | cat | 12 |
| 3 | dog | 4 |
| 4 | dog | 6 |
| 5 | cow | 16 |
| 6 | cow | 12 |
+----+-------+-------+
What I want to do is : Take one animal per animal group, ordered by the count field ascending. In the example above, the output should be :
| 1 | cat | 11 |
| 3 | dog | 4 |
| 6 | cow | 12 |
But it's more complexe than it looks like. What is the most optimized query to get thoses results ? (Of course, making a subquery for each group is not an option)
As per the manual...
SELECT x.*
FROM my_table x
JOIN
( SELECT `group`, MIN(count) count FROM my_table GROUP BY `group`) y
ON y.group = x.group
AND y.count = x.count;
From MySQL 8.0+ you could use ROW_NUMBER():
SELECT *
FROM (SELECT *, ROW_NUMBER() OVER(PARTITION BY `group` ORDER BY `count`) AS rn
FROM table_name) sub
WHERE rn = 1;
DBFiddle Demo

Query data by year from joined table

I have one table named colors and I need to create a query that returns how many unique colors are used each year based on the date in my other table, programs.
It looks like this:
colors
+----+----------+
| id | name |
+----+----------+
| 1 | blue |
| 2 | yellow |
+----+----------+
programs
+----+------------+
| id | date |
+----+------------+
| 1 | 2016-01-08 |
| 2 | 2016-02-08 |
| 3 | 2017-02-08 |
+----+------------+
programs_colors
+------------+----------+
| program_id | color_id |
+------------+----------+
| 1 | 1 |
| 1 | 1 |
| 2 | 2 |
| 2 | 1 |
| 3 | 1 |
| 3 | 1 |
+------------+----------+
I have tried with this:
SELECT min(date), count(*) FROM (
SELECT min(date) AS date FROM programs_colors INNER JOIN programs ON programs.id = program_id GROUP BY color_id
) AS a GROUP BY year(date)
min(date): count(*):
2016-01-08 2
2017-01-08 0
But the above query groups my colors as a whole, but I need them grouped by each year
Expected result:
min(date): count(*):
2016-01-08 2
2017-01-08 1
I hope my question makes sense
SELECT min(date), count(distinct color_id)
FROM programs_colors
INNER JOIN programs
ON programs.id = program_id
GROUP BY year(date);
If I understand right, you might need to do something like this
SELECT min(date), sum(count) FROM (
SELECT min(date) AS date, count(*) as count FROM programs_colors INNER JOIN programs ON programs.id = program_id GROUP BY color_id
) AS a GROUP BY year(date)

How to get greatest time when there is an aggregate function?

I have a table like this:
// mytable
+----+------+-------+-------------+
| id | type | score | unix_time |
+----+------+-------+-------------+
| 1 | 1 | 5 | 1463508841 | -- this (current output)
| 2 | 1 | 10 | 1463508842 |
| 3 | 2 | 5 | 1463508843 | -- this (current output)
| 4 | 1 | 5 | 1463508844 |
| 5 | 2 | 15 | 1463508845 | -- this (expected output)
| 6 | 1 | 10 | 1463508846 | -- this (expected output)
+----+------+-------+-------------+
And here is my query:
SELECT SUM(score), unix_time
FROM mytable
WHERE 1
GROUP BY type
And this is current output:
+-------+-------------+
| score | unix_time |
+-------+-------------+
| 30 | 1463508841 |
| 20 | 1463508843 |
+-------+-------------+
As you see, the output is containing the first row's unix_time. But I'm trying to select greatest one.
So here is expected output:
+-------+-------------+
| score | unix_time |
+-------+-------------+
| 30 | 1463508846 |
| 20 | 1463508845 |
+-------+-------------+
How can I do that?
I think you need sum(score) and max(unix_time) for the grouped value
SELECT type, SUM(score), max(unix_time)
FROM mytable
GROUP BY type
I omitted where 1 because seems non sense ..
Get the max time and total for each type and join on type.
select tm.maxtime,tot.total
from (select max(unixtime) maxtime, type
from mytable
group by type) tm
join (SELECT SUM(score) total, type
FROM mytable
GROUP BY type) tot
on tot.type = tm.type
or simply
select max(unixtime) maxtime, sum(score) total
from mytable
group by type
I think you just want max():
SELECT SUM(score), MAX(unix_time)
FROM mytable
WHERE 1
GROUP BY type;
MySQL is pretty much the only database that accepts your version of the query. You should be getting an error, because unix_time is not in the GROUP BY and not an argument to an aggregation function.

MySQL SELECT value before MAX

How to select 1st, 2nd or 3rd value before MAX ?
usually we do it with order by and limit
SELECT * FROM table1
ORDER BY field1 DESC
LIMIT 2,1
but with my current query I don't know how to make it...
Sample table
+----+------+------+-------+
| id | name | type | count |
+----+------+------+-------+
| 1 | a | 1 | 2 |
| 2 | ab | 1 | 3 |
| 3 | abc | 1 | 1 |
| 4 | b | 2 | 7 |
| 5 | ba | 2 | 1 |
| 6 | cab | 3 | 9 |
+----+------+------+-------+
I'm taking name for each type with max count with this query
SELECT
`table1b`.`name`
FROM
(SELECT
`table1a`.`type`, MAX(`table1a`.`count`) AS `Count`
FROM
`table1` AS `table1a`
GROUP BY `table1a`.`type`) AS `table1a`
INNER JOIN
`table1` AS `table1b` ON (`table1b`.`type` = `table1a`.`type` AND `table1b`.`count` = `table1a`.`Count`)
and I want one more column additional to name with value before max(count)
so result should be
+------+------------+
| name | before_max |
+------+------------+
| ab | 2 |
| b | 1 |
| cab | NULL |
+------+------------+
Please ask if something isn't clear ;)
AS per your given table(test) structure, the query has to be as follows :
select max_name.name,before_max.count
from
(SELECT type,max(count) as max
FROM `test`
group by type) as type_max
join
(select type,name,count
from test
) as max_name on (type_max.type = max_name.type and count = type_max.max )
left join
(select type,count
from test as t1
where count != (select max(count) from test as t2 where t1.type = t2.type)
group by type
order by count desc) as before_max on(type_max.type = before_max .type)