Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Oracle Recursive Subquery Factoring convert

I'm trying to use this recursive SQL feature but can't get it to do what I want, not even close. I've coded up the logic in an unrolled loop, asking if it can be converted into a single recursive SQL query, not the table update style I've used.

http://sqlfiddle.com/#!4/b7217/1

There are six players to be ranked. They have id, group id, score and rank.

Initial state

+----+--------+-------+--------+
| id | grp_id | score |  rank  |
+----+--------+-------+--------+
| 1  |     1  |  100  | (null) |
| 2  |     1  |   90  | (null) |
| 3  |     1  |   70  | (null) |
| 4  |     2  |   95  | (null) |
| 5  |     2  |   70  | (null) |
| 6  |     2  |   60  | (null) |
+----+--------+-------+--------+

I want to take the person with the highest initial score and give them rank 1. Then I apply 10 bonus points to the score of everyone who has the same group id. Take the next highest, assign rank 2, distribute bonus points and so on until there are no players left.

User id breaks ties.

The bonus points changes the ranking. id=4 initially appears to be second placed with 95, behind the leader with 100 but with the 10 pts bonus, id=2 moves up and takes the spot.

Final state

+-----+---------+--------+------+
| ID  | GRP_ID  | SCORE  | RANK |
+-----+---------+--------+------+
|  1  |      1  |   100  |    1 |
|  2  |      1  |   100  |    2 |
|  4  |      2  |    95  |    3 |
|  3  |      1  |    90  |    4 |
|  5  |      2  |    80  |    5 |
|  6  |      2  |    80  |    6 |
+-----+---------+--------+------+
like image 314
Andrew Avatar asked Oct 30 '22 18:10

Andrew


1 Answers

This is a quite a bit late, but I'm not sure this can be done using Recursive CTE. I did however come up with a solution using the MODEL clause:

WITH SAMPLE (ID,GRP_ID,SCORE,RANK) AS (
SELECT 1,1,100,NULL FROM DUAL UNION
SELECT 2,1,90,NULL FROM DUAL UNION
SELECT 3,1,70,NULL FROM DUAL UNION
SELECT 4,2,95,NULL FROM DUAL UNION
SELECT 5,2,70,NULL FROM DUAL UNION
SELECT 6,2,60,NULL FROM DUAL)
SELECT ID,GRP_ID,SCORE,RANK FROM SAMPLE
MODEL
DIMENSION BY (ID,GRP_ID)
MEASURES (SCORE,0 RANK,0 LAST_RANKED_GRP,0 ITEM_COUNT,0 HAS_RANK)
RULES
ITERATE (1000) UNTIL (ITERATION_NUMBER = ITEM_COUNT[1,1]) --ITERATE ONCE FOR EACH ITEM TO BE RANKED
(
RANK[ANY,ANY] = CASE WHEN SCORE[CV(),CV()] = MAX(SCORE) OVER (PARTITION BY HAS_RANK) THEN RANK() OVER (ORDER BY SCORE DESC,ID) ELSE RANK[CV(),CV()] END, --IF THE CURRENT ITEM SCORE IS EQUAL TO THE MAX SCORE OF UNRANKED, ASSIGN A RANK
LAST_RANKED_GRP[ANY,ANY] = FIRST_VALUE(GRP_ID) OVER (ORDER BY RANK DESC),
SCORE[ANY,ANY] = CASE WHEN RANK[CV(),CV()] = 0 AND CV(GRP_ID) = LAST_RANKED_GRP[CV(),CV()] THEN SCORE[CV(),CV()]+10 ELSE SCORE[CV(),CV()] END,
ITEM_COUNT[ANY,ANY] = COUNT(*) OVER (),
HAS_RANK[ANY,ANY] = CASE WHEN RANK[CV(),CV()] <> 0 THEN 1 ELSE 0 END --TO SEPARATE RANKED/UNRANKED ITEMS
)
ORDER BY RANK;

It's not very pretty, and I suspect there is a better way to go about this, but it does give the expected output.

Caveats:

You'd have to increase the iteration count if you have more than that number of rows.

This does a full re-ranking based on the score after each iteration. So if we took your sample data, but changed the initial score of item 2 to 95 rather than 90: after ranking item 1 and giving the 10 point bonus to item 2, it now has a score of 105. So we rank it as 1st and move item 1 down to 2nd. You'd have to make a few modifications if this is not the desired behavior.

like image 116
Mike Avatar answered Nov 15 '22 08:11

Mike