0

全部,

虽然我对 SQL 并不陌生,但已经有很长一段时间了,而且我对 Oracle 还是很陌生。另请注意,我知道我这样做的方式可能是最慢的解决方案,但正如我所说,我是新手,它适用于我需要测试的 1000 个左右的项目。问题是它确实有效,所以我想扩展它以测试 1,000,000 个,我知道这是一头驴而不是赛马。

有一个“标准化”公司名称列表的小任务,所以我创建了一个包含已知缩写的表

Col1        Col2    Col3    Col4    ….  Col14

Company Co  Cmpy
Limited     Ltd Lim LMT
Etc

然后我写了一个函数来选择这个表到一个游标中,并为每个发送给它的名称循环遍历它,并使用第 1 列中的名称替换任何“NON”标准缩写,使用以下内容:

我一直在考虑的是重写代码(显然)并将其放入一个包中,因为我相信与现在相比,这应该会带来相当大的速度和清晰度。

我遇到的问题并不是真正改进函数的循环部分,而是我需要从调用此函数的 Select 中做些什么,以确保它只加载一次 STD_GBR_CO_SUFFIX 并重新使用游标。

我一直在阅读 O'Reilly 的一些文章,我认为我想做的事情是可能的,但我不知道该怎么做。

我想我需要把它放在一个包中,并使用 REF 光标,BULK LOAD 看起来是一个竞争者,但我读得越多,我就越困惑。如果那里有人可以为我指出正确的方向,我可以从那里继续。我不希望有人只写解决方案,我想学习如何更好地做到这一点,而不是为我完成。

提前感谢大家的帮助。

所以如果我做一个

Select Standardise_Company_Suffix(company_name) AS STD_Company_Name
From VERY_LARGE_TABLE

CREATE OR REPLACE Function Standardise_Company_Suffix(Co_Name IN VARCHAR2)
RETURN varchar2 IS
  stg_Co_Name varchar2(400 byte);
  fmt_S varchar2(20 byte);
  fmt_E varchar2(20 byte);
  parse_Str varchar2(2400 byte);
  parse_char varchar2(4 byte);

CURSOR C1 IS
-- Populate Cursor with Table of Suffixes
select * from STD_GBR_CO_SUFFIX;

BEGIN

parse_char := "s";

Fmt_S := '(^|\'||parse_char||')';

Fmt_E := '(\'||parse_char||'|$)';

stg_Co_Name := upper(co_name);

parse_str := ' ';

   FOR c1pass IN C1 LOOP

   parse_str :='';

   If c1pass.column14 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column14)||Fmt_E;   End If;

   If c1pass.column13 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column13)||Fmt_E;   End If;

   If c1pass.column12 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column12)||Fmt_E;   End If;

   If c1pass.column11 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column11)||Fmt_E;   End If;

   If c1pass.column10 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column10)||Fmt_E;   End If;

   If c1pass.column9 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column9)||Fmt_E;     End If;

   If c1pass.column8 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column8)||Fmt_E;     End If;

   If c1pass.column7 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column7)||Fmt_E;     End If;

   If c1pass.column6 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column6)||Fmt_E;     End If;

   If c1pass.column5 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column5)||Fmt_E;     End If;

   If c1pass.column4 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column4)||Fmt_E;     End If;

   If c1pass.column3 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column3)||Fmt_E;     End If;

   If c1pass.column2 is not null then parse_str := parse_str||'|'||Fmt_S||Upper(c1pass.column2)||Fmt_E;     End If;

   Parse_str := substr(parse_str,2);

   If regexp_instr(stg_Co_Name,parse_str) <> 0 Then

            stg_Co_Name := regexp_REPLACE(stg_Co_Name,parse_str,
                            ' $'||UPPER(c1pass.column1||'$ '));

   Else

            stg_Co_Name := stg_Co_Name;

    End if;    

 END LOOP;

return stg_Co_Name; 

End;

/
4

1 回答 1

2

这并不是您真正要求的那种方向,但是如果可能的话,我会忘记该函数-这至少会导致上下文切换,但可能会产生很多其他开销-并尝试在本机 SQL 中执行某些操作. 我还会更改您的查找表,因为具有多列不是很灵活。我在每一行都有一个标准缩写对:

STD_VALUE            ABBREVIATION
-------------------- ------------
Bar                  Ba           
Company              Co           
Company              Cmpy         
Foo                  Fo           
Limited              Ltd          
Limited              Lim          
Limited              LMT          

这似乎工作,至少对于一些基本的测试用例,但你需要在 11gR2 上,所以你有函数listagg递归子查询因子(又名递归 CTE 或递归with)。我非常赞同你不希望有人只写解决方案的观点,但我很高兴弄清楚它,浪费它似乎很可惜。我试图解释下面发生了什么;希望你仍然可以从中吸取教训。它可能需要一些修改,所以你需要理解它......

with p as (
  select ' $'|| upper( std_value) || '$ ' as replace_string,
    listagg('(^|\s)' || abbreviation || '(\s|$)', '|')
      within group (order by length(abbreviation) desc) as pattern,
    rank() over (order by std_value) as rn
  from std_gbr_co_suffix
  group by std_value
),
r (company_name, replacements, rn, std_company_name) as (
  select company_name, 0, 0, company_name from very_large_table
  union all
  select r.company_name, r.replacements + 1, p.rn, 
    regexp_replace(r.std_company_name, p.pattern, p.replace_string)
  from r
  join p on p.rn > r.rn and regexp_like(r.std_company_name, p.pattern)
),
t as (
  select company_name, std_company_name,
    rank() over (partition by company_name order by replacements desc) as rn
  from r
)
select company_name, std_company_name
from t
where rn = 1;

带有几个测试用例的SQL Fiddle 演示;有兴趣知道它在正确性和性能方面如何与您的 1000 多个测试项目进行比较。

稍微分解一下,第一个 CTE p

p as (
  select ' $'|| upper( std_value) || '$ ' as replace_string,
    listagg('(^|\s)' || abbreviation || '(\s|$)', '|')
      within group (order by length(abbreviation) desc) as pattern,
    rank() over (order by std_value) as rn
  from std_gbr_co_suffix
  group by std_value
)

...为每个不同的std_valuecolumn1在您的原始表中)生成模式和替换字符串:

REPLACE_STRING                   RN PATTERN                                          
------------------------ ---------- --------------------------------------------------
 $BAR$                            1 (^|\s)Ba(\s|$)                                     
 $COMPANY$                        2 (^|\s)Cmpy(\s|$)|(^|\s)Co(\s|$)                    
 $FOO$                            3 (^|\s)Fo(\s|$)                                     
 $LIMITED$                        4 (^|\s)LMT(\s|$)|(^|\s)Lim(\s|$)|(^|\s)Ltd(\s|$)    

第二个 CTEr是递归的:

r (company_name, replacements, rn, std_company_name) as (
  select company_name, 0, 0, company_name from very_large_table
  union all
  select r.company_name, r.replacements + 1, p.rn, 
    regexp_replace(r.std_company_name, p.pattern, p.replace_string)
  from r
  join p on p.rn > r.rn and regexp_like(r.std_company_name, p.pattern)
)

它从作为锚成员的表中的原始值开始,然后递归地应用p. (rn加入中的目的是阻止它以两种方式应用模式,以防原始名称匹配多个;您将做比必要的工作更多的工作,最终会得到重复的结果)。对于我给出的虚拟数据:

COMPANY_NAME                   REPLACEMENTS         RN STD_COMPANY_NAME                                 
------------------------------ ------------ ---------- --------------------------------------------------
Oracle Co                                 0          0 Oracle Co                                          
Oracle Ltd                                0          0 Oracle Ltd                                         
Oracle Ltd.                               0          0 Oracle Ltd.                                        
Oracle Co Ltd.                            0          0 Oracle Co Ltd.                                     
Oracle Co Ltd Cmpy LMT                    0          0 Oracle Co Ltd Cmpy LMT                             
Oracle Co                                 1          2 Oracle $COMPANY$                                   
Oracle Ltd                                1          4 Oracle $LIMITED$                                   
Oracle Co Ltd Cmpy LMT                    1          2 Oracle $COMPANY$ Ltd $COMPANY$ LMT                 
Oracle Co Ltd Cmpy LMT                    1          4 Oracle Co $LIMITED$ Cmpy $LIMITED$                 
Oracle Co Ltd.                            1          2 Oracle $COMPANY$ Ltd.                              
Oracle Co Ltd Cmpy LMT                    2          4 Oracle $COMPANY$ $LIMITED$ $COMPANY$ $LIMITED$     

您可以看到一个原始名称可以有多个替换,以及原始值本身;特别是这里:

Oracle Co Ltd Cmpy LMT                    0          0 Oracle Co Ltd Cmpy LMT                             
Oracle Co Ltd Cmpy LMT                    1          2 Oracle $COMPANY$ Ltd $COMPANY$ LMT                 
Oracle Co Ltd Cmpy LMT                    1          4 Oracle Co $LIMITED$ Cmpy $LIMITED$                 
Oracle Co Ltd Cmpy LMT                    2          4 Oracle $COMPANY$ $LIMITED$ $COMPANY$ $LIMITED$     

replacements值跟踪有多少正则表达式匹配,所以如果有多个匹配,那么我们可以挑选出最多的 - 对于同时具有CompanyLimited模式的原始值,每个单独的替换都有一行,并且两者都应用了(再次,只有一个,因为rn检查)。

最终的 CTE t(是的,我很难给出这些有意义的名称)只是确定哪一行的替换最多;每个原件都有一行,排名如下1

t as (
  select company_name, std_company_name,
    rank() over (partition by company_name order by replacements desc) as rn
  from r
)

...最后我们排除了所有没有排在第一位的东西,剩下的就是:

COMPANY_NAME                   STD_COMPANY_NAME                                 
------------------------------ --------------------------------------------------
Oracle Co                      Oracle $COMPANY$                                   
Oracle Co Ltd Cmpy LMT         Oracle $COMPANY$ $LIMITED$ $COMPANY$ $LIMITED$     
Oracle Co Ltd.                 Oracle $COMPANY$ Ltd.                              
Oracle Ltd                     Oracle $LIMITED$                                   
Oracle Ltd.                    Oracle Ltd.                                        

model你也许可以用这个子句做点什么,但我不是很熟悉......


如果您确实想坚持使用某个功能,如果只是为了了解如何做到这一点,那么我认为您不需要 ref 游标,但批量收集不会受到伤害。你可以在你的包中声明一个表类型,你填充一次(每个会话),然后regexp_replace在你的函数中应用调用时引用它。

再次可能比您真正想要的更完整,但是没有太多要删除的内容...除非您现在停下来,然后去阅读 PL/SQL 集合以及使用PL/SQL 语言使包成为阶段性的原因参考

您的包规范可以声明一个记录类型来保存正则表达式模式和替换字符串,以及这些记录的表。而且,至关重要的是,该表类型的包级变量。这将使包有状态,并且状态将与您的会话相关联 - 运行它的不同人将拥有自己的该表变量的副本。

create or replace package p42 as
  type regex_rec_type is record(pattern varchar2(4000),
    replace_string varchar2(50));
  type regex_tab_type is table of regex_rec_type;

  regexes regex_tab_type;

  function standardise_company_suffix(company_name in varchar2)
  return varchar2;
end p42;
/

包体分为两部分;您的函数和初始化块:

create or replace package body p42 as
  function standardise_company_suffix(company_name in varchar2)
  return varchar2 is
    std_company_name varchar2(4000);
  begin
    std_company_name := company_name;
    for i in regexes.first..regexes.last loop
      std_company_name := regexp_replace(std_company_name,
        regexes(i).pattern, regexes(i).replace_string);
    end loop;
    return std_company_name;
  end standardise_company_suffix;

begin
  select listagg('(^|\s)' || abbreviation || '(\s|$)', '|')
      within group (order by length(abbreviation) desc),
    ' $'|| upper( std_value) || '$ '
  bulk collect into regexes
  from std_gbr_co_suffix
  group by std_value;
end p42;
/

块优先;它运行listagg我之前使用的相同查询,在同一个值对表上我必须替换你的,所以它得到相同的模式和替换字符串。这被放入regexes包规范中声明的变量中。当第一次引用包时,每个会话都会发生一次。

该函数从原始公司名称开始,遍历正则表达式记录的集合,依次应用每一个。然后它返回最终结果。非常简单,但我会让您查看 PL/SQL 参考以确切了解它在做什么。我没有为此烦恼,regexp_instr因为我认为仅应用替换会更昂贵,但是由于我不确定,因此可能值得尝试一下。

使用相同的虚拟数据:

select company_name,
  p42.standardise_company_suffix(company_name) as std_company_name
from very_large_table;

COMPANY_NAME                   STD_COMPANY_NAME                                 
------------------------------ --------------------------------------------------
Oracle Co                      Oracle $COMPANY$                                   
Oracle Ltd                     Oracle $LIMITED$                                   
Oracle Ltd.                    Oracle Ltd.                                        
Oracle Co Ltd.                 Oracle $COMPANY$ Ltd.                              
Oracle Co Ltd Cmpy LMT         Oracle $COMPANY$ $LIMITED$ $COMPANY$ $LIMITED$     

尽管此代码可以说要简单得多,但我希望这在大型数据集上会慢得多,但至少这应该可以最大限度地减少开销,就像您的目标一样。您仍然会有上下文切换,但您现在不会在 PL/SQL 中切换回 SQL,这将有所帮助。我再次对与您现在拥有的性能比较以及递归 CTE 版本感兴趣。

于 2014-02-14T18:38:10.830 回答