Oracle - 对列进行排名和转换

Oracle - Rank and transform on columns

我在这个 cte_orders 中得到了如下示例数据。每行都是一个订单,包含 user_id、zone_code 和 zone_name.

我需要为每个 user_id 排名前 3 zone_code,并且我需要输出在列上。 像这样

|user_id |  top1_zone  |  top2_zone  |  top3_zone  |
|--------+-------------+-------------+-------------+
|1000    |  5555-ABCD  |  4567-ZMNY  |  7888-IXPO  |
|--------+-------------+-------------+-------------+
|9999    |  3456-JJKL  |  7688-HBGT  |  5555-ABCD  |
|--------+-------------+-------------+-------------+

这是 SQL,其中包含我正在尝试的测试数据和查询。 此查询的问题是为排名的每个位置给出了一行结果。

(
    SELECT 1 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
    SELECT 2 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
    SELECT 3 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
    SELECT 4 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
    SELECT 5 as order_id, '1000' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
    SELECT 6 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
    SELECT 7 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
    SELECT 8 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
    SELECT 9 as order_id, '1000' as user_id, '1999' as zone_code, 'LNKJ' as zone_name from dual union all
    SELECT 10 as order_id, '1000' as user_id, '5677' as zone_code, 'OPLH' as zone_name from dual union all
    SELECT 11 as order_id, '1000' as user_id, '7888' as zone_code, 'IXPO' as zone_name from dual union all
    SELECT 12 as order_id, '9999' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
    SELECT 13 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
    SELECT 14 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
    SELECT 15 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
    SELECT 16 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
    SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
    SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
    SELECT 18 as order_id, '9999' as user_id, '1566' as zone_code, 'LNOI' as zone_name from dual union all
    SELECT 19 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
    SELECT 20 as order_id, '9999' as user_id, '7654' as zone_code, 'NNJJ' as zone_name from dual union all
    SELECT 21 as order_id, '9999' as user_id, '4433' as zone_code, 'NHJE' as zone_name from dual union all
    SELECT 22 as order_id, '9999' as user_id, '4111' as zone_code, 'ABHJ' as zone_name from dual

)

select 
    user_id,
    CASE WHEN rank_zones = 1 then zone_concat end as top1_zone,
    CASE WHEN rank_zones = 2 then zone_concat end as top2_zone,
    CASE WHEN rank_zones = 3 then zone_concat end as top3_zone
    from

( 
    select  
        user_id, 
        zone_code||'-'||zone_name as zone_concat,
        row_number() over (partition by user_id order by count(*) desc) rank_zones 
    from cte_orders
    group by
        user_id,
        zone_code||'-'||zone_name

)
where rank_zones <= 3

group by 
    user_id,
    CASE WHEN rank_zones = 1 then zone_concat end,
    CASE WHEN rank_zones = 2 then zone_concat end,
    CASE WHEN rank_zones = 3 then zone_concat end
order by user_id;

我得到的输出

|user_id |  top1_zone  |  top2_zone  |  top3_zone  |
|--------+-------------+-------------+-------------+
|1000    |  5555-ABCD  |    (null)   |    (null)   |
|--------+-------------+-------------+-------------+
|1000    |    (null)   |  4567-ZMNY  |    (null)   |
|--------+-------------+-------------+-------------+
|1000    |    (null)   |    (null)   |  7888-IXPO  |
|--------+-------------+-------------+-------------+
|9999    |  3456-JJKL  |    (null)   |    (null)   |
|--------+-------------+-------------+-------------+
|9999    |    (null)   |  7688-HBGT  |    (null)   |
|--------+-------------+-------------+-------------+
|9999    |    (null)   |    (null)   |  5555-ABCD  |
|--------+-------------+-------------+-------------+

如何修复我的查询,使每个 user_id 没有空值的行?

How can i fix my query to get one row for each user_id without the nulls?

经过一点点汇总(结果是评论)。

示例数据:

SQL> with cte_orders as (
  2      SELECT 1 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
  3      SELECT 2 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
  4      SELECT 3 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
  5      SELECT 4 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
  6      SELECT 5 as order_id, '1000' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
  7      SELECT 6 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
  8      SELECT 7 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
  9      SELECT 8 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
 10      SELECT 9 as order_id, '1000' as user_id, '1999' as zone_code, 'LNKJ' as zone_name from dual union all
 11      SELECT 10 as order_id, '1000' as user_id, '5677' as zone_code, 'OPLH' as zone_name from dual union all
 12      SELECT 11 as order_id, '1000' as user_id, '7888' as zone_code, 'IXPO' as zone_name from dual union all
 13      SELECT 12 as order_id, '9999' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
 14      SELECT 13 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
 15      SELECT 14 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
 16      SELECT 15 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
 17      SELECT 16 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
 18      SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
 19      SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
 20      SELECT 18 as order_id, '9999' as user_id, '1566' as zone_code, 'LNOI' as zone_name from dual union all
 21      SELECT 19 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
 22      SELECT 20 as order_id, '9999' as user_id, '7654' as zone_code, 'NNJJ' as zone_name from dual union all
 23      SELECT 21 as order_id, '9999' as user_id, '4433' as zone_code, 'NHJE' as zone_name from dual union all
 24      SELECT 22 as order_id, '9999' as user_id, '4111' as zone_code, 'ABHJ' as zone_name from dual
 25  )

这就是您所要求的(MAX 聚合函数;第 28 - 30 行):

 26  select
 27      user_id,
 28      max(CASE WHEN rank_zones = 1 then zone_concat end) as top1_zone,
 29      max(CASE WHEN rank_zones = 2 then zone_concat end) as top2_zone,
 30      max(CASE WHEN rank_zones = 3 then zone_concat end) as top3_zone
 31      from
 32  (
 33      select
 34          user_id,
 35          zone_code||'-'||zone_name as zone_concat,
 36          row_number() over (partition by user_id order by count(*) desc) rank_zones
 37      from cte_orders
 38      group by
 39          user_id,
 40          zone_code||'-'||zone_name
 41  )
 42  where rank_zones <= 5

评论 CASE 表达式(第 45 - 47 行):

 43  group by
 44      user_id
 45  --    CASE WHEN rank_zones = 1 then zone_concat end,
 46  --    CASE WHEN rank_zones = 2 then zone_concat end,
 47  --    CASE WHEN rank_zones = 3 then zone_concat end
 48  order by user_id;

然后结果是:

USER TOP1_ZONE TOP2_ZONE TOP3_ZONE
---- --------- --------- ---------
1000 5555-ABCD 4567-ZMNY 5599-HZTR
9999 3456-JJKL 5555-ABCD 7688-HBGT

SQL>

您可以使用 oracle pivot 如下:

 with cte_orders as(
     SELECT 1 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 2 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
     SELECT 3 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 4 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 5 as order_id, '1000' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
     SELECT 6 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
     SELECT 7 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
     SELECT 8 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 9 as order_id, '1000' as user_id, '1999' as zone_code, 'LNKJ' as zone_name from dual union all
     SELECT 10 as order_id, '1000' as user_id, '5677' as zone_code, 'OPLH' as zone_name from dual union all
     SELECT 11 as order_id, '1000' as user_id, '7888' as zone_code, 'IXPO' as zone_name from dual union all
     SELECT 12 as order_id, '9999' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
     SELECT 13 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
     SELECT 14 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 15 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
     SELECT 16 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
     SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
     SELECT 18 as order_id, '9999' as user_id, '1566' as zone_code, 'LNOI' as zone_name from dual union all
     SELECT 19 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
     SELECT 20 as order_id, '9999' as user_id, '7654' as zone_code, 'NNJJ' as zone_name from dual union all
     SELECT 21 as order_id, '9999' as user_id, '4433' as zone_code, 'NHJE' as zone_name from dual union all
     SELECT 22 as order_id, '9999' as user_id, '4111' as zone_code, 'ABHJ' as zone_name from dual
 
 ),
 cte as(select  
         user_id, 
         zone_code||'-'||zone_name as zone_concat,
         row_number() over (partition by user_id order by count(*) desc ) rank_zones 
     from cte_orders
     group by
         user_id,
         zone_code||'-'||zone_name)
 
 select user_id,"1" top1_zone, "2" top2_zone, "3" top3_zone
 from cte 
 PIVOT (
   MAX(ZONE_CONCAT) FOR rank_zones IN (1,2,3)
 )
 

输出:

USER_ID TOP1_ZONE TOP2_ZONE TOP3_ZONE
1000 5555-ABCD 4567-ZMNY 5599-HZTR
9999 3456-JJKL 5555-ABCD 7688-HBGT

db<>fiddle here

或者您可以在分组依据中使用解码聚合:

 with cte_orders as(
     SELECT 1 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 2 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
     SELECT 3 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 4 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 5 as order_id, '1000' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
     SELECT 6 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
     SELECT 7 as order_id, '1000' as user_id, '4567' as zone_code, 'ZMNY' as zone_name from dual union all
     SELECT 8 as order_id, '1000' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 9 as order_id, '1000' as user_id, '1999' as zone_code, 'LNKJ' as zone_name from dual union all
     SELECT 10 as order_id, '1000' as user_id, '5677' as zone_code, 'OPLH' as zone_name from dual union all
     SELECT 11 as order_id, '1000' as user_id, '7888' as zone_code, 'IXPO' as zone_name from dual union all
     SELECT 12 as order_id, '9999' as user_id, '5599' as zone_code, 'HZTR' as zone_name from dual union all
     SELECT 13 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
     SELECT 14 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 15 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
     SELECT 16 as order_id, '9999' as user_id, '5555' as zone_code, 'ABCD' as zone_name from dual union all
     SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
     SELECT 17 as order_id, '9999' as user_id, '7688' as zone_code, 'HBGT' as zone_name from dual union all
     SELECT 18 as order_id, '9999' as user_id, '1566' as zone_code, 'LNOI' as zone_name from dual union all
     SELECT 19 as order_id, '9999' as user_id, '3456' as zone_code, 'JJKL' as zone_name from dual union all
     SELECT 20 as order_id, '9999' as user_id, '7654' as zone_code, 'NNJJ' as zone_name from dual union all
     SELECT 21 as order_id, '9999' as user_id, '4433' as zone_code, 'NHJE' as zone_name from dual union all
     SELECT 22 as order_id, '9999' as user_id, '4111' as zone_code, 'ABHJ' as zone_name from dual
 
 ),
 cte as(select  
         user_id, 
         zone_code||'-'||zone_name as zone_concat,
         row_number() over (partition by user_id order by count(*) desc ) rank_zones 
     from cte_orders
     group by
         user_id,
         zone_code||'-'||zone_name)
 
 select user_id,
   max(decode(rank_zones,1,ZONE_CONCAT)) top1_zone,
   max(decode(rank_zones,2,ZONE_CONCAT)) top2_zone,
   max(decode(rank_zones,3,ZONE_CONCAT)) top3_zone
 from cte 
 group by user_id

输出:

USER_ID TOP1_ZONE TOP2_ZONE TOP3_ZONE
1000 5555-ABCD 4567-ZMNY 5599-HZTR
9999 3456-JJKL 5555-ABCD 7688-HBGT

db<>fiddle here