Convert A Relational Schema to SQL query - mysql

I am currently taking Database Systems course in University this is a question from one of the exercises ,given by my instructor, that I couldn't figure out. I was able to do other questions. Thanks in advance.(primary keys are in italic)
QUESTION//
Consider the following relational schema.
student(sid, sname, address, city, gpa)
course(cid, cname, iid)
enroll(sid, cid, grade)
instructor(iid, iname)
Give the corresponding SQL queries for each of the following.
Find the id and name of the student with the 10th highest gpa. You can assume, for simplicity, that the gpa values are distinct.

The psedocode I would attempt is select the top ten students with their gpa in descending order. At that point you have ten GPA's in desc (greatest to least) order. The 10th is at the bottom. But how do you get the 10th? You could subquery the result of that query, to get the top value in ascending (least to greatest) order.
A general way to subquery:
SELECT dT.Col1
FROM (
SELECT Col1
FROM Table
--ORDER BY?
) AS dT
--ORDER BY ?

I solved it like this Feel free to make any corrections. Thanks for helpful comments.
SELECT sid, sname
FROM ( SELECT sid, sname
FROM student
ORDER BY gpa DSC LIMIT 10)
ORDER BY gpa ASC LIMIT 1

Related

Can we define default value for NTH_VALUE() in MySQL

I want to fetch the name of person having 3rd highest salary. Is there any way to display any custom message for departments having less than 2 employees?
Eg: In lag() we can use something like LAG(salary, 1, 'first in list').
Currently it shows null
table data
Since I refuse to open your link, I don't know if the table name and column names in my query are exactly as you need. If not, just change them.
SELECT department, name
FROM
(SELECT department, name, salary, ROW_NUMBER() OVER
(PARTITION BY department ORDER BY salary DESC) AS rn
FROM yourtable
) sub
WHERE rn = 3
UNION ALL
SELECT department, 'No One'
FROM yourtable
GROUP BY department
HAVING COUNT(department) < 3
ORDER BY department;
There might be shorter options, but this one is quite clear: The first query finds the department and the name of people having the 3rd highest salary in this department. It doesn't select anything for departments with less than three people.
The second query will find all departments having less than three people.
Sidenote: This query will only take one person if multiple persons in a certain department have the same 3rd highest salary.
Due to your less detailed description, it's unclear if this is correct for you. It's up to you to adjust this if necessary.

Rank() function with WHERE clause display nothing MYSQL

So I have to find student max scores from each of their organization, I comes up with the solution by applying RANK() function:
SET SQL_SAFE_UPDATES = 0;
INSERT INTO interviewqs.details(scores,name,organization)
VALUES
(30,"Daniel","OWARD UNI"),
(40,"Kayla","OWARD UNI"),
(12,"Hope","ZELENSKY UNI"),
(50,"Osman","ZELENSKY UNI"),
(4,"Daniel","REWARD UNI"),
(77,"Joe","REWARD UNI");
DESCRIBE interviewqs.details;
# Find the student with highest scores from each organization
SELECT DISTINCT organization,name,scores,
RANK() OVER (PARTITION BY organization ORDER BY scores DESC)
AS "rank"
FROM details
WHERE "rank" = 1;
The problem is when I executed the code the output display empty table,
Without 'WHERE' function applied
organization name scores rank
OWARD UNI Kayla 40 1
OWARD UNI Daniel 30 3
REWARD UNI Daniel 77 1
REWARD UNI Daniel 30 2
REWARD UNI Daniel 4 4
ZELENSKY UNI Osman 50 1
ZELENSKY UNI Hope 12 3
With 'WHERE' function applied
organization name scores rank
What mistake did I do here?
SELECT DISTINCT
organization,
FIRST_VALUE(name) OVER (PARTITION BY organization ORDER BY scores DESC) name,
MAX(scores) OVER (PARTITION BY organization) scores
FROM details
You cannot use window functions in WHERE clause. The reason is that WHERE clause are processed first before window functions. I would highly suggest you to read up an article, Why Can't I use RANK() in Where Clause
To solve this, change your query to using CTE or subquery as shown below:
Subquery:
SELECT organization, name, scores
FROM (
SELECT
organization, name, scores,
RANK() OVER(PARTITION BY organization ORDER BY scores DESC) AS rnk
FROM details
) tmp
WHERE rnk = 1
This is another way using CTE.
WITH tmp AS
(
SELECT DISTINCT organization, `name`, scores,
RANK() OVER (PARTITION BY organization ORDER BY scores DESC) `rank`
FROM details
)
SELECT organization, `name`, scores FROM tmp WHERE `rank` = 1;
DB Fiddle
Note : rank is a reserve word so you must be careful in using it, always embed it in backticks.
Check MySQL Common Table Expression for more details about CTE.
In MySQL, every statement or query produces a temporary result or relation. A common table expression or CTE is used to name those temporary results set that exist within the execution scope of that particular statement

How to use a column in select statement which is not in aggregate function nor in group by clause? [duplicate]

This question already has answers here:
Retrieving the last record in each group - MySQL
(33 answers)
Closed 10 months ago.
Above is the table and on the basis of which I have to answer the below question in my past interview.
Q. The most recent order value for each customer?
Answer which I have given in interview:
select customerID, ordervalue, max(orderdate)
from office
group by customerID;
I know since we are not using ordervalue in aggregate and nor in group by so this query will throw an error in SQL but I want to know how to answer this question.
Many times in my past interviewers asked a question where I need to use a column in select statement which is not in aggregate function or nor in group by. So I want know in general what is a workaround for it with an example so that I can resolve these type of questions or how to answer these questions.
The work around depends on what is being asked. For the requirements you have above, I think it makes sense to create (customerid, MAX(orderdate)) pairs.
SELECT customerid, MAX(orderdate)
FROM office
GROUP BY customerid;
Then you can use them to match the row you need from the table.
SELECT customerid, ordervalue, orderdate
FROM office
WHERE (customerid, orderdate) IN
(SELECT customerid, MAX(orderdate)
FROM office
GROUP BY customerid);
Note, this assumes there is only one order per customer per day. If there were more than one, you would see the most recent order(s) per customer. You could add also a GROUP BY on the outer query if needed.
SELECT customerid, MAX(ordervalue), orderdate
FROM office AS tt
WHERE (customerid, orderdate) IN
(SELECT customerid, MAX(orderdate)
FROM office
GROUP BY customerid)
GROUP BY customerid, orderdate;
If the non-aggregate column you need in the SELECT is functionally dependent on the column in the GROUP BY, you can add a subquery in the SELECT.
We can extend your example by adding a name column, where the name of different customers could be the same. If you wanted name instead of ordervalue, just match the customerid of the outer query to get name.
SELECT customerid,
(SELECT name FROM office WHERE customerid=o.customerid LIMIT 1) AS name,
MAX(orderdate)
FROM office AS o
GROUP BY customerid;
You are approaching the task as follows: Aggregate all rows to get one result line per customer, showing the maximum order date and its order value. The problem with this: you'd need an aggregate function to get the value for the maximum order date. The only DBMS I know of featuring such a function is Oracle with KEEP FIRST/LAST.
So look at the task from a different angle. Don't think aggregation-wise where you could count and add up values for a group and get the minimum or maximum value over all the group's rows, because after all you just want to pick single rows. (That is, pick the top 1 row per customer.) In order to pick rows, you'll use a WHERE clause.
One option has been shown by Steve in his answer:
select *
from office
where (customerid, orderdate) in
(
select customerid, max(orderdate)
from office
group by customerid
);
This is a good, straight-forward approach. (Some DBMS, though, don't feature tuples with IN clauses.)
Another way to get the "best" row for a customer would be to pick those rows for which not exists a better row:
select *
from office
where not exists
(
select null
from office better
where better.customerid = office.customerid
and better.orderdate > office.orderdate
);
And then there is the option to use a window function (aka analytic function) in order to get those rows. One example is to get the maximum dates along with the rows' data:
select customerid, ordervalue, orderdate
from
(
select
customerid, ordervalue, orderdate,
max(orderdate) over (partition by customerid) as max_orderdate
from office
)
where orderdate = max_orderdate;
And with ROW_NUMBER, RANK, and DENSE_RANK there are window functions to assign numbers to your rows in the order you want. You number them such that the best rows get number 1 and pick them. The big advantage here: you can apply any order, deal with ties and not only get the top 1, but the top n rows.
select customerid, ordervalue, orderdate
from
(
select
customerid, ordervalue, orderdate,
row_number() over (partition by customerid order by orderdate desc) as rn
from office
)
where rn = 1;

Query for getting top 5 candidate in every group in single table

I have a table in which student marks in each subject and i have to get query in such a way that i will able to get all top 5 student in every subject who secure highest marks.
Here is a sample table:
My expected output look somthing like :
Top five student in PCM, ART, PCB on the basis of students marks,And also if two or more student secure same than those record also need to be in list with single query.
Original Answer
Technically, what you want to accomplish is not possible using a single SQL query. Had you only wanted one student per subject you could have achieved that using GROUP BY, but in your case it won't work.
The only way I can think of to get 5 students for each subject would be to write x queries, one for each subject and use UNION to glue them together. Such query will return a maximum of 5x rows.
Since you want to get the top 5 students based on the mark, you will have to use an ORDER BY clause, which, in combination with the UNION clauses will cause an error. To avoid that, you will have to use subqueries, so that UNION and ORDER BY clauses are not on the same level.
Query:
-- Select the 5 students with the highest mark in the `PCM` subject.
(
SELECT *
FROM student
WHERE subject = 'PCM'
ORDER BY studentMarks DESC
LIMIT 5
)
UNION
(
SELECT *
FROM student
WHERE subject = 'PCB'
ORDER BY studentMarks DESC
LIMIT 5
)
UNION
(
SELECT *
FROM student
WHERE subject = 'ART'
ORDER BY studentMarks DESC
LIMIT 5
);
Check out this SQLFiddle to evaluate the result yourself.
Updated Answer
This update aims to allow getting more than 5 students in the scenario that many students share the same grade in a particular subject.
Instead of using LIMIT 5 to get the top 5 rows, we use LIMIT 4,1 to get the fifth highest grade and use that to get all students that have a grade more or equal to that in a given subject. Though, if there are < 5 students in a subject LIMIT 4,1 will return NULL. In that case, we want essentially every student, so we use the minimum grade.
To achieve what is described above, you will need to use the following piece of code x times, as many as the subjects you have and join them together using UNION. As can be easily understood, this solution can be used for a small handful of different subjects or the query's extent will become unmaintainable.
Code:
-- Select the students with the top 5 highest marks in the `x` subject.
SELECT *
FROM student
WHERE studentMarks >= (
-- If there are less than 5 students in the subject return them all.
IFNULL (
(
-- Get the fifth highest grade.
SELECT studentMarks
FROM student
WHERE subject = 'x'
ORDER BY studentMarks DESC
LIMIT 4,1
), (
-- Get the lowest grade.
SELECT MIN(studentMarks)
FROM student
WHERE subject = 'x'
)
)
) AND subject = 'x';
Check out this SQLFiddle to evaluate the result yourself.
Alternative:
After some research I found an alternative, simpler query that will yield the same result as the one presented above based on the data you have provided without the need of "hardcoding" every subject in its own query.
In the following solution, we define a couple of variables that help us control the data:
one to cache the subject of the previous row and
one to save an incremental value that differentiates the rows having the same subject.
Query:
-- Select the students having the top 5 marks in each subject.
SELECT studentID, studentName, studentMarks, subject FROM
(
-- Use an incremented value to differentiate rows with the same subject.
SELECT *, (#n := if(#s = subject, #n +1, 1)) as n, #s:= subject
FROM student
CROSS JOIN (SELECT #n := 0, #s:= NULL) AS b
) AS a
WHERE n <= 5
ORDER BY subject, studentMarks DESC;
Check out this SQLFiddle to evaluate the result yourself.
Ideas were taken by the following threads:
Get top n records for each group of grouped results
How to SELECT the newest four items per category?
Select X items from every type
Getting the latest n records for each group
Below query produces almost what I desired, may this query helps others in future.
SELECT a.studentId, a.studentName, a.StudentMarks,a.subject FROM testquery AS a WHERE
(SELECT COUNT(*) FROM testquery AS b
WHERE b.subject = a.subject AND b.StudentMarks >= a.StudentMarks) <= 2
ORDER BY a.subject ASC, a.StudentMarks DESC

select two numbers from the same column using a union but with two different order bys

So i need to select the city with highest and lowest population in the same query using a Union statement... I have tried almost every answer on the site, google, mysql.com and mysql tutorials... nothing works so
this is my code
(select name, population from city order by population asc limit 1)
union
( select name, population from city order by population desc limit 1)
it is not working obviously, I dont want the answer, I just need to understand why it isnt working and what to fix please and thanks