MySQL- Select at least n rows per group - mysql
Suppose you have
id / value
1 2
1 3
1 6
2 3
3 1
3 3
3 6
And I want to retrieve at least n rows per id group, let's say n = 4. In addition, it would help if a counter is added as a column. So the results should be like:
counter / id / value
1 1 2
2 1 3
3 1 6
4 null null
1 2 3
2 null null
3 null null
4 null null
1 3 1
2 3 3
3 3 6
4 null null
regards
I'm assuming that the combination of id and value is unique. Here's how you can do it without using MySQL variables:
SELECT
a.n AS counter,
b.id,
b.value
FROM
(
SELECT
aa.n,
bb.id
FROM
(
SELECT 1 AS n UNION ALL
SELECT 2 AS n UNION ALL
SELECT 3 AS n UNION ALL
SELECT 4 AS n
) aa
CROSS JOIN
(
SELECT DISTINCT id
FROM tbl
) bb
) a
LEFT JOIN
(
SELECT aa.id, aa.value, COUNT(*) AS rank
FROM tbl aa
LEFT JOIN tbl bb ON aa.id = bb.id AND aa.value >= bb.value
GROUP BY aa.id, aa.value
) b ON a.id = b.id AND a.n = b.rank
ORDER BY
a.id,
a.n
The next blog post describes the solution to your query:
SQL: selecting top N records per group.
It requires an additional small table of numbers, which is utilized to "iterate" the top N values per group via String Walking technique.
It uses GROUP_CONCAT as a way to overcome the fact MySQL does not support Window Functions. This also means it's not a pretty sight!
An advantage of this technique is that it does not require subqueries, and can optimally utilize an index on the table.
To complete the answer to your question, we must add an additional columns: you have requested a counter per item per group.
Here's an example using the world sample database, choosing top 5 largest counties per continent:
CREATE TABLE `tinyint_asc` (
`value` tinyint(3) unsigned NOT NULL default '0',
PRIMARY KEY (value)
) ;
INSERT INTO `tinyint_asc` VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24),(25),(26),(27),(28),(29),(30),(31),(32),(33),(34),(35),(36),(37),(38),(39),(40),(41),(42),(43),(44),(45),(46),(47),(48),(49),(50),(51),(52),(53),(54),(55),(56),(57),(58),(59),(60),(61),(62),(63),(64),(65),(66),(67),(68),(69),(70),(71),(72),(73),(74),(75),(76),(77),(78),(79),(80),(81),(82),(83),(84),(85),(86),(87),(88),(89),(90),(91),(92),(93),(94),(95),(96),(97),(98),(99),(100),(101),(102),(103),(104),(105),(106),(107),(108),(109),(110),(111),(112),(113),(114),(115),(116),(117),(118),(119),(120),(121),(122),(123),(124),(125),(126),(127),(128),(129),(130),(131),(132),(133),(134),(135),(136),(137),(138),(139),(140),(141),(142),(143),(144),(145),(146),(147),(148),(149),(150),(151),(152),(153),(154),(155),(156),(157),(158),(159),(160),(161),(162),(163),(164),(165),(166),(167),(168),(169),(170),(171),(172),(173),(174),(175),(176),(177),(178),(179),(180),(181),(182),(183),(184),(185),(186),(187),(188),(189),(190),(191),(192),(193),(194),(195),(196),(197),(198),(199),(200),(201),(202),(203),(204),(205),(206),(207),(208),(209),(210),(211),(212),(213),(214),(215),(216),(217),(218),(219),(220),(221),(222),(223),(224),(225),(226),(227),(228),(229),(230),(231),(232),(233),(234),(235),(236),(237),(238),(239),(240),(241),(242),(243),(244),(245),(246),(247),(248),(249),(250),(251),(252),(253),(254),(255);
SELECT
Continent,
SUBSTRING_INDEX(
SUBSTRING_INDEX(
GROUP_CONCAT(Name ORDER BY SurfaceArea DESC),
',', value),
',', -1)
AS Name,
CAST(
SUBSTRING_INDEX(
SUBSTRING_INDEX(
GROUP_CONCAT(SurfaceArea ORDER BY SurfaceArea DESC),
',', value),
',', -1)
AS DECIMAL(20,2)
) AS SurfaceArea,
CAST(
SUBSTRING_INDEX(
SUBSTRING_INDEX(
GROUP_CONCAT(Population ORDER BY SurfaceArea DESC),
',', value),
',', -1)
AS UNSIGNED
) AS Population,
tinyint_asc.value AS counter
FROM
Country, tinyint_asc
WHERE
tinyint_asc.value >= 1 AND tinyint_asc.value <= 5
GROUP BY
Continent, value
;
Related
How to select a column value depending if id is even or odd
having a table structure of id and a name: create table Mytable ( id integer not null, name varchar(30) not null, unique(id) ); insert into Mytable (id,name) values (1 , 'one'), (2 , 'two'), (3 , 'three'), (4 , 'four'), (6 , 'six'); How may I get a mix of even and odd rows in a result table like: even | odd ----------- null one '0 is not in Mytable so it puts null value two three four null '5 and 6 are not in Mytable so it puts null value six null I was trying to first get the following as a template and use it later as a dictionary: SELECT MIN(id-1) as id,MAX(id-1) as col FROM Mytable GROUP BY FLOOR((id+1)/2); I get: id col 0 1 2 3 5 5 But I do not know how to continue
For MySQL Version <= 5.7, You can use the below query Query 1: SELECT MAX(CASE WHEN m.id % 2 = 0 THEN name END) AS even, MAX(CASE WHEN m.id % 2 = 1 THEN name END) AS odd FROM ( SELECT (SELECT MAX(id) FROM mytable) AS maxid, #rn := #rn + 1 AS rn, (SELECT IF((#rn * 2) <= maxid, #rn, NULL)) AS rid FROM mytable JOIN (SELECT #rn := -1) AS var ) AS t JOIN mytable m ON FLOOR(m.id/2) = t.rid GROUP BY rid; Result 1: even | odd :--- | :---- null | one two | three four | null six | null Demo 1: db fiddle Query 2: After confirmation based on #Madhur Bhaiya comment. If there is no row for id = 8 and 9 then it will show null, null. SELECT MAX(CASE WHEN m.id % 2 = 0 THEN name END) AS even, MAX(CASE WHEN m.id % 2 = 1 THEN name END) AS odd FROM ( SELECT (SELECT MAX(id) FROM mytable) AS maxid, #rn := #rn + 1 AS rn, (SELECT IF((#rn * 2) <= maxid, #rn, NULL)) AS rid FROM (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t JOIN (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t2 JOIN (SELECT #rn := -1) var -- currently it will return 1..100, if needed more add joins based on your needs ) AS t LEFT JOIN mytable m ON FLOOR(m.id/2) = t.rid GROUP BY rid HAVING rid IS NOT NULL; Result 2: even | odd :------ | :----- null | one two | three four | null six | null null | null null | eleven null | null null | null sixteen | null Demo 2: db fiddle For MySQL Version > 8.0, You can use #Nick query but if you need null, null like Result 2 mentioned for <= v5.7 then add LEFT JOIN with ORDER BY clause. Query: with recursive maxid as ( select max(id) as id from Mytable) , cte as ( select 0 as rid union all select rid + 1 from cte cross join maxid where (rid + 1) * 2 <= maxid.id) select max(case when m.id % 2 = 0 then name end) as even, max(case when m.id % 2 = 1 then name end) as odd from cte left join Mytable m on floor(m.id / 2) = cte.rid group by rid order by rid; Result: even | odd :------ | :----- null | one two | three four | null six | null null | null null | eleven null | null null | null sixteen | null Demo: db fiddle Credits: Thanks to #Nick, #Madhur Bhaiya for the fiddle and the logic used to create this query.
Here's a CTE based query that will work in SQL Server and MySQL > v8.0 (with the addition of the keyword recursive before maxid). It generates a list of rows that encompasses the pairs of MyTable values (in the sample, this is 0,1,2,3) and then JOINs that to Mytable to extract the even/odd column values: with maxid as ( select max(id) as id from Mytable) , cte as ( select 0 as rid union all select rid + 1 from cte cross join maxid where (rid + 1) * 2 <= maxid.id) select max(case when m.id % 2 = 0 then name end) as even, max(case when m.id % 2 = 1 then name end) as odd from cte join Mytable m on m.id / 2 = cte.rid group by rid Output: even odd one two three four six Demo on dbfiddle
how find a max value with comma separeted values and ids into a table
i have some problem with this query, to find max item_cost into this table OrderID | item_ids| item_cost --------------------------------------------------------- 1 1,2,3 22,88,77 2 2,4 83,26 i find this function SELECT * FROM scb.invoice_out where FIND_IN_SET('2',item_ids) but the answer is the complete row and i only need the max (item_cost) of the single item OrderID | item_ids| item_cost --------------------------------------------------------- 1 1,2,3 22,88,77 2 2,4 83,26 somebody with the same problem ?
A mysql solution for your Question Could be select max(SUBSTRING_INDEX(SUBSTRING_INDEX(`item_cost`, ',', numbers.n), ',', -1)) as max_id from (select 1 n union all select 2 union all select 3 union all select 4 union all select 5) numbers INNER JOIN invoice_out on CHAR_LENGTH(`item_cost`) -CHAR_LENGTH(REPLACE(`item_cost`, ',', ''))>=numbers.n-1 where FIND_IN_SET('2',item_ids) order by OrderID, n
SQL query of multiple values in one cell
There is a table(Course Interests) which has all the values in one cell. But those values are just ids and I want to join them with another table(Course) so I can know their names. Course Interests: MemberID MemberName CoursesInterested -------------- --------------------- -------------- 1 Al 1,4,5,6 2 A2 3,5,6 Course Table: CourseId Course -------------- --------------------- 1 MBA 2 Languages 3 English 4 French 5 Fashion 6 IT Desired Output: MemberID MemberName CoursesInterested -------------- --------------------- -------------- 1 Al MBA,French,Fashion,IT 2 A2 English,Fashion,IT I would like to do a SQL query in MySql that can help me to extract the desired output. I know how to do it in the opposite way(join values to one cell), but I've struggling on seek a way to separate the ids and do a cross-join into another table. I'll appreciate any help from the community. Thanks
Use FIND_IN_SET to search for something in a comma-delimited list. SELECT i.MemberID, i.MemberName, GROUP_CONCAT(c.Course) AS CoursesInterested FROM CourseInterests AS i JOIN Course AS c ON FIND_IN_SET(c.CourseId, i.CoursesInterested) However, it would be better to create a relation table instead of storing the courses in a single column. This type of join cannot be optimized using an index, so it will be expensive for a large table.
Try this Out: SELECT MemberID,MemberName,Group_Concat(C.Course) from ( SELECT MemberID,MemberName,SUBSTRING_INDEX(SUBSTRING_INDEX(t.CoursesInterested, ',', n.n), ',', -1) value FROM Table1 t CROSS JOIN ( SELECT a.N + b.N * 10 + 1 n FROM (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a ,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b ORDER BY n ) n WHERE n.n <= 1 + (LENGTH(t.CoursesInterested) - LENGTH(REPLACE(t.CoursesInterested, ',', ''))) ORDER BY MemberID,value ) T JOIN course C ON T.value = C.CourseId Group By MemberID,MemberName Fiddle Demo Output: MemberID MemberName CoursesInterested -------------- --------------------- -------------- 1 Al MBA,French,Fashion,IT 2 A2 English,Fashion,IT
finding second position in mysql
I need to pull the name of the students who stood second positions from grade 1 to grade 12. each grade has separate databases with similar table structure I have the following data: Set 1 uid marks 1 10 2 20 3 17 4 17 5 20 6 20 Set 2 uid marks 1 10 2 20 3 17 4 17 5 20 6 17 7 20 I need a query which can say uid 3,4 are second in set 1 and 3,4,6 are second in set 2. i need it in a single query because there are several set of databases what could be the possible way? I tried: SELECT * FROM TBL WHERE marks ! = SELECT MAX(marks) from tbl but it fetched all marks except the highest
Try this out: SELECT uid, marks FROM ( SELECT uid, marks, #rank := #rank + (#prevMarks != marks) rank, #prevMarks := marks FROM t, (SELECT #rank := 0, #prevMarks := 0) init ORDER BY marks ) s WHERE rank = 2 Fiddle here. Another alternative without User Defined Variables: SELECT t.uid, t.marks FROM t JOIN ( SELECT DISTINCT marks FROM t ORDER BY marks LIMIT 1, 1 ) s ON t.marks = s.marks Output: | UID | MARKS | |-----|-------| | 3 | 17 | | 4 | 17 |
Use LIMIT and ORDER BY SELECT * FROM TBL ORDER BY marks DESC LIMIT 1,1 There you ordered all students by marks fro hi to low. And then limit return from second (0 is first record) and return only one record. If need all students with second mark, the use subquery SELECT * FROM TBL WHERE marks = ( SELECT marks FROM TBL ORDER BY marks DESC GROUP BY marks LIMIT 1,1 )
SELECT * FROM table WHERE mark = ( SELECT MAX(mark) FROM table WHERE mark < ( SELECT MAX(mark) FROM table ) )
Try this SELECT t.marks, t.uid, ( SELECT COUNT( marks ) +1 FROM tbl t1 WHERE t.marks < t1.marks ) AS rank FROM tbl t LIMIT 0 , 30 now you can use rank column with bit modification below SELECT * from ( SELECT t.marks, t.uid, ( SELECT COUNT( marks ) +1 FROM tbl t1 WHERE t.marks < t1.marks ) AS rank FROM tbl t ) alias where rank=n (2 here)
SQL split comma separated row [duplicate]
This question already has answers here: MySQL: Split comma separated list into multiple rows (4 answers) Closed 9 years ago. I have a column with a variable number of comma seperated values: somethingA,somethingB,somethingC somethingElseA, somethingElseB And I want the result to take each value, and create a row: somethingA somethingB somethingC somethingElseA somethingElseB How can I do this in SQL (MySQL)? (I've tried googling "implode" and "lateral view", but those don't seem to turn up related questions. All the related SO questions are trying to do much more complicated things)
You can do it with pure SQL like this SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(t.values, ',', n.n), ',', -1) value FROM table1 t CROSS JOIN ( SELECT a.N + b.N * 10 + 1 n FROM (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a ,(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b ORDER BY n ) n WHERE n.n <= 1 + (LENGTH(t.values) - LENGTH(REPLACE(t.values, ',', ''))) ORDER BY value Note: The trick is to leverage tally(numbers) table and a very handy in this case MySQL function SUBSTRING_INDEX(). If you do a lot of such queries (splitting) then you might consider to populate and use a persisted tally table instead of generating it on fly with a subquery like in this example. The subquery in this example generates a sequence of numbers from 1 to 100 effectively allowing you split up to 100 delimited values per row in source table. If you need more or less you can easily adjust it. Output: | VALUE | |----------------| | somethingA | | somethingB | | somethingC | | somethingElseA | | somethingElseB | Here is SQLFiddle demo This is how the query might look with a persisted tally table SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(t.values, ',', n.n), ',', -1) value FROM table1 t CROSS JOIN tally n WHERE n.n <= 1 + (LENGTH(t.values) - LENGTH(REPLACE(t.values, ',', ''))) ORDER BY value Here is SQLFiddle demo