2013-06-13 2 views
3

날짜 필드 (date s_date)와 설명 필드 (varchar2(n) desc)가 포함 된 테이블이 있습니다. 필요한 것은 스크립트 (또는 가능하면 단일 쿼리)를 작성하고 desc 필드를 구문 분석하고 유효한 Oracle 날짜가 포함 된 경우이 날짜를 자르고 null 인 경우 s_date을 업데이트합니다.PL/SQL varchar에서 날짜 검색 최적화

그러나 하나 이상의 조건이 있습니다. 정확히 중 하나가 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; 

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

하지만 매우 느리게 작동하고 (각 레코드에 대한 두 번째보다 내가 약 30000 레코드를 업데이트해야합니다). 어떻게 든 기능을 최적화 할 수 있습니까? 어쩌면 정규식을 사용하지 않고 그 일을 할 수있는 방법일까요? 다른 아이디어?

어떤 조언은 감사합니다 :)

편집 :

OK, 어쩌면 누군가를 위해 도움이 될 것입니다.

(((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)))) 

내가 제안하는 쿼리와 함께 사용 다음 정규 표현식은 윤년에 대한 검사를 포함 한 달에 계정에 일 수를 고려 유효 기간 (DD.MM.YYYY), 검사 수행 @David (받아 들인 대답을보십시오), 그러나 update 대신 select를 시도했습니다 (우리가 regexp_substr을하지 않기 때문에 행 당 1 정규 표현식이 더 적습니다). "벤치마킹"용도로만 사용하십시오.

숫자는 아마도 여기서는별로 알려주지 않을 것입니다. 하드웨어, 소프트웨어 및 특정 DB 디자인에 따라 다르 겠지만, 36K 레코드를 선택하는 데 약 2 분이 걸렸습니다. 업데이트 속도는 느려지 겠지만 여전히 합리적인 시간이라고 생각합니다.

+1

당신은 유효 "의 몇 가지 예를들 수 : (이 느린 때문에) 당신이 당신의 to_date_single 기능을 적용 할 을 행의 액수를 필터링 할 수 있지만, 정규 표현식 혼자 당신이 원하는 것을하지 않을 것이다 다윗의 아이디어 @ 사용 오라클 날짜 "많은 사람들이 그게 (나를 포함하여) 될 것입니다 익숙하지 않을 것입니다. – greedybuddha

+0

"DD.MM.YYYY"형식을 사용 중입니다. – Slippy

답변

4

단일 업데이트 쿼리 라인을 따라 리팩터링합니다.

첫 번째 일치 항목이 발생하고 두 번째 항목은 일치하지 않는 행을 찾으려면 where 절에서 두 개의 regexp_instr() 호출을 사용하고 regexp_substr()은 업데이트를 위해 일치하는 문자를 가져옵니다. 최초의 제로가 아닌 경우에만 두 번째 정규 표현식을 평가할 때 ...

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' 

: 당신이 더 나은 성능을 얻을 수 있습니다

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 

. 첫 x 째 조회도이를 수행 할 수 있지만 옵티 마이저는 두 x 째 술어를 우선적으로 평가하기로 선택할 수 있습니다.

케이스 표현을 재정렬하는 것이 더 좋을 수도 있습니다. 판단하기 어렵고 데이터에 크게 의존하는 트레이드 오프입니다.

+0

매우 빠르게 작동합니다. :) 내 괴물 같은 기능보다 훨씬 낫다. – Slippy

1

이 작업을 개선 할 방법이 없다고 생각합니다. 사실, 당신이 원하는 것을 성취하기 위해서는 더 천천히해야합니다. 정규식이 31.02.2013, 31.04.2013과 같은 텍스트와 일치합니다.게임에 1 년을 넣으면 더 악화됩니다. 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과 같은 것으로 충분합니다. 물론 그것은 귀하의 데이터에 달려 있습니다.

update my_table 
set my_date = fn_to_date() 
where regexp_instr(desc,patern,1,1) > 0 
+0

고마워, 나는 정말로 "..."이 텍스트에 앞으로 유효한 다른 날짜가 없을 것이라는 것을 의미하지 않는다고 생각했다. 실제로는 빠른 쿼리로 레코드를 필터링 한 다음 느린 함수를 왼쪽에 적용하는 것이 좋습니다. – Slippy

+0

Btw, 내 정규식 도약 년 및 일 카운트를 한달에 확인하지 않지만 실제로 트릭을 할 훨씬 더 복잡한 예제를 보았다. 질문은 - 그들이 얼마나 빨리 (천천히) 일할 것인가? :) – Slippy

+0

좋은 질문입니다. 테스트를 실행할 수 있도록 게시하십시오. 누군가에게 도움이 될지도 모릅니다. –