3

date s_date日付フィールド( )と説明フィールド( )を含むテーブルがありますvarchar2(n) desc。私が必要とするのは、descフィールドを解析し、有効なオラクルの日付が含まれている場合は、この日付を切り取り、s_dateそれがnull.

しかし、もう 1 つの条件がありますdesc。0 または >1 の場合 - 何も更新しないでください。

正規表現を使用して、このかなり醜いソリューションを思いついたときまでに:

----------------------------------------------

create or replace function to_date_single( p_date_str in varchar2 )
    return date
is
    l_date date;
    pRegEx varchar(150);
    pResStr varchar(150); 
begin
    pRegEx := '((0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20)\d\d)((.|\n|\t|\s)*((0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20)\d\d))?';
    pResStr := regexp_substr(p_date_str, pRegEx);
    if not (length(pResStr) = 10)
    then return null;
    end if;
    l_date := to_date(pResStr, 'dd.mm.yyyy');
    return l_date;
exception
    when others then return null;
end to_date_single;

----------------------------------------------

update myTable t
set t.s_date = to_date_single(t.desc)
where t.s_date is null;

----------------------------------------------

しかし、動作が非常に遅いです (各レコードで 1 秒以上、約 30000 レコードを更新する必要があります)。どういうわけか機能を最適化することは可能ですか? 多分それは正規表現なしで物事を行う方法ですか? 他のアイデアはありますか?

どんなアドバイスでも大歓迎です:)

編集:

わかりました、多分それは誰かに役立つでしょう。次の正規表現は、うるう年のチェックを含め、1 か月の日数を考慮して、有効な日付 (DD.MM.YYYY) のチェックを実行します。

(((0[1-9]|[12]\d|3[01])\.(0[13578]|1[02])\.((19|[2-9]\d)\d{2}))|((0[1-9]|[12]\d|30)\.(0[13456789]|1[012])\.((19|[2-9]\d)\d{2}))|((0[1-9]|1\d|2[0-8])\.02\.((19|[2-9]\d)\d{2}))|(29\.02\.((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00))))

@Davidが提案したクエリで使用しました(受け入れられた回答を参照)が、「ベンチマーク」目的のためだけに(行ごとに正規表現が1つ少ないため、行ごとに正規表現が1つ少ないため)select代わりに試しました。updateregexp_substr

ハードウェア、ソフトウェア、および特定の DB 設計に依存するため、数値はおそらくここではあまりわかりませんが、36K レコードを選択するのに約 2 分かかりました。更新は遅くなりますが、それでも妥当な時間になると思います。

4

2 に答える 2

4

単一の更新クエリの行に沿ってリファクタリングします。

where 句で 2 つの regexp_instr() 呼び出しを使用して、最初の一致が発生し、2 回目の一致が発生しない行を検索し、regexp_substr() を使用して一致する文字を取得して更新します。

update my_table
set    my_date = to_date(regexp_subtr(desc,...),...)
where  regexp_instr(desc,pattern,1,1) > 0 and
       regexp_instr(desc,pattern,1,2) = 0

以下を使用すると、さらに優れたパフォーマンスが得られる場合があります。

update my_table
set    my_date = to_date(regexp_subtr(desc,...),...)
where  case regexp_instr(desc,pattern,1,1)
         when 0 then 'N'
         else case regexp_instr(desc,pattern,1,2)
           when 0 then 'Y'
           else 'N'
         end
       end = 'Y'

...最初の正規表現がゼロでない場合にのみ2番目の正規表現を評価するためです。最初のクエリもそれを行う可能性がありますが、オプティマイザは 2 番目の述語を最初に評価することを選択する場合があります。これは、それがより選択的であるという仮定の下での等価条件であるためです。

または、Case 式を並べ替えたほうがよい場合もあります。これは判断が難しく、おそらくデータに大きく依存するトレードオフです。

于 2013-06-13T22:53:03.197 に答える
1

この仕事を改善する方法はないと思います。実際、あなたが望むものを達成するためには、さらに遅くなるはずです。正規表現は、月の範囲外の , の31.02.2013ようなテキストに一致します。31.04.2013ゲームに年を入れると、さらに悪化します。29.02.2012は有効ですが、そうで29.02.2013はありません。そのため、結果が有効な日付であるかどうかをテストする必要があります。そのための完全な正規表現はないため、実際には PLSQL で行う必要があります。

あなたのto_date_single関数では、無効な日付が見つかったときに null を返します。しかし、それはテキストに他の有効な日付がないという意味ではありません. したがって、有効な日付が 2 つ見つかるか、テキストの最後に到達するまで試行を続ける必要があります。

create or replace function fn_to_date(p_date_str in varchar2) return date is
    l_date date;
    pRegEx varchar(150);
    pResStr varchar(150);
    vn_findings number;
    vn_loop number;
begin
    vn_findings := 0;
    vn_loop := 1;
    pRegEx := '((0[1-9]|[12][0-9]|3[01])[.](0[1-9]|1[012])[.](19|20)\d\d)';
    loop
        pResStr := regexp_substr(p_date_str, pRegEx, 1, vn_loop);
        if pResStr is null then exit; end if;
        begin
           l_date := to_date(pResStr, 'dd.mm.yyyy');
           vn_findings := vn_findings + 1;

           -- your crazy requirement :)
           if vn_findings = 2 then
              return null;
           end if;
        exception when others then
          null;
         end;
         -- you have to keep trying :)
         vn_loop := vn_loop + 1;
    end  loop;
    return l_date;
end;

いくつかのテスト:

select fn_to_date('xxxx29.02.2012xxxxx')            c1 --ok
     , fn_to_date('xxxx29.02.2012xxx29.02.2013xxx') c2 --ok, 2nd is invalid
     , fn_to_date('xxxx29.02.2012xxx29.02.2016xxx') c2 --null, both are valid    
from dual

とにかく試行錯誤をしなければならないので、より単純な正規表現を使用することをお勧めします。のようなもの\d\d[.]\d\d[.]\d\d\d\dで十分です。もちろん、それはあなたのデータに依存します。@Davidのアイデアを使用すると、行の量をフィルタリングしてto_date_single関数を適用できますが(遅いため)、正規表現だけでは必要なことができません。

update my_table
set    my_date = fn_to_date( )
where  regexp_instr(desc,patern,1,1) > 0
于 2013-06-13T23:54:34.327 に答える