繁体   English   中英

Oracle SQL 将一行中的列与同一列中的前一行进行比较

[英]Oracle SQL to compare a column in a row with previous row in the same column

这就是我的表(Table1)当前在 Oracle 数据库中的方式。

ID   Year_Mth   Product    
123  201901     1,2,3      
123  201902     1,2,4,5    
123  201903     2,3,4,6    
123  201904     1,4,5,6  

我正在尝试获取一个 output,它比较每一行的产品列以获得如下结果:在这里,我将第 1 行与第 2 行进行比较,以查看第 2 行是否有第 1 行中不存在的新产品 (NEW_PRODUCTS)。

似乎我可以使用 LAG 或 LEAD function 但它似乎很棘手,因为,产品之间的分隔符。

ID   Year_Mth   Product    New_Products 
123  201901     1,2,3      1,2,3        
123  201902     1,2,4,5    4,5           
123  201903     2,3,4,6    3,6           
123  201904     1,4,5,6    1,5        

这是一种选择。 看起来和您的数据一样丑陋 model :) 请参阅代码中的注释。 如果您不确定每个 CTE 的作用,我建议您逐步运行以下代码并查看其结果。

为了可读性,我将它分成几个部分。

SQL> with
  2  test (id, year_mth, product) as
  3    -- your sample data (as well as some of my sample data)
  4    (select 123, 201901, '1,2,3'   from dual union all
  5     select 123, 201902, '1,2,4,5' from dual union all
  6     select 123, 201903, '2,3,4,6' from dual union all
  7     select 123, 201904, '1,4,5,6' from dual union all
  8     --
  9     select 888, 201901, 'apple,banana' from dual union all
 10     select 888, 201902, 'apple,banana' from dual union all
 11     select 888, 201903, 'apple,lemon'  from dual
 12    ),
 13  py as
 14    (select id,
 15            year_mth ymp,                                                -- "this" year_mth
 16            lead(year_mth) over (partition by id order by year_mth) ymn  -- "next" year_mth
 17     from test
 18     order by id, year_mth
 19    ),
 20  tabp as
 21    -- products that belong to "THIS" year_mth split to rows
 22    (select
 23        t.id,
 24        t.year_mth,
 25        p.ymp,
 26        p.ymn,
 27        regexp_substr(t.product, '[^,]+', 1, c.column_value) product
 28      from test t join py p on t.id = p.id and t.year_mth = p.ymp cross join
 29        table(cast(multiset(select level from dual
 30                            connect by level <= regexp_count(product, ',') + 1
 31                           ) as sys.odcinumberlist)) c
 32    ),
 33  tabn as
 34    -- products that belong to "NEXT" year_mth split to rows
 35    (select
 36        t.id,
 37        t.year_mth,
 38        p.ymp,
 39        p.ymn,
 40        regexp_substr(t.product, '[^,]+', 1, c.column_value) product
 41      from test t join py p on t.id = p.id and t.year_mth = p.ymn cross join
 42        table(cast(multiset(select level from dual
 43                            connect by level <= regexp_count(product, ',') + 1
 44                           ) as sys.odcinumberlist)) c
 45    ),
  •  46 newprod as 47 -- MINUS set operator finds differences between "NEXT" and "THIS" year_mth 48 (select id, ymn, product from tabn 49 minus 50 select id, ymn, product from tabp 51 ) 52 -- finally, aggregate new products (result of the previous MINUS set operation) 53 select 54 t.id, 55 t.year_mth, 56 t.product, 57 listagg(case when t.rn = 1 then t.product else n.product end, ',') 58 within group (order by n.product) new_products 59 from (select a.id, 60 a.year_mth, 61 a.product, 62 row_number() over (partition by a.id order by a.year_mth) rn 63 from test a 64 ) t left join newprod n on t.id = n.id and t.year_mth = n.ymn 65 group by t.id, t.year_mth, t.product 66 order by t.id, t.year_mth;
  •  ID YEAR_MTH PRODUCT NEW_PRODUCTS

     123 201901 1,2,3 1,2,3 123 201902 1,2,4,5 4,5 123 201903 2,3,4,6 3,6 123 201904 1,4,5,6 1,5 888 201901 apple,banana apple,banana 888 201902 apple,banana 888 201903 apple,lemon lemon

    选择了 7 行。

    SQL>

如果您需要使用此类分隔字符串,使用 xml 函数通常非常方便,例如 fn:string-join()、fn:tokenize()。

例如:

xmltable(
       'let $x:=tokenize($a,","), $y:=tokenize($b,",")
        return fn:string-join($x[not(.=$y)],",")'
       passing product as "a"
              ,prev_product as "b"
     columns New_Products varchar(100) path '.'
    ) x

此 xmltable() 拆分输入参数 product 和 prev_product 并从 product 返回那些不在 prev_product 中的子字符串:

  1. Function tokenize($a, ",")使用逗号作为分隔符拆分输入字符串 $a。
  2. $x[not(.=$y)]从 $x 返回那些在 $y 中不存在的值
  3. Function string-join($arg1, ",")使用逗号作为分隔符连接来自 $arg1 的值。

完整示例:

with
test (id, year_mth, product) as
  -- your sample data (as well as some of my sample data)
  (select 123, 201901, '1,2,3'   from dual union all
   select 123, 201902, '1,2,4,5' from dual union all
   select 123, 201903, '2,3,4,6' from dual union all
   select 123, 201904, '1,4,5,6' from dual union all
   --
   select 888, 201901, 'apple,banana' from dual union all
   select 888, 201902, 'apple,banana' from dual union all
   select 888, 201903, 'apple,lemon'  from dual
  )
select
    t.*
   ,x.*
from 
    (
     select 
        t.*
       ,lag(t.product)over(partition by id order by year_mth) prev_product
     from test t
    ) t
    ,xmltable(
       'let $x:=tokenize($a,","), $y:=tokenize($b,",")
        return fn:string-join($x[not(.=$y)],",")'
       passing product as "a"
              ,prev_product as "b"
     columns New_Products varchar(100) path '.'
    ) x;

我把上面的 xquery 写得这么长,只是为了让它更具可读性。 在现实生活中 xquery 会短得多: fn:string-join(tokenize($a,",")[not(.=tokenize($b,","))],",")

with
test (id, year_mth, product) as
  -- your sample data (as well as some of my sample data)
  (select 123, 201901, '1,2,3'   from dual union all
   select 123, 201902, '1,2,4,5' from dual union all
   select 123, 201903, '2,3,4,6' from dual union all
   select 123, 201904, '1,4,5,6' from dual union all
   --
   select 888, 201901, 'apple,banana' from dual union all
   select 888, 201902, 'apple,banana' from dual union all
   select 888, 201903, 'apple,lemon'  from dual
  )
select
    t.*
   ,x.*
from 
    (
     select 
        t.*
       ,lag(t.product)over(partition by id order by year_mth) prev_product
     from test t
    ) t
    ,xmltable(
       'fn:string-join(tokenize($a,",")[not(.=tokenize($b,","))],",")'
       passing product as "a"
              ,prev_product as "b"
     columns New_Products varchar(100) path '.'
    ) x

我的类似,如果你想重新旋转,请在末尾添加一个 listagg 和 group-by 查询...

WITH
input(id,year_mth,product) AS (
          SELECT 123,201901,'1,2,3'    FROM dual
UNION ALL SELECT 123,201902,'1,2,4,5'  FROM dual
UNION ALL SELECT 123,201903,'2,3,4,6'  FROM dual
UNION ALL SELECT 123,201904,'1,4,5,6'  FROM dual
)
,
i(i) AS (
           SELECT 1 FROM dual
 UNION ALL SELECT 2 FROM dual
 UNION ALL SELECT 3 FROM dual
 UNION ALL SELECT 4 FROM dual
 UNION ALL SELECT 5 FROM dual
)
,
unpivot AS (
  SELECT
    id
  , i
  , year_mth
  , REGEXP_SUBSTR(product,'\d+',1,i) AS prd
  FROM input CROSS JOIN i
  WHERE REGEXP_SUBSTR(product,'\d+',1,i) <> ''
)
SELECT 
  * 
, CASE 
    WHEN LAG(year_mth) OVER(PARTITION BY id,prd ORDER BY year_mth) IS NULL
    THEN 'new'
    ELSE 'old'
   END
FROM unpivot ORDER BY 3,4;
-- out  id  | i | year_mth | prd | case 
-- out -----+---+----------+-----+------
-- out  123 | 1 |   201901 | 1   | new
-- out  123 | 2 |   201901 | 2   | new
-- out  123 | 3 |   201901 | 3   | new
-- out  123 | 1 |   201902 | 1   | old
-- out  123 | 2 |   201902 | 2   | old
-- out  123 | 3 |   201902 | 4   | new
-- out  123 | 4 |   201902 | 5   | new
-- out  123 | 1 |   201903 | 2   | old
-- out  123 | 2 |   201903 | 3   | old
-- out  123 | 3 |   201903 | 4   | old
-- out  123 | 4 |   201903 | 6   | new
-- out  123 | 1 |   201904 | 1   | old
-- out  123 | 2 |   201904 | 4   | old
-- out  123 | 3 |   201904 | 5   | old
-- out  123 | 4 |   201904 | 6   | old

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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