This question already has answers here:
Retrieving the last record in each group - MySQL
(33 answers)
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 3 years ago.
I am trying to select the latest record of all the students (I don't want a student's past grade, only their most recent). Currently, its just returning me one result.
student_grade table
╔════╤═══════╤═══════╤═════════════════════╗
║ id │ name │ grade │ date_added ║
╠════╪═══════╪═══════╪═════════════════════╣
║ 1 │ bob │ 23 │ 2019-10-01 14:25:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 2 │ james │ 45 │ 2019-10-02 17:31:27 ║
╟────┼───────┼───────┼─────────────────────╢
║ 3 │ mike │ 42 │ 2019-10-03 18:08:13 ║
╟────┼───────┼───────┼─────────────────────╢
║ 4 │ bob │ 68 │ 2019-10-04 02:00:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 5 │ mike │ 83 │ 2019-10-04 09:28:43 ║
╟────┼───────┼───────┼─────────────────────╢
║ 6 │ bob │ 23 │ 2019-10-04 11:42:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 7 │ james │ 86 │ 2019-10-05 12:11:20 ║
╚════╧═══════╧═══════╧═════════════════════╝
What I want it to return
╔════╤═══════╤═══════╤═════════════════════╗
║ id │ name │ grade │ date_added ║
╠════╪═══════╪═══════╪═════════════════════╣
║ 5 │ mike │ 83 │ 2019-10-04 09:28:43 ║
╟────┼───────┼───────┼─────────────────────╢
║ 6 │ bob │ 23 │ 2019-10-04 11:42:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 7 │ james │ 86 │ 2019-10-05 12:11:20 ║
╚════╧═══════╧═══════╧═════════════════════╝
My sql statement
SELECT id, DISTINCT name, grade, max(date_added)
FROM student_grade
ORDER BY date_added DESC
Or an efficient way of returning me this detail. I'm a little stuck as to how I can get this.
Use a subquery to first select the max id for each name and then the record related to this id :
SELECT sg.*
FROM student_grade sg
INNER JOIN (
SELECT name, max(id) as id
FROM student_grade
GROUP BY name
) x ON x.name = sg.name AND x.id = sg.id
I assumed that the id is the right field to find the last added record. Maybe date_added would be more rightful but if they have the same functional meaning, using id is more efficient
Related
I don't quite get how recursive queries work and how to solve this problem. We were given the table on the left and the structure looks like the tree on the right:
ID | Parent 1
1 null / \
2 1 2 3
3 1 \
4 3 4
5 4 / \
6 4 5 6
7 6 \
7
I know how to get all the parent nodes of every node... but I don't get how you find the max depth of the tree. Meaning I have to find out how many levels this tree has. We aren't given any more information
I would be super grateful if you could give me a solution for mysql, but any sql statement will help me figuring this out
Thanks in advance!
You could use RECURSIVE cte (MySQL 8.0):
WITH RECURSIVE cte AS (
SELECT 1 AS lvl, Parent, id
FROM tab
WHERE Parent IS NULL
UNION ALL
SELECT lvl + 1, tab.Parent, tab.id
FROM tab
JOIN cte
ON tab.Parent = cte.Id
)
SELECT * -- MAX(lvl) AS max_depth_of_tree
FROM cte;
Output:
┌──────┬─────────┬────┐
│ lvl │ Parent │ id │
├──────┼─────────┼────┤
│ 1 │ │ 1 │
│ 2 │ 1 │ 2 │
│ 2 │ 1 │ 3 │
│ 3 │ 3 │ 4 │
│ 4 │ 4 │ 5 │
│ 4 │ 4 │ 6 │
│ 5 │ 6 │ 7 │
└──────┴─────────┴────┘
DBFiddle Demo
I have a table called student_grades
╔════╤═══════╤═══════╤═════════════════════╗
║ id │ name │ grade │ date_added ║
╠════╪═══════╪═══════╪═════════════════════╣
║ 1 │ bob │ 23 │ 2019-10-01 14:25:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 2 │ james │ 45 │ 2019-10-02 17:31:27 ║
╟────┼───────┼───────┼─────────────────────╢
║ 3 │ mike │ 42 │ 2019-10-03 18:08:13 ║
╟────┼───────┼───────┼─────────────────────╢
║ 4 │ bob │ 68 │ 2019-10-04 02:00:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 5 │ mike │ 83 │ 2019-10-04 09:28:43 ║
╟────┼───────┼───────┼─────────────────────╢
║ 6 │ bob │ 93 │ 2019-10-04 11:42:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 7 │ james │ 98 │ 2019-10-05 14:51:20 ║
╟────┼───────┼───────┼─────────────────────╢
║ 8 │ steph │ 72 │ 2019-10-05 15:31:20 ║
╟────┼───────┼───────┼─────────────────────╢
║ 9 │ john │ 78 │ 2019-10-05 16:33:20 ║
╟────┼───────┼───────┼─────────────────────╢
║ 10 │ john │ 74 │ 2019-10-05 17:42:23 ║
╟────┼───────┼───────┼─────────────────────╢
║ 10 │ nick │ 92 │ 2019-10-05 17:59:12 ║
╚════╧═══════╧═══════╧═════════════════════╝
I use this statement to get the LATEST records for a student. For example. James has 2 records. One with id 2 AND ONE WITH id 7. So I want the one with id 7 because the id is larger.
Then I randomize those rows and it returns me
╔════╤═══════╤═══════╤═════════════════════╗
║ id │ name │ grade │ date_added ║
╠════╪═══════╪═══════╪═════════════════════╣
║ 7 │ james │ 86 │ 2019-10-05 12:11:20 ║
╟────┼───────┼───────┼─────────────────────╢
║ 5 │ mike │ 83 │ 2019-10-04 09:28:43 ║
╚════╧═══════╧═══════╧═════════════════════╝
Statement:
SELECT s1.*
FROM student_grade s1
JOIN (SELECT name, MAX(id) AS id
FROM student_grade
GROUP BY name) s2 ON s2.name = s1.name AND s2.id = s1.id
ORDER BY RAND()
LIMIT 2;
My question is, how can I select the latest records with 2 students who scored between 70 and 80 (selected randomly) and 2 students who scored between 90 and 99 (selected randomly).
SELECT s1.*
FROM student_grade s1
JOIN (SELECT name, MAX(id) AS id
FROM student_grade
WHERE (grade >= 70 and grade <= 80) or (grade >= 90 and grade <= 99)
GROUP BY name) s2 ON s2.name = s1.name AND s2.id = s1.id
ORDER BY RAND()
LIMIT 4;
But if I do the statement above, it may give me 3 students who scored 70-80 and 1 student who scored 90-99. I want exactly 2 students from 70-80 (selected randomly) and exactly 2 (selected randomly) from 90-99. How can I do this?
You can UNION ALL together two of your existing query to get the results you want:
(SELECT s1.*
FROM student_grade s1
JOIN (SELECT name, MAX(id) AS id
FROM student_grade
WHERE grade BETWEEN 70 AND 80
GROUP BY name) s2 ON s2.name = s1.name AND s2.id = s1.id
ORDER BY RAND()
LIMIT 2)
UNION ALL
(SELECT s1.*
FROM student_grade s1
JOIN (SELECT name, MAX(id) AS id
FROM student_grade
WHERE grade BETWEEN 90 AND 99
GROUP BY name) s2 ON s2.name = s1.name AND s2.id = s1.id
ORDER BY RAND()
LIMIT 2)
Demo on dbfiddle
We can use UNION ALL since we know there will not be any duplicates because the grade ranges are different.
My question is, how can I select the latest records with 2 students who scored between 70 and 80 (selected randomly) and 2 students who scored between 90 and 99 (selected randomly).
In MySQL 8+, the simplest way uses row numbers:
select sg.*
from (select sg.*
row_number() over (partition by (case when grade between 70 and 80 then 1 when grade between 90 and 99 then 2 else 3 end)
order by rand()
) as seqnum
from student_grades sg
where grade between 70 and 80 or grade between 90 and 99
) sg
where seqnum <= 2;
I have a table called student_grades
╔════╤═══════╤═══════╤═════════════════════╗
║ id │ name │ grade │ date_added ║
╠════╪═══════╪═══════╪═════════════════════╣
║ 1 │ bob │ 23 │ 2019-10-01 14:25:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 2 │ james │ 45 │ 2019-10-02 17:31:27 ║
╟────┼───────┼───────┼─────────────────────╢
║ 3 │ mike │ 42 │ 2019-10-03 18:08:13 ║
╟────┼───────┼───────┼─────────────────────╢
║ 4 │ bob │ 68 │ 2019-10-04 02:00:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 5 │ mike │ 83 │ 2019-10-04 09:28:43 ║
╟────┼───────┼───────┼─────────────────────╢
║ 6 │ bob │ 23 │ 2019-10-04 11:42:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 7 │ james │ 86 │ 2019-10-05 12:11:20 ║
╚════╧═══════╧═══════╧═════════════════════╝
First I want to select all the names from the table BUT I only want their most recent record. For example. James has 2 records. One with id 2 AND ONE WITH id 7. So I want the one with id 7 because the id is larger.
So to do that I get:
╔════╤═══════╤═══════╤═════════════════════╗
║ id │ name │ grade │ date_added ║
╠════╪═══════╪═══════╪═════════════════════╣
║ 5 │ mike │ 83 │ 2019-10-04 09:28:43 ║
╟────┼───────┼───────┼─────────────────────╢
║ 6 │ bob │ 23 │ 2019-10-04 11:42:00 ║
╟────┼───────┼───────┼─────────────────────╢
║ 7 │ james │ 86 │ 2019-10-05 12:11:20 ║
╚════╧═══════╧═══════╧═════════════════════╝
.
SELECT *
FROM student_grade
GROUP BY id
ORDER BY id DESC
Now I want to randomize those rows and get the first 2 rows of those randomized rows
╔════╤═══════╤═══════╤═════════════════════╗
║ id │ name │ grade │ date_added ║
╠════╪═══════╪═══════╪═════════════════════╣
║ 7 │ james │ 86 │ 2019-10-05 12:11:20 ║
╟────┼───────┼───────┼─────────────────────╢
║ 5 │ mike │ 83 │ 2019-10-04 09:28:43 ║
╚════╧═══════╧═══════╧═════════════════════╝
How do I randomize those 3 rows. My end goal is to get the latest records of each student. I don't care about their past records. I just want their most recent ones, and then I want to randomize them. What is the most efficient way of me doing this?
This query will give you the results you want. It finds the row for each student which has the maximum id value, and then sorts all those rows randomly and selects 2:
SELECT *
FROM student_grade s
WHERE id = (SELECT MAX(id)
FROM student_grade
WHERE name = s.name)
ORDER BY RAND()
LIMIT 2
Dependent on the size of your table, it may be more efficient to implement this as a JOIN:
SELECT s1.*
FROM student_grade s1
JOIN (SELECT name, MAX(id) AS id
FROM student_grade
GROUP BY name) s2 ON s2.name = s1.name AND s2.id = s1.id
ORDER BY RAND()
LIMIT 2;
Demo on dbfiddle
I have the next structure in my django app:
class A(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
time = models.DateTimeField(auto_now_add=True)
I want select the earliest time of each name, in database like:
╔═════╤══════╤════════════╗
║ id │ name │ time ║
╠═════╪══════╪════════════╣
║ 1 │ a │ 2018-09-10 ║
║ 2 │ a │ 2018-09-11 ║
║ 3 │ b │ 2018-09-10 ║
║ 4 │ b │ 2018-09-11 ║
╚═════╧══════╧════════════╝
Then the result should be:
╔═════╤══════╤════════════╗
║ id │ name │ time ║
╠═════╪══════╪════════════╣
║ 1 │ a │ 2018-09-10 ║
║ 3 │ b │ 2018-09-10 ║
╚═════╧══════╧════════════╝
I know raw-sql is a solution, but is it possible to use django query expressions? I'm using django 1.8.
Any help please.
We can do this with:
from django.db.models import Min
A.objects.values('name').annotate(
min_time=Min('time')
).order_by('name')
This will result in a QuerySet object that contains dictionaries: in this case it will look like:
<QuerySet [{'name': 'a', 'min_time': date(2018,09,10)},
{'name': 'b', 'min_time': date(2018,09,10)}]>
So you can iterate over the queryset, and thus process the dictionary elements.
Scenario
I have a users table that has a column for the users iso_code_2 for their country of residence and nationality, and in another table I have all the countries in different languages, so what I want to do is get the country text for the users residence and nationality. I know the problem is the GROUP BY but I do not know how to solve it.
Tables
/* Users table */
╔══════╦═════════════╦════════════╦═════════════╦═══════════════╗
║ id ║ firstname ║ lastname ║ residence ║ nationality ║
╚══════╩═════════════╩════════════╩═════════════╩═══════════════╝
│ 1 │ Joe │ Doe │ JP │ PH │
├──────┼─────────────┼────────────┼─────────────┼───────────────┤
│ 2 │ Lisa │ Simpson │ US │ AR │
├──────┼─────────────┼────────────┼─────────────┼───────────────┤
│ 3 │ Homer │ Simpson │ JP │ JP │
└──────┴─────────────┴────────────┴─────────────┴───────────────┘
/* Countries table */
╔══════╦═══════════════╦══════════════╦═════════════════════╗
║ id ║ language_id ║ iso_code_2 ║ country ║
╚══════╩═══════════════╩══════════════╩═════════════════════╝
│ 1 │ 1 │ JP │ Japan │
├──────┼───────────────┼──────────────┼─────────────────────┤
│ 2 │ 2 │ JP │ 日本 │
├──────┼───────────────┼──────────────┼─────────────────────┤
│ 3 │ 1 │ PH │ Philippines │
├──────┼───────────────┼──────────────┼─────────────────────┤
│ 4 │ 2 │ PH │ フィリピン │
├──────┼───────────────┼──────────────┼─────────────────────┤
│ 5 │ 1 │ US │ United States │
├──────┼───────────────┼──────────────┼─────────────────────┤
│ 6 │ 2 │ US │ 米国 │
├──────┼───────────────┼──────────────┼─────────────────────┤
│ 7 │ 1 │ AR │ Argentina │
├──────┼───────────────┼──────────────┼─────────────────────┤
│ 8 │ 2 │ AR │ アルゼンチン │
└──────┴───────────────┴──────────────┴─────────────────────┘
/* Expected results */
╔══════╦═════════════╦════════════╦════════════════════════╦═══════════════════════╗
║ id ║ firstname ║ lastname ║ residence_country ║ nationality_country ║
╚══════╩═════════════╩════════════╩════════════════════════╩═══════════════════════╝
│ 1 │ Joe │ Doe │ Japan │ Philippines │
├──────┼─────────────┼────────────┼────────────────────────┼───────────────────────┤
│ 1 │ Lisa │ Simpson │ United States │ Argentina │
├──────┼─────────────┼────────────┼────────────────────────┼───────────────────────┤
│ 1 │ Homer │ Simpson │ Japan │ Japan │
└──────┴─────────────┴────────────┴────────────────────────┴───────────────────────┘
Current Query
SELECT
u.id,
u.firstname,
u.lastname,
CASE c.iso_code_2
WHEN u.nationality THEN c.country
END AS nationality_country,
CASE c.iso_code_2
WHEN u.residence THEN c.country
END AS residence_country
FROM
users AS u
LEFT JOIN
countries AS c ON c.language_id = 1 WHERE c.iso_code_2 IN (u.nationality, u.residence)
GROUP BY u.id
ORDER BY u.created_at DESC
LIMIT 15
Wrong results
╔══════╦═════════════╦════════════╦════════════════════════╦═══════════════════════╗
║ id ║ firstname ║ lastname ║ residence_country ║ nationality_country ║
╚══════╩═════════════╩════════════╩════════════════════════╩═══════════════════════╝
│ 1 │ Joe │ Doe │ NULL │ Philippines │
├──────┼─────────────┼────────────┼────────────────────────┼───────────────────────┤
│ 1 │ Lisa │ Simpson │ NULL │ Argentina │
├──────┼─────────────┼────────────┼────────────────────────┼───────────────────────┤
│ 1 │ Homer │ Simpson │ NULL │ Japan │
└──────┴─────────────┴────────────┴────────────────────────┴───────────────────────┘
You group by is retaining only one row per user in the result. Depending on MySQL's preferences, it will either contain the residence_country or the nationality_country.
You need to select twice from the country table to get your desired results (and it will make the query easier)
SELECT
u.id,
u.firstname,
u.lastname,
cn.country
cr.country
FROM
users AS u
LEFT JOIN countries AS cn ON cn.language_id = 1 WHERE cn.iso_code_2 = u.nationality
LEFT JOIN countries AS cr ON cr.language_id = 1 WHERE cr.iso_code_2 = u.residence