是否有人写过模糊日期匹配来捕获数据输入错误?

时间:2013-05-02 16:22:25

标签: java .net plsql

我在PL / SQL中编写了一个例程来尝试匹配可能出现印刷/数据输入错误的日期。

它有效,但我想看看是否有人有其他/更好的想法。该例程不需要在PL / SQL中,因为我读了很多语言。

  FUNCTION FUZZY_DATE_MATCH(IN_DATE_1 DATE, IN_DATE_2 DATE) RETURN NUMBER AS
    MONTH_1 NUMBER(2);
    MONTH_2 NUMBER(2);
    DAY_1 NUMBER(2);
    DAY_2 NUMBER(2);
    YEAR_1 NUMBER(4);
    YEAR_2 NUMBER(4);
    MATCH_SCORE NUMBER(3) := 0;
  BEGIN
    IF TRUNC(IN_DATE_1) = TRUNC(IN_DATE_2)
    THEN
      MATCH_SCORE := 100;
    ELSE
      IF ABS(TRUNC(IN_DATE_1) - TRUNC(IN_DATE_2)) < 2
      THEN
        MATCH_SCORE :=50;
      ELSE
        MONTH_1 := TO_NUMBER(TO_CHAR(IN_DATE_1,'MM'));
        MONTH_2 := TO_NUMBER(TO_CHAR(IN_DATE_2,'MM'));
        IF MONTH_1 = MONTH_2
        THEN
          MATCH_SCORE := MATCH_SCORE + 15;
        ELSE
          IF (ABS(MONTH_1 - MONTH_2) < 2) OR
             (TO_NUMBER(SUBSTR(LPAD(MONTH_1,2,'0'),2,1)||SUBSTR(LPAD(MONTH_1,2,'0'),1,1)) = MONTH_2)
          THEN
            MATCH_SCORE := MATCH_SCORE + 7;
          END IF;
        END IF;
        DAY_1 := TO_NUMBER(TO_CHAR(IN_DATE_1,'DD'));
        DAY_2 := TO_NUMBER(TO_CHAR(IN_DATE_2,'DD'));
        IF DAY_1 = DAY_2
        THEN
          MATCH_SCORE := MATCH_SCORE + 10;
        ELSE
          IF (ABS(DAY_1 - DAY_2) < 2) OR
             (TO_NUMBER(SUBSTR(LPAD(DAY_1,2,'0'),2,1)||SUBSTR(LPAD(DAY_1,2,'0'),1,1)) = DAY_2)
          THEN
            MATCH_SCORE := MATCH_SCORE + 5;
          END IF;
        END IF;
        YEAR_1 := TO_NUMBER(TO_CHAR(IN_DATE_1,'YYYY'));
        YEAR_2 := TO_NUMBER(TO_CHAR(IN_DATE_2,'YYYY'));
        IF YEAR_1 = YEAR_2
        THEN
          MATCH_SCORE := MATCH_SCORE + 25;
        ELSE
          IF (ABS(YEAR_1 - YEAR_2) < 2) OR
             (TO_NUMBER(SUBSTR(LPAD(YEAR_1,2,'0'),4,1)||SUBSTR(LPAD(YEAR_1,2,'0'),3,1)) = TO_NUMBER(SUBSTR(TO_CHAR(YEAR_2),3)))
          THEN
            MATCH_SCORE := MATCH_SCORE + 12;
          END IF;
        END IF;
      END IF;
    END IF;
    RETURN MATCH_SCORE;
  END FUZZY_DATE_MATCH;

基本概念是比较两个日期并返回0到100之间的值,其中100是完全匹配,0是不匹配。我正在寻找的错误类型是单个数字错误和转置错误。我的假设是,年份比几个月的体重更重,而体重则超过几天。

我尝试使用谷歌搜索模糊日期匹配,但答案通常处理日期之间的距离而不是数据输入错误。

AAll帮助表示赞赏。

3 个答案:

答案 0 :(得分:1)

无需亲自实施。看一下UTL_MATCH包,它是Oracle的标准部分。这是一个快速摘要:

FUNCTION edit_distance(s1 IN VARCHAR2, s2 IN VARCHAR2)
                       RETURN pls_integer;
  -- Computes the Levenshtein distance between s1 and s2.

FUNCTION jaro_winkler(s1 IN VARCHAR2, s2 IN VARCHAR2)
                      RETURN binary_double;
  -- Similar to Levenshtein distance, but tries to account for mis-typings,
  -- character swaps, etc.

FUNCTION edit_distance_similarity(s1 IN VARCHAR2, s2 IN VARCHAR2)
                                  RETURN pls_integer;
  -- Similar to Levenshtein distance, but returns an integer from 0 to 100
  -- where 0 means no similarity and 100 means the strings are identical.

FUNCTION jaro_winkler_similarity(s1 IN VARCHAR2, s2 IN VARCHAR2)
                                 RETURN pls_integer;
  -- Similar to above, but based on Jaro-Winkler.

这是一个简单的例子:

SELECT UTL_MATCH.EDIT_DISTANCE('potato', 'tomato') AS lev,
       UTL_MATCH.EDIT_DISTANCE_SIMILARITY('potato', 'tomato') AS lev_sim,
       TO_NUMBER(UTL_MATCH.JARO_WINKLER('potato', 'tomato')) AS jw,
       UTL_MATCH.JARO_WINKLER_SIMILARITY('potato', 'tomato') jw_sim
  FROM DUAL;

听起来像你可以使用JARO_WINKLER_SIMILARITY。将两个日期转换为标准字符串表示形式(例如TO_CHAR(aDate,'DD / MM / YYYY HH24:MI:SS')),然后比较它们。

(顺便说一句,TO_NUMBER应用于JARO_WINKLER的结果,因为Oracle在调用JARO_WINKLER时抛出ORA-031115 : unsupported network datatype or representation,因为它返回BINARY_DOUBLE,这是Oracle界面在Windows上的例程平台似乎无法处理。如果你不能使用该类型为什么有类型????: - )

分享并享受。

答案 1 :(得分:0)

如果您正在纠正数据输入错误,那么加权不同的日期部分可能没有任何优势 - 我假设同样可能的是,当年部分会出现键错误。因此,这是一个模糊的字符串匹配问题,而不是模糊的日期匹配问题。

一组常用的模糊字符串匹配算法是edit distance - Hamming distance很快但假设不正确的字符串不包含任何字符添加/删除(因此它会很好地执行比较“hello”和“gello”,但不比较“hello”和“hhello”),而Levenshtein distance的计算成本更高,但能够解释错误字符串中的字符添加/删除。

答案 2 :(得分:0)

我有相同的搜索,所以我将分享我的日期函数版本 `参见代码DateMatch:

 CREATE Function [fn_DateMatch] (@dt1 DateTime, @dt2 DateTime)
 RETURNS FLOAT 
   AS 
 BEGIN
 DECLARE @Result FLOAT, @yyyy1 NUMERIC, @yyyy2 NUMERIC, @mm1 NUMERIC, @mm2 NUMERIC, @dd1 NUMERIC, @dd2 NUMERIC, @threshold NUMERIC  
   SELECT @Result = 0, @threshold = 85 
   IF @dt1 = @dt2 SET @Result = 100 
   IF (@Result < 100) SET @Result = 100-abs(DATEDIFF (DAY,@dt1, @dt2)) 
   IF (@Result < @threshold)
   BEGIN 
     SELECT @yyyy1 = CONVERT (INT, DATEPART (year, @dt1)), @mm1 = CONVERT  (INT, DATEPART (month, @dt1)), @dd1 = CONVERT  (INT, DATEPART (day, @dt1)) 
          , @yyyy2 = CONVERT (INT, DATEPART (year, @dt2)), @mm2 = CONVERT  (INT, DATEPART (month, @dt2)), @dd2 = CONVERT  (INT, DATEPART (day, @dt2))
     SET @Result = 100-((@yyyy1+@yyyy2)*3.0+(@mm1+@mm2)*3.0+(@dd1+@dd2)*1.0)*3
     IF (@Result < @threshold) and @yyyy1=@yyyy2 and @mm1+@dd1=@mm2+@dd2 SET @Result = 90
   END 
   IF (@Result < @threshold) 
     BEGIN  
     IF convert(varchar, @dt1, 108) <> '00:00:00' and convert(varchar, @dt2, 108) <> '00:00:00' 
        BEGIN
          SET @Result = 100-((CONVERT(float, dbo.fn_levenshtein (convert(varchar, @dt1, 120), convert(varchar, @dt2, 120)))/CONVERT(float,len(convert(varchar, @dt2, 120))))*100) 
        END 
     ELSE  
        BEGIN 
          SET @Result = 100-((CONVERT(float, dbo.fn_levenshtein (convert(varchar, @dt1, 112), convert(varchar, @dt2, 112)))/CONVERT(float,len(convert(varchar, @dt2, 112))))*100) 
        END 
     END 
   RETURN @Result 
 END;`