简体   繁体   中英

Ordering SQL results by more than one dynamic condition

I have a query that selects some users based on certain conditions (namely, having the same value, expressed in points, in a cell in the drivers_standings table) and orders them based on their lowest value ( position ) in another table ( results ).

$superquer = "
    SELECT SD.driver, MIN(R.position) AS highest_position FROM standings_drivers SD 
        INNER JOIN results R ON R.driverId = SD.driver 
    WHERE R.compId='$compId' AND R.eventID>='$firstevent' 
        AND R.eventID<='$lastevent' AND R.eventSession NOT IN ('T', 'P', 'Q', 'Q1', 'Q2', 'QA')
        AND SD.season = '42' AND SD.points='$points_pos' 
    GROUP BY SD.driver 
    ORDER BY MIN(R.position)";

However, I have cases in which both have the same value AND also the lowest position . What I am trying to achieve is finding a way to make it look at the next lowest value, and then the next, and the next until a difference is found.

For example, what I have now is:

User A: 56 points, best result 9th
User B: 56 points, best result 6th
User C: 56 points, best result 7th

---> User B, User C, User A

However, I have also cases like this:

User A: 56 points, best result 4th
User B: 56 points, best result 6th
User C: 56 points, best result 4th

In this case, User A and User C are to be ordered by their second best result, so if it was:

User A: 56 points, best result 4th, second best 5th
User B: 56 points, best result 6th, second best 9th
User C: 56 points, best result 4th, second best 4th

We would order them like this:

--- > User C, User A, User B

NOTE: the second best result COULD be the same as the best result - all users took part in several events and there is nothing to prevent them achieving the same ranking in more than one.

Of couse, since I am using the sql MIN function, I don't know how to code it so that it would look for the next best result and ONLY for those whose best result is the same.

One possibility is to use separate subqueries to query the first and second best results for each user.

select SD.driver,  

    coalesce((select R.Position from results R 
     where R.driverId = SD.driver and R.compId='$compId' 
     and R.eventID>='$firstevent' and R.eventID<='$lastevent' 
     and R.eventSession not in ('T', 'P', 'Q', 'Q1', 'Q2', 'QA') 
     order by R.Position 
     limit 1 offset 0), 999999) as best_position,

    coalesce((select R2.Position from results R2 
     where R2.driverId = SD.driver and R2.compId='$compId' 
     and R2.eventID>='$firstevent' and R2.eventID<='$lastevent' 
     and R2.eventSession not in ('T', 'P', 'Q', 'Q1', 'Q2', 'QA') 
     order by R2.Position 
     limit 1 offset 1), 999999) as second_best_position

from standings_drivers SD 
where SD.season = '42' and SD.points='$points_pos' 
order by best_position, second_best_position;

Not tested, so may contain some syntax errors.

We use two subqueries to find all results for each driver, order the results by position and then use the limit clause to get the first or the second row only respectively.

We use coalesce() on the returned result of the subquery so that when the subquery returns NULL (which means the user has no further result), the position is returned as 999999.

This way we get a result set of drivers with their best and second best result as separate columns and can easily order by these columns.

Here is a solution with the use of group_concat . It concatenates the position values in ascending order per driver, formatting each position in 3 digits. Then it appends a z to it when sorting, so that drivers with fewer results will end up last in a tie:

select   driverId, score
from
          (select   driverId, 
                    group_concat(
                        lpad(position, 3, '0')
                        order by position) AS score
          from      results
          group by  driverId) as drivers
order by  score || 'z'

fiddle

This solution has no practical limitation in precision as it is string based: a series of a thousand positions is no problem. Just make sure the number of digits used per position ( 3 ) is set correctly. As I assume this is not about races with many hundreds of drivers, I guess 3 should be just right.

Doing this with one sub query, like above, will have far petter performance than having a sub query per race (session).

Also be aware that in MySql the maximum number of tables joined in a FROM clause is limited to 61 , meaning a pure join-based solution can only look at that many events (sessions) before it runs into an exception.

Managed to make the query loop for the maximum number of events in this fashion:

        $orderbit = "ORDER BY position1";
        $superquer = "SELECT SD.driver, 

             COALESCE((SELECT R.position FROM results R 
             WHERE R.driverId = SD.driver AND R.compId='$compId' 
             AND R.eventID>='$firstevent' AND R.eventID<='$lastevent' 
             AND R.eventSession NOT IN ('T', 'P', 'Q', 'Q1', 'Q2', 'QA') 
             ORDER BY R.position 
             LIMIT 1 OFFSET 0), 999) AS position1";

             $i=2;
             $max = $totalsessions+1;

            while ($i<$max) {

                $superquer .= ", COALESCE((SELECT R$i.position FROM results R$i 
             WHERE R$i.driverId = SD.driver AND R$i.compId='$compId' 
             AND R$i.eventID>='$firstevent' AND R$i.eventID<='$lastevent' 
             AND R$i.eventSession NOT IN ('T', 'P', 'Q', 'Q1', 'Q2', 'QA') 
             ORDER BY R$i.position 
             LIMIT 1 OFFSET $i), 999) AS position$i";

                $orderbit .= ", position$i";

                $i++;
            }                

        $superquer .= " FROM standings_drivers SD 
        WHERE SD.season = '42' AND SD.points='$points_pos'";

        $orderbit .= ";";

        $querytotl = "$superquer $orderbit";

This is an evolution of @NineBerry's code so thank you for all the contributions!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM