列出函数以查找定界集之间的差异

时间:2018-06-20 14:07:49

标签: sql oracle plsql

我有两个定界列表,如下所示,需要查找已更改的内容。

示例:(这里|是分隔符)

old string: Joe | Public | NY
new string: Joe | Smith  | NY

由于列表的仅第二个成员已更改,因此输出应仅显示已更改的内容,如下所示:

输出:

              - | Smith | -

是否有可用的Oracle函数或标准软件包可以比较这两个定界的字符串/集合并确定发生了什么变化?

2 个答案:

答案 0 :(得分:2)

在字符串中存储定界列表不是一个好主意;遗漏了关系数据库的意义(正如@Tim在评论中指出的那样)。

如果您要在较早的步骤中生成定界字符串,请返回源数据并直接从中工作。

如果您真的对这些字符串感到困惑,可以使用多种方法将定界的字符串标记为单个值(作为结果集中的行)。这使用正则表达式将两个字符串立即拆分为两列-为此,我为每个字符串使用了一个绑定变量,以使重复的文字无效,但是您可能正在从表列中获取字符串(这需要做更多的工作) )或PL / SQL变量或其他任何内容

var old_string varchar2(20);
var new_string varchar2(20);

exec :old_string := 'Joe|Public|NY';
exec :new_string := 'Joe|Smith|NY';

with vals (pos, old_val, new_val) as (
  select level,
    regexp_substr(:old_string, '(.*?)(\||$)', 1, level, null, 1),
    regexp_substr(:new_string, '(.*?)(\||$)', 1, level, null, 1)
  from dual
  connect by level < greatest(regexp_count(:old_string, '(.*?)(\||$)'),
                              regexp_count(:new_string, '(.*?)(\||$)'))
)
select * from vals;

       POS OLD_VAL    NEW_VAL   
---------- ---------- ----------
         1 Joe        Joe       
         2 Public     Smith     
         3 NY         NY        

现在比较两列很简单,以查看发生了什么变化,然后(如果需要)将它们聚合回一个字符串值:

with vals (pos, old_val, new_val) as (
  select level,
    regexp_substr(:old_string, '(.*?)(\||$)', 1, level, null, 1),
    regexp_substr(:new_string, '(.*?)(\||$)', 1, level, null, 1)
  from dual
  connect by level < greatest(regexp_count(:old_string, '(.*?)(\||$)'),
                              regexp_count(:new_string, '(.*?)(\||$)'))
)
select listagg(case when (old_val is null and new_val is null)
      or old_val = new_val then '-' else new_val end, '|')
    within group (order by pos) as diff
from vals;

DIFF                
--------------------
-|Smith|-

case表达式确定您看到的是破折号(表示没有变化)还是新值。

这应该处理空值(空元素,即两个相邻的分隔符);如果可能的话,它将处理不同数量的元素:

exec :old_string := 'Joe|Public|NY||';
exec :new_string := 'Joe|Smith|NY||USA';

... same query ...

DIFF                
--------------------
-|Smith|-|-|USA

但是您应该真正修复数据模型...


如果新旧字符串当前来自表中的两列,则可以扩展它以比较多行;您只需要在connect by子句中引用非确定性函数:

create table t42 (id, old_string, new_string) as
select 1, 'Joe|Public|NY', 'Joe|Smith|NY' from dual
union all select 2, 'Joe|Public|NY', 'Joe|Smith|NY|USA' from dual;

with vals (id, pos, old_val, new_val) as (
  select id, level,
    regexp_substr(old_string, '(.*?)(\||$)', 1, level, null, 1),
    regexp_substr(new_string, '(.*?)(\||$)', 1, level, null, 1)
  from t42
  connect by id = prior id
  and prior dbms_random.value is not null
  and level < greatest(regexp_count(old_string, '(.*?)(\||$)'),
                       regexp_count(new_string, '(.*?)(\||$)'))
)
select id, listagg(case when (old_val is null and new_val is null)
    or old_val = new_val then '-' else new_val end, '|')
    within group (order by pos) as diff
from vals
group by id
order by id;

        ID DIFF                
---------- --------------------
         1 -|Smith|-           
         2 -|Smith|-|USA       

如果它们来自不同的行或不同的表,则更为复杂,应考虑@MTOs方法。


我还应该指出,我假设定界符周围的空格使问题中的字符串更易于阅读;如果它们确实在数据中,则可以调整模式(再次类似于@MTO的模式)。

答案 1 :(得分:1)

这不是一个简单的解决方案,但是您可以在SQL中使用集合和使用正则表达式来匹配分隔列表的每个元素的层次查询来做到这一点。

(注意:此方法适用于多个输入行。)

SQL Fiddle

Oracle 11g R2架构设置

CREATE TABLE table_name ( id, old_string, new_string ) AS
SELECT 1, 'Joe | Public | NY', 'Joe | Smith  | NY' FROM DUAL UNION ALL
SELECT 2, 'Joe | Public | NY', 'Joe | Smith  | NY|USA' FROM DUAL
/

CREATE TYPE indexed_string AS OBJECT(
  idx   INT,
  value VARCHAR2(100)
)
/

CREATE TYPE indexed_string_table AS TABLE OF indexed_string
/

查询1

SELECT id,
       ( SELECT LISTAGG(
                  CASE
                  WHEN o.value = n.value THEN '-'
                  WHEN o.value IS NULL AND n.value IS NULL THEN '-'
                  ELSE n.value
                  END,
                  ' | '
                ) WITHIN GROUP ( ORDER BY COALESCE( o.idx, n.idx ) )
         FROM   TABLE(
                  CAST(
                    MULTISET(
                      SELECT indexed_string(
                               LEVEL,
                               REGEXP_SUBSTR(
                                 t.old_string,
                                 '(.*?)($|\s*\|\s*)',
                                 1,
                                 LEVEL,
                                 NULL,
                                 1
                               )
                             )
                      FROM   DUAL
                      CONNECT BY LEVEL < REGEXP_COUNT(
                                  t.old_string,
                                  '(.*?)($|\s*\|\s*)'
                                )
                    ) AS indexed_string_table
                  )
                ) o
                FULL OUTER JOIN
                TABLE(
                  CAST(
                    MULTISET(
                      SELECT indexed_string(
                               LEVEL,
                               REGEXP_SUBSTR(
                                 t.new_string,
                                 '(.*?)($|\s*\|\s*)',
                                 1,
                                 LEVEL,
                                 NULL,
                                 1
                               )
                             )
                      FROM   DUAL
                      CONNECT BY LEVEL < REGEXP_COUNT(
                                  t.new_string,
                                  '(.*?)($|\s*\|\s*)'
                                )
                    ) AS indexed_string_table
                  )
                ) n
                ON ( o.idx = n.idx )
       ) AS changes
FROM   table_name t

Results

| ID |             CHANGES |
|----|---------------------|
|  1 |       - | Smith | - |
|  2 | - | Smith | - | USA |