Home > Software engineering >  How to rank only a subset of data in SQL
How to rank only a subset of data in SQL

Time:10-27

I've been struggling with this for a while now. I have an Oracle table like below.

ROW_NUM CATALOG CODE EVENT_DT
1 1 G 30-AUG-21
2 2 M 30-AUG-21
3 1 G_M 30-AUG-21
4 3 U 30-AUG-21
5 1 U 30-AUG-21
6 1 G 30-AUG-21
7 2 G_M 30-AUG-21

I want to introduce a rank just for Catalog = 1 and Code = G or G_M based on earliest Event_DT. All the EVENT_DT are the same. I want the resulting table to look like this:

ROW_NUM CATALOG CODE EVENT_DT C_RANK
1 1 G 30-AUG-21 1
2 2 M 30-AUG-21
3 1 G_M 30-AUG-21 2
4 3 U 30-AUG-21
5 1 U 30-AUG-21
6 1 G 30-AUG-21 3
7 2 G_M 30-AUG-21

This is what I have tried:

SELECT CATALOG, CODE, EVENT_DT,
CASE WHEN CODE NOT LIKE 'G%' THEN 0 ELSE ROW_NUMBER() OVER (PARITION BY CATALOG ORDER BY EVENT_DT ASC) END AS C_RANK
FROM TABLE
WHERE CATALOG = 1;

This results in the following table. Row number 6 should have a rank of 3. However, row number 5, which has catalog = 1 and code = U is also counted towards the ranking. I thought the case statement will introduce a 0 for row number 5, it looks have done that but also counted towards the ranking.

ROW_NUM CATALOG CODE EVENT_DT C_RANK
1 1 G 30-AUG-21 1
2 2 M 30-AUG-21 0
3 1 G_M 30-AUG-21 2
4 3 U 30-AUG-21 0
5 1 U 30-AUG-21 0
6 1 G 30-AUG-21 4
7 2 G_M 30-AUG-21 0

CodePudding user response:

If you onöy after the G, you can use

SELECt "ROW_NUM", "CATALOG", "CODE", "EVENT_DT"
, CASE WHEN substr("CODE",1,1) = 'G' 
  THEN ROW_NUMBER() OVER (ORDER BY substr("CODE",1,1),"ROW_NUM") ELSE 0 END AS "c_rwn"
FROM tab1 ORDER BY "ROW_NUM"
ROW_NUM | CATALOG | CODE | EVENT_DT  | c_rwn
------: | ------: | :--- | :-------- | ----:
      1 |       1 | G    | 30-AUG-21 |     1
      2 |       2 | M    | 30-AUG-21 |     0
      3 |       1 | G_M  | 30-AUG-21 |     2
      4 |       3 | U    | 30-AUG-21 |     0
      5 |       1 | U    | 30-AUG-21 |     0
      6 |       1 | G    | 30-AUG-21 |     3
      7 |       2 | G_M  | 30-AUG-21 |     4

db<>fiddle here

As you only want catalog 1

SELECt "ROW_NUM", "CATALOG", "CODE", "EVENT_DT"
, CASE WHEN substr("CODE",1,1) = 'G' AND "CATALOG" = 1 
  THEN ROW_NUMBER() OVER (ORDER BY substr("CODE",1,1),"ROW_NUM") ELSE 0 END AS "c_rwn"
FROM tab1 ORDER BY "ROW_NUM"
ROW_NUM | CATALOG | CODE | EVENT_DT  | c_rwn
------: | ------: | :--- | :-------- | ----:
      1 |       1 | G    | 30-AUG-21 |     1
      2 |       2 | M    | 30-AUG-21 |     0
      3 |       1 | G_M  | 30-AUG-21 |     2
      4 |       3 | U    | 30-AUG-21 |     0
      5 |       1 | U    | 30-AUG-21 |     0
      6 |       1 | G    | 30-AUG-21 |     3
      7 |       2 | G_M  | 30-AUG-21 |     0

db<>fiddle here

CodePudding user response:

You may try adding the filter expression CODE LIKE 'G%' to your partition clause. This will create a sub-partition of true/false values. Moreover, your data may not order automatically as how you've shown it in the example if all rows have the same date. I've included an additional field ROW_NUM to order in the example. Based on your actual dataset, you may substitute this with a suitable field.

Eg.

SELECT
    t.*,
    CASE
        WHEN (CATALOG=1) AND (CODE LIKE 'G%') THEN ROW_NUMBER() OVER (
            PARTITION BY CATALOG,CASE WHEN (CODE LIKE 'G%') THEN 1 ELSE 0 END
            ORDER BY EVENT_DT,ROW_NUM
        )
    END as C_RANK
FROM
    my_table t
ORDER BY
    ROW_NUM;
row_num CATALOG code event_dt c_rank
1 1 G 2021-08-30T00:00:00.000Z 1
2 2 M 2021-08-30T00:00:00.000Z
3 1 G_M 2021-08-30T00:00:00.000Z 2
4 3 U 2021-08-30T00:00:00.000Z
5 1 U 2021-08-30T00:00:00.000Z
6 1 G 2021-08-30T00:00:00.000Z 3
7 2 G_M 2021-08-30T00:00:00.000Z

View working demo on DB Fiddle

Let me know if this works for you.

  • Related