0

全て、

私は SQL は初めてではありませんが、長い間、Oracle は初めてです。また、私がこれを行った方法がおそらく最も遅い解決策であることを知っていることに注意してください。問題は、それが機能したことです。これを拡張して 1,000,000 をテストしたいのですが、これは競走馬ではなくロバであることがわかっています。

会社名のリストを「標準化」する小さなタスクがあったので、既知の略語を含む表を作成しました

Col1        Col2    Col3    Col4    ….  Col14

Company Co  Cmpy
Limited     Ltd Lim LMT
Etc

次に、このテーブルをカーソルに選択し、それに送信された名前ごとにループして、「非」標準の略語を列 1 の略語に置き換える関数を作成しました。次を使用します。

私が考えているのは、コードを書き直して (明らかに) パッケージに入れることです。これにより、現在と比較してかなりの速度と明確さが得られるはずです。

私が抱えている問題は、関数のループ部分を実際に改善することではありませんが、この関数を呼び出す Select から実行して、STD_GBR_CO_SUFFIX を 1 回だけロードし、カーソルを再利用するようにする必要があります。

私は O'Reilly のいくつかを読んでいて、やりたいことは可能だと思いますが、方法がわかりません。

これをパッケージに入れ、REF Cursor を使用する必要があると思います。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

これは実際にはあなたが求めていた方向ではありませんが、可能であれば関数を忘れてしまいます. . 複数の列を持つ柔軟性があまりないため、ルックアップテーブルも変更します。各行に単一の標準略語のペアがあります。

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

これは、少なくともいくつかの基本的なテスト ケースでは機能するようですが、11gR2 を使用する必要があるためlistagg関数再帰サブクエリ ファクタリング(別名 recursive CTE または recursive 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|$)    

2 番目の 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両方を持つ元の値の場合、個々の置換ごとに 1 つの行があり、両方が適用された行があります (繰り返しますが、チェックのために 1 つだけです)。CompanyLimitedrn

最終的な CTE t(はい、これらの意味のある名前を付けるのに苦労しました) は、それぞれの行の置換が最も多い行を決定するだけです。各オリジナルは次のようにランク付けされた 1 つの行を取得し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代わりに句を使って何かできるかもしれませんが、私はそれについてあまり詳しくありません...


関数に固執したい場合、これがどのように行われるかを学ぶためだけであれば、参照カーソルは必要ないと思いますが、一括収集は問題ありません。regexp_replaceパッケージ内で (セッションごとに) 1 回入力するテーブル型を宣言し、関数で呼び出しを適用するときにそれを参照できます。

繰り返しますが、おそらくあなたが実際に望んでいたよりも完全ですが、取り除けるものはそれほど多くありません...ここで立ち止まって、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;
/

パッケージ本体は 2 つの部分に分かれています。関数と初期化ブロック:

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これは、パッケージ仕様で宣言された変数に入れられます。これは、パッケージが最初に参照されたときに、セッションごとに 1 回発生します。

この関数は元の会社名から始まり、正規表現レコードのコレクションをループして、それぞれを順番に適用します。そして、最終結果を返します。非常に簡単ですが、PL/SQL リファレンスを調べて、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 に答える