-1

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:

enter image description here

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.

Kandy
  • 673
  • 9
  • 21
  • please provide table structure – Saurabh Mistry Jan 11 '18 at 10:02
  • 1
    Please see How to create a Minimal, Complete, and Verifiable example https://stackoverflow.com/help/mcve – Mittal Patel Jan 11 '18 at 10:02
  • @Saurabh Mistry hope this clear your doubt. – Kandy Jan 11 '18 at 10:18
  • top 5 student in every subject on basis of highest marks secure – Kandy Jan 11 '18 at 11:14
  • What you want to achieve is not clear. Edit your question and add the expected output, not _"top five student in PCM,ART,PCB in single query"_, but the actual rows you want for the data you have provided. – Angel Politis Jan 11 '18 at 11:42
  • @Angel Politis What is unclear for you. I am asking those five student in every subject who secure first five highest marks in each subject in a single query....If this not clears you than i will not able to make you understand. Thanks – Kandy Jan 11 '18 at 11:56
  • I couldn't understand exactly what you want, because you haven't put enough effort in your question to explain it properly. I'm not inside your head. Anyway, check out [my answer](https://stackoverflow.com/a/48206569/6313073). I have also created a fiddle so you can test the query I have created yourself. I hope it works as you want – Angel Politis Jan 11 '18 at 12:49
  • 1
    Possible duplicate of [Get top n records for each group of grouped results](https://stackoverflow.com/questions/12113699/get-top-n-records-for-each-group-of-grouped-results) – rlanvin Jan 11 '18 at 13:27
  • @rlanvin thanks this will given some idea to thank u – Kandy Jan 11 '18 at 14:43

2 Answers2

2

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:

Angel Politis
  • 10,955
  • 14
  • 48
  • 66
  • By this query it will return only five row of every group, what if two or more student secure same marks. So limit is not good idea. – Kandy Jan 11 '18 at 13:45
  • You see? That's what I meant when I said you didn't explain what you want well enough. Such a requirement wasn't originally presented in your question. I've updated my answer to cover this scenario as well @Kandy Tell me if it works as you want. – Angel Politis Jan 12 '18 at 06:41
  • Thanks for your patience. Next time surely i will try to explain my problem in better way and better formats.. thanks once again. – Kandy Jan 12 '18 at 06:42
  • Does it work as you want @Kandy? If so, be so kind as to give it an upvote for the effort. Based on the data you provided, it functions as expected. – Angel Politis Jan 12 '18 at 06:44
  • thanks your query working but think when i have more than hundred subject or groups than there is performance down in your query and as well as maintenance of code is hard. so i will not able to upvote this answer, But I always Thankful for your quick response @Angel Politis – Kandy Jan 12 '18 at 06:59
  • 1
    You're welcome @Kandy and you are right about the difficulty to maintain a query of such size, especially when dealing with a multitude of different subjects. Give another look at my answer, as I updated it to include a much smaller yet equivalent query that can be maintained easily. I've also included a handful of links to similar questions, so that you can benefit from the answers there as well. – Angel Politis Jan 12 '18 at 08:39
0

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
Angel Politis
  • 10,955
  • 14
  • 48
  • 66
Kandy
  • 673
  • 9
  • 21