我有一个表,其中包含日期字段(让它为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分钟。更新会更慢,但我认为这仍然是一个合理的时间。
答案 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.2013
,31.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