Oracle PL / SQL - 字符串匹配和删除功能

时间:2017-10-11 16:27:35

标签: string oracle function plsql

我有一个功能可以将STR_A的值与STR_B进行比较,'dream'是STR_ASTR_B之间的任何匹配字符都将从{{STR_A移除1}}。

例如; STR_A = 'LTD'STR_B = 'LIMITED'因此结果为null

订单很重要,因此如果STR_A = 'LDT'STR_B = 'LIMITED'结果为'T'

另一个例子; STR_A = 'AUSTIN'STR_B = 'ADVERTISING'结果为'UT'

请注意; STR_A中的字符数始终小于或等于STR_B

此外,字符只能使用一次,因此如果STR_A = 'LLTD'STR_B = 'LIMITED',结果将为'L'

我的功能如下;

create or replace FUNCTION CP_RDN_REMSTR(
  S1 VARCHAR2,
  S2 VARCHAR2)
 RETURN VARCHAR2
IS
 LEN INTEGER := NVL(LENGTH(S2),0);
 OUTSTR VARCHAR2(32767) := S1;
 POS INTEGER := 1;
 IND INTEGER := POS;
BEGIN
 FOR I IN 1..LEN
 LOOP
  POS := INSTR(SUBSTR(OUTSTR,POS),SUBSTR(S2,I,1));
  IF POS > 0 THEN
   OUTSTR := SUBSTR(OUTSTR,1,POS-IND)||SUBSTR(OUTSTR,POS+IND);
  END IF;
 END LOOP;
RETURN OUTSTR;
END;

然而,使用上述内容并没有给我预期的结果;

SELECT CP_RDN_REMSTR('LTD','LIMITED') AS STR_A FROM DUAL
UNION ALL
SELECT CP_RDN_REMSTR('LDT','LIMITED') AS STR_A FROM DUAL
UNION ALL
SELECT CP_RDN_REMSTR('AUSTIN','ADVERTISING') AS STR_A FROM DUAL
UNION ALL
SELECT CP_RDN_REMSTR('ADP','ADVANCED') AS STR_A FROM DUAL

结果如下;

('LTD','LIMITED')        = NULL
('LDT','LIMITED')        = 'D'
('AUSTIN','ADVERTISING') = NULL
('ADP','ADVANCED')       = 'P'

预期结果是;

('LTD','LIMITED')        = NULL
('LDT','LIMITED')        = 'T'
('AUSTIN','ADVERTISING') = 'UT'
('ADP','ADVANCED')       = 'P'

一如既往,非常感谢。

2 个答案:

答案 0 :(得分:2)

这是一个使用递归因子子查询(递归WITH子句)的解决方案,自Oracle 11.2起可用。如果您的Oracle版本较旧,则可以使用PL / SQL代码攻击类似的内容。

我们检查LHS字符串中的字母,一次一个。我们使用REGEXP_LIKE(以及以NULL开头的递归模式)检查是否仍然可以找到匹配项。在每一步,如果找到匹配,我们扩展模式;如果未找到匹配项,我们将展开UNM字符串(“不匹配”)。

没有要求LHS短于RHS,它没有任何区别。该解决方案还在两侧正确处理NULL(我添加了数据以测试它)。

请注意在第一个因子子查询中创建的测试数据 - 它仅用于测试,它不是解决方案的一部分。

with
     inputs ( lhs, rhs ) as (
       select 'LTD'   ,'LIMITED'     from dual union all
       select 'LDT'   ,'LIMITED'     from dual union all
       select 'AUSTIN','ADVERTISING' from dual union all
       select 'ADP'   ,'ADVANCED'    from dual union all
       select 'ALPHA' , null         from dual union all
       select null    ,'BETA'        from dual
     ),
     r ( lvl, lhs, rhs, unm, pattrn, next_letter ) as (
       select  1, lhs, rhs, null, null, substr(lhs, 1, 1)
         from  inputs
       union all
       select  lvl + 1, lhs, rhs,
               unm    || case when regexp_like(rhs, pattrn || '.*' || next_letter)
                              then null else next_letter end,
               pattrn || case when regexp_like(rhs, pattrn || '.*' || next_letter)
                              then '.*' || next_letter end,
               substr(lhs, lvl + 1, 1)
         from  r
         where next_letter is not null
     )
     cycle lvl set cycle to 1 default 0
select lhs, rhs, unm
from   r
where  next_letter is null
;

输出(来自模拟输入):

LHS     RHS          UNM  
------  -----------  -----
        BETA                             
LTD     LIMITED                          
LDT     LIMITED      T                   
ADP     ADVANCED     P                   
ALPHA                ALPHA               
AUSTIN  ADVERTISING  UT   

注意:这是查看递归查询功能的一种方法。在解决方案的最后,我只是从r中选择了我需要的列和行。相反,用

替换最后几行
select   *
from     r
order by lhs, lvl
;

然后看看那个输出。

修改

有趣的问题。这是一个应该比第一个版本快一点的解决方案。它仅使用标准字符串函数(INSTR和SUBSTR),它们比正则表达式快得多。此外,从左到右工作,已经检查过的字母不需要继续进行(导致更长和更复杂的匹配模式);相反,可以一次只检查一个字母,并在每一步中从RHS字符串中删除初始段,使每次搜索更短。

with
     inputs ( lhs, rhs ) as (
       select 'LTD'   ,'LIMITED'     from dual union all
       select 'LDT'   ,'LIMITED'     from dual union all
       select 'AUSTIN','ADVERTISING' from dual union all
       select 'ADP'   ,'ADVANCED'    from dual union all
       select 'ALPHA' , null         from dual union all
       select null    ,'BETA'        from dual
     ),
     r ( lhs, rhs, next_letter, unm, new_lhs, new_rhs ) as (
       select  lhs, rhs, substr(lhs, 1, 1), null, lhs, rhs
         from  inputs
       union all
       select  lhs, rhs, substr(new_lhs, 2, 1),
               unm || case when nvl(instr(new_rhs, next_letter), 0) = 0 
                           then next_letter end,
               substr(new_lhs, 2),
               substr(new_rhs, nvl(instr(new_rhs, next_letter), 0) + 1) 
         from  r
         where next_letter is not null
    )
    cycle new_lhs set cycle to 1 default 0
select lhs, rhs, unm
from   r
where  next_letter is null
;

答案 1 :(得分:1)

根据您对订单重要性的评论,听起来您想要遍历S2中的每个字符,只有S1才会将其从S1移除,如果它是CREATE OR REPLACE FUNCTION CP_RDN_REMSTR (S1 VARCHAR2, S2 VARCHAR2) RETURN VARCHAR2 IS LEN INTEGER := NVL (LENGTH (S2), 0); OUTSTR VARCHAR2 (32767) := S1; POS INTEGER := 1; IND INTEGER := POS; BEGIN FOR I IN 1 .. LEN LOOP POS := 0; POS := INSTR (SUBSTR (OUTSTR, POS), SUBSTR (S2, I, 1)); IF POS = 1 THEN OUTSTR := SUBSTR (OUTSTR, 1, POS - IND) || SUBSTR (OUTSTR, POS + IND); END IF; END LOOP; RETURN OUTSTR; END CP_RDN_REMSTR; 的主要字符。

以下内容满足您的第一,第二和第四个示例,但导致第三个示例没有意义:

('LTD','LIMITED')        = NULL 
('LDT','LIMITED')        = 'T'
('AUSTIN','ADVERTISING') = 'USTIN' 
('ADP','ADVANCED')       = 'P'

输出:

CREATE OR REPLACE FUNCTION checkUser(pUserName VARCHAR2, pCountry VARCHAR2) 
RETURN NUMBER 

IS
   vUsername VARCHAR2(100); 
   vCountry VARCHAR2(100);

BEGIN
   SELECT Person.Username INTO vUsername
   FROM Person
   WHERE Person.Username = pUserName;
   EXCEPTION WHEN NO_DATA_FOUND THEN
       vUsername := NULL;

   SELECT Person.Country INTO vCountry
   FROM Person
   WHERE Person.Country = pCountry;
   EXCEPTION WHEN NO_DATA_FOUND THEN
      vCountry := NULL;

   IF vUsername IS NULL OR vCountry IS NULL THEN
      RETURN False;
   ELSE
      RETURN True;
   END IF;
END checkUser;