mysql order by multiple columns with if - mysql

SELECT s.name, s.mark, g.grade FROM students s, grades g
where g.grade = ( Select grade from grades where s.mark >= min_mark and s.mark <= max_mark)
order by IF(g.grade='F' or g.grade='E' or g.grade='D', (g.grade, s.mark), g.grade)
This is the mysql syntax that I am trying but not getting it to work.
The select works as intended, but I want to order the grades from A to F and on same grades I want to order the marks desc for A-C and asc for D-F
Hope it's clear what I want:
name grade mark
Ewan Black A 100
Ryan Richards B 90
Drake Porter C 78
Jamie Miller C 76
NULL D 67
NULL F 43
NULL F 54

As #Vatev noted, you can use a conditional statement to change the 2nd value being sorted. I would recommend using a CASE statement, as it's more compliant with SQL standards. Also, I would recommend you use the standard JOIN syntax, rather than the old-style (20-year-plus) joins. Also, you don't need a sub-query. So something like this:
select
students.name
,grades.grade
,students.mark
from students
inner join grades on
students.mark between grades.min_mark and grades.max_mark
order by
grades.grade
,case
when grades.grade in ('D', 'E', 'F')
then students.mark
else
100 - students.mark
end

Related

multiple LEFT JOIN using LIMIT on one of the joins

I looked around for answers to this question but all the ones I tried simply didn't work. The other answer suggestions all threw errors for me. Maybe it's because I'm using using MariaDB ?.
SELECT * FROM 'view_winners'
I need top 3 in column 'class'
table view_winners is multiple left joins and I could not figure out how to limit 3 of the left join on table allClasses.
view_winners is:
`$view = "view_winners";
$db->query("DROP $view");
$db->query("CREATE VIEW $view AS
SELECT *
FROM thw22 evnt
LEFT JOIN allUsers usr
ON usr.user_id = evnt.e_owner_id
LEFT JOIN hw_vehicles veh
ON veh.vehicle_id = evnt.e_vehicle_id
LEFT JOIN hw_m_vehicle_class mcls
ON mcls.v_class_id = evnt.e_class_id
LEFT JOIN allClasses cls
ON mcls.cvm_id = cls.class_id
LEFT JOIN hw_v_scores sco
ON sco.v_score_id = evnt.e_score_id
WHERE (cls.class_name <> '' OR cls.class_name IS NOT NULL)
AND (sco.total <> '' OR sco.total IS NOT NULL)
ORDER BY cls.vehicle_type ASC, cls.class_name ASC, sco.total DESC
");`
It's probably best if I could LIMIT 3 on LEFT JOIN allClasses but I can't figure that out. So I figured I would loop through the result and unset rows over 3 in class in PHP. But again I could not figure out how to compare rows as looping through.
I need help with the LIMIT 3 on the JOIN or how to compare the results unsetting rows.
entry
class
score
786
sally
99
234
sally
90
456
bob
45
621
joe
90
964
joe
80
548
joe
66
346
joe
22
900
frank
89
700
frank
86
800
frank
72
123
frank
70
860
frank
50
333
frank
45
Desired results:
entry
class
score
786
sally
99
234
sally
90
456
bob
45
621
joe
90
964
joe
80
548
joe
66
900
frank
89
700
frank
86
800
frank
72
Might this answer help
And to clarify, it appears that for example, you want AT MOST, 3 entries per class (person name per sample data). If one class has only a single entry, get it. However, if someone else has 8 classes you want only the first 3 based on some pre-determined priority ordering, such as top 3 scores.
In your case, the OVER is partitioned by the "class", and the order by will be the score DESC (descending). So having the view give you this extra computed column (per class), you can then filter WHERE finalColumnNameYouAssign <= 3
I just answered similar question yesterday. See How to limit SQL query with JOIN
Here's my solution based on 2 linked tables, users and history for each user. There is also another solution there depending on your MySQL version.
SELECT *
FROM
`users` AS u
LEFT JOIN `history` AS h ON u.id = h.user_id
WHERE
FIND_IN_SET(h.id, (SELECT `list` FROM
(SELECT user_id, SUBSTRING_INDEX(GROUP_CONCAT(id SEPARATOR ','), ',', 3) AS `list` FROM
(SELECT h.user_id, h.id
FROM
`users` AS u
LEFT JOIN `history` AS h ON u.id = h.user_id
) AS `a`
GROUP BY user_id
) AS `b`
WHERE b.user_id = u.id
) )
Instead of having so many joins and confusing myself badly, I made a couple different views that I will need anyway for other statistics. Then by simply ordering by class ASC, score DESC I have a very simple master list of all classes in order then scores highest to lowest (along with all the other joined data). After that I can compare each row and limit 3 as follows:
SELECT * FROM
(SELECT *,
#rn := IF(#prev = class_name,
#rn + 1, 1)
AS rn,
#prev := class_name
FROM view_allScores
JOIN (SELECT #prev := NULL, #rn := 0)
AS vars
) AS T1
WHERE T1.rn <= 3
The confusing thing was that I was trying to add a LIMIT to a join and that kept confusing me.

SQL nested query under WHERE

One of the test questions came by with following schemas, to look for the best doctor in terms of:
Best scored;
The most times/attempts;
For each medical procedures (in terms of name)
[doctor] table
id
first_name
last_name
age
1
Phillip
Singleton
50
2
Heidi
Elliott
34
3
Beulah
Townsend
35
4
Gary
Pena
36
5
Doug
Lowe
45
[medical_procedure] table
id
doctor_id
name
score
1
3
colonoscopy
44
2
1
colonoscopy
37
3
4
ulcer surgery
98
4
2
angiography
79
5
3
angiography
84
6
3
embolization
87
and list goes on...
Given solution as follow:
WITH cte AS(
SELECT
name,
first_name,
last_name,
COUNT(*) AS procedure_count,
RANK() OVER(
PARTITION BY name
ORDER BY COUNT(*) DESC) AS place
FROM
medical_procedure p JOIN doctor d
ON p.doctor_id = d.id
WHERE
score >= (
SELECT AVG(score)
FROM medical_procedure pp
WHERE pp.name = p.name)
GROUP BY
name,
first_name,
last_name
)
SELECT
name,
first_name,
last_name
FROM cte
WHERE place = 1;
It'll mean a lot to be clarified on/explain on how the WHERE clause worked out under the subquery:
How it worked out in general
Why must we match the two pp.name and p.name for it to reflect the correct rows...
...
WHERE
score >= (
SELECT AVG(score)
FROM medical_procedure pp
WHERE pp.name = p.name)
...
Thanks a heap!
Above is join with doctor and medical procedure and group by procedure name and you need doctor names with most attempt and best scored.
Subquery will join by procedure avg score and those who have better score than avg will be filtered.
Now there can be multiple doctor better than avg so taken rank by procedure count so most attempted will come first and then you taken first to pick top one

How to find a column that meets the frequency of another column of varchar

I am trying to find the class numbers that has at least two male students. In this table, both classes (15033 & 15031) meet this criteria so both should pop up. I've tried this query but I cannot tell if this query would ONLY give me the correct answer if the class has exactly 2 males, but not more than 2 if that was case; if this is correct.
SELECT class, COUNT(*)
FROM students
GROUP BY class
HAVING COUNT(2) and sgender = "M";
Students table
sno sname gender sbday class
108 Peter M 1997-09-01 15033
105 Gary M 1995-10-02 15031
101 Alex M 1996-02-20 15033
103 Quincy M 1994-06-03 15031
107 Lily F 1996-01-23 15033
109 Eva F 1995-02-10 15031
Use a case expression in the HAVING clause make sure there are exactly 2 M.
SELECT class, COUNT(*)
FROM students
GROUP BY class
HAVING SUM(case when sgender = "M" then 1 else 0 end) >= 2
Or, simply
SELECT class, COUNT(*)
FROM students
WHERE sgender = "M"
GROUP BY class
HAVING COUNT(*) >= 2
Agree with #jarlh 's answer
but if you like the cte version, is
Updated to include where clause
;With CTE AS
(
SELECT class, COUNT(sno) AS [NoOfStudents]
FROM Students
WHERE sgender = 'M'
GROUP BY class
) SELECT class FROM CTE WHERE NoOfStudents >= 2

Alternative Using Having in mysql

I am trying to get the records where avg is greater than 81, I noticed I can't use a simple where avg(score) > 80
But using a Having statement is problematic as well as it does not consider where the individual records average is greater than 80, but it considers the group average. Is there an alternative?
In general, if we want to return aggregates (SUM,AVG) and also return detail that makes up the aggregate, we typically use two SELECT
As a rudimentary example, consider a table of "test_score"
test_id student_id score
------- ---------- -----
101 6 90
101 7 71
101 8 88
222 6 93
222 7 78
222 8 81
We can calculate the average score for each test, with a SELECT ... GROUP BY query.
SELECT r.test_id AS test_id
, AVG(r.score) AS avg_score
, MAX(r.score) AS high_score
FROM test_score r
GROUP
BY r.test_id
We expect that to return a resultset like this:
test_id avg_score
------- ---------
101 83
222 84
We can use that query as an inline view i.e. we wrap it in parens and reference it like a table in the FROM clause of another SELECT.
As a demonstration, to return student scores that were better (or equal to) average for each test:
SELECT s.test_id
, s.avg_score
, t.student_id
, t.score
FROM ( -- inline view to get average score for each test_id
SELECT r.test_id AS test_id
, AVG(r.score) AS avg_score
FROM test_score r
GROUP
BY r.test_id
) s
LEFT
JOIN test_score t
ON t.test_id = s.test_id
AND t.score >= s.avg_score
ORDER
BY t.test_id
, s.score DESC
And we'd expect that to return something like:
test_id avg_score student_id score
------- --------- ---------- -----
101 83 6 90
101 83 8 88
222 84 6 93
The first two columns, returned from the inline view, are the result of the aggregate (AVG). The last two columns are detail rows, matched to the rows from the aggregate result.
To summarize the main point here:
To return aggregates along with details, we typically need two SELECT.
One SELECT to get the aggregates (with a GROUP BY if the aggregates are "per" each something or other)
Another SELECT to get the details and a match to the aggregate.
If the average score being computed in your query is already correct, you are just having trouble filtering by it, just wrap it in parens and select from it
select * from (
SELECT Count(entry_id) AS Filled,
q.question AS Questions,
AVG(ag.score) AS TOTAL
FROM entry e
LEFT JOIN entry_answer ea
ON ea.entry_id= e.entry
LEFT JOIN question q
ON q.question_id = ea.question_id
LEFT JOIN question_group qg
ON ea.question_parent_id = qg.question_parent_id
LEFT JOIN answer_group ag
ON ag.question_id = qg.question_parent_id
JOIN sent_list using (sent_list_id)
WHERE
entry_group_id = 2427
AND ag.score >= 0
AND ea.rated_answer_id = ag.rated_answer_id
AND sent_id = 6156
AND e.entry_date BETWEEN '2018-01-01' AND '2019-12-31'
group by ea.question_id
) results where total >= 81

how to use union resolve this full outer join problem under mySQL

Here is the table
stuid stuname subject grade
1 alex algo 99
1 alex dastr 100
2 bob algo 90
2 bob dastr 95
3 casy algo 100
4 Daisy dastr 100
case1: assuming there are only two subjects in the table
Following is the expected output
stuname algo dastr
alex 99 100
bob 90 95
casy 100 0
Daisy 0 100
I think following is a workable query
select g1.stuname,
COALESCE(g1.grade,0) as algo
COALESCE(g2.grade,0) as dastr
from grades g1
full outer join grades g2 on g1.stuid = g2.stuid
where g1.subject = algo and g2.subject = dastr;
But, mysql doesnt support full outer join. Is there any other way to resolve the problem?
Also, case 2
assuming there are unknown number of subjects in the table
and the expected output would be
stuname subj1 subj2 subj3 ... subjn
I know I might be using procedure resolve it, is there any other way that I can use to compose columns in mySQL?
Your queries would work better if you re-structured your tables. You are attempting to store too much information in one table. Here is a proposed structure:
Students
student_id student_name
1 Alex
2 Bob
3 Casy
4 Daisy
Subjects
subject_id subject_name
1 Algo
2 Dastr
Grades
student_id subject_id grade
1 1 99
1 2 100
2 1 90
2 2 95
3 1 100
4 2 100
In grades, student_id and subject_id would be a composite key, meaning a unique combination of the two becomes the unique identifier (student 1, subject 1 is unique from student 1, subject 2)
To return the data based on your comment, try:
SELECT a.student_name, b.subject_name, c.grade
FROM students a, subjects b, grades c
WHERE a.student_id = c.student_id
AND b.subject_id = c.subject_id
ORDER BY a.student_id
Have you tried something along the line of:
SELECT a.stuid as sidA, a.grade as grA, a.grade as grB
FROM grades a JOIN grades b ON (a.stuname = b.stuname)
But as D.N. suggested, it may be worth restructuring your tables
From your existing data...
select
stuid,
max( stuName ) stuName,
max( if( subject = "algo", grade, 000 )) as Algo,
max( if( subject = "dastr", grade, 000 )) as Dastr
from
Grades
group by
stuid
order by
stuName
However, if you have multiple people with the same "StuName", by grouping by their unique ID, it will keep them differentiated, so for clarification, I've included the ID column in the final query.
However, the data restructuring as suggested by #D.N. would be a cleaner approach.