PL / SQL优化在varchar中搜索日期

时间:2013-06-13 22:12:51

标签: regex string oracle date plsql

我有一个表,其中包含日期字段(让它为date s_date)和描述字段(varchar2(n) desc)。我需要的是编写一个脚本(或单个查询,如果可能的话),它将解析desc字段,如果它包含有效的oracle日期,那么它将删除此日期并更新{{1} },如果是s_date

但是还有一个条件 - null中必须有恰好一个出现日期。如果有0或> 1 - 则不应更新任何内容。

当我使用正则表达式提出这个非常难看的解决方案时:

desc

但它的工作速度非常慢(每条记录超过一秒钟,我需要更新大约30000条记录)。有可能以某种方式优化功能吗?也许这是没有正则表达式的方法吗?还有其他想法吗?

感谢任何建议:)

修改

好吧,也许这对某人有用。以下正则表达式会考虑一个月中的天数(包括闰年检查)来检查有效日期(DD.MM.YYYY):

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

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;

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

我将它与@David建议的查询一起使用(请参阅接受的答案),但我已经尝试(((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)))) 而不是select(所以每行减少1个正则表达式,因为我们不知道做update)只是为了“基准”目的。

这里的数字可能不会说太多,因为这一切都取决于硬件,软件和特定的数据库设计,但为我选择36K记录大约需要2分钟。更新会更慢,但我认为这仍然是一个合理的时间。

2 个答案:

答案 0 :(得分:4)

我会按照单个更新查询的方式重构它。

在where子句中使用两个regexp_instr()调用来查找第一次出现匹配但第二次出现不匹配的行,并使用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'

...因为它只评估第二个正则表达式,如果第一个是非零的。第一个查询也可以这样做,但优化者可能会选择首先评估第二个谓词,因为它是一个相等条件,假设它更具选择性。

或重新排序Case表达式可能会更好 - 这是一种难以判断的权衡,可能非常依赖于数据。

答案 1 :(得分:1)

我认为没有办法改善这项任务。实际上,为了达到你想要的效果,它应该变得更慢。 您的正则表达式会匹配月份范围之外的31.02.201331.04.2013等文本。如果你把年份放在游戏中, 它变得更糟。 29.02.2012有效,但29.02.2013不是。 这就是为什么你必须测试结果是否是有效日期的原因。 由于没有完整的正则表达式,你必须真的通过PLSQL来做。

to_date_single函数中,如果找到无效日期,则返回null。 但这并不意味着文本上没有其他有效日期。 因此,您必须继续尝试,直到找到两个有效日期或点击文本末尾:

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