2016-06-16 24 views
0

有人可以解释这种情况吗? 例子,我有数据转储表是这样的:SQL Oracle,得到不同的结果IN和BETWEEN

TGL 
19810909 
19761026 
19832529 

当我与此查询执行:

SELECT to_date(tgl,'YYYYMMDD') tgl 
    FROM 
     (
     SELECT tgl 
     FROM tmpx 
     WHERE 
     SUBSTR(tgl,5,2) BETWEEN '01' AND '12' 
     AND length(tgl) = 8 
     ) 
    WHERE to_date(tgl,'YYYYMMDD') < to_date('19811231','YYYYMMDD') 

结果:没有错误

TGL 
09/09/1981 
26/10/1976 

但是,当我执行与此查询:

SELECT to_date(tgl,'YYYYMMDD') tgl 
FROM 
    (
    SELECT tgl 
    FROM tmpx 
    WHERE 
    SUBSTR(tgl,5,2) IN ('01','02','03','04','05','06','07','08','09','10','01','12') 
    AND length(tgl) = 8 
    ) 
WHERE to_date(tgl,'YYYYMMDD') < to_date('19811231','YYYYMMDD') 

结果:错误

ORA-01843: not a valid month 

为什么行号第三(19832529),包括选择会导致一个错误? 而如果我执行以下查询:

SELECT tgl 
FROM tmpx 
WHERE 
SUBSTR(tgl,5,2) IN ('01','02','03','04','05','06','07','08','09','10','11','12') 
AND length(tgl) = 8 

的结果是这样的(wihtout行号3)

TGL 
19810909 
19761026 

谢谢。

回答

1

如果你看看这两个查询的执行计划,你可以看到它们是如何被优化器处理的。对于第一个:

--------------------------------------------------------------------------            
| Id | Operation   | Name | Rows | Bytes | Cost (%CPU)| Time  |            
--------------------------------------------------------------------------            
| 0 | SELECT STATEMENT |  |  1 |  6 |  3 (0)| 00:00:01 |            
|* 1 | TABLE ACCESS FULL| TMPX |  1 |  6 |  3 (0)| 00:00:01 |            
--------------------------------------------------------------------------            

Predicate Information (identified by operation id):                  
---------------------------------------------------                  

    1 - filter(LENGTH("TGL")=8 AND SUBSTR("TGL",5,2)>='01' AND               
       SUBSTR("TGL",5,2)<='12' AND TO_DATE("TGL",'YYYYMMDD')<TO_DATE('           
       1981-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss')) 

而对于第二:

--------------------------------------------------------------------------            
| Id | Operation   | Name | Rows | Bytes | Cost (%CPU)| Time  |            
--------------------------------------------------------------------------            
| 0 | SELECT STATEMENT |  |  1 |  6 |  3 (0)| 00:00:01 |            
|* 1 | TABLE ACCESS FULL| TMPX |  1 |  6 |  3 (0)| 00:00:01 |            
--------------------------------------------------------------------------            

Predicate Information (identified by operation id):                  
---------------------------------------------------                  

    1 - filter(LENGTH("TGL")=8 AND TO_DATE("TGL",'YYYYMMDD')<TO_DATE('             
       1981-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND              
       (SUBSTR("TGL",5,2)='01' OR SUBSTR("TGL",5,2)='02' OR              
       SUBSTR("TGL",5,2)='03' OR SUBSTR("TGL",5,2)='04' OR              
       SUBSTR("TGL",5,2)='05' OR SUBSTR("TGL",5,2)='06' OR              
       SUBSTR("TGL",5,2)='07' OR SUBSTR("TGL",5,2)='08' OR              
       SUBSTR("TGL",5,2)='09' OR SUBSTR("TGL",5,2)='10' OR              
       SUBSTR("TGL",5,2)='12'))   

注意,应用过滤器的顺序。在第一个中,它首先查看子字符串,并且只有通过该过滤器的值才会转换为1998年比较的日期。

在第二个日期检查首先完成,所以它会尝试转换无效值,然后它将其过滤掉。

这里真正的问题是将日期存储为字符串,它允许输入无效数据。如果你坚持这样做,那么另一种方法是使用函数来尝试将字符串转换为日期并忽略抛出的错误,这仍然不理想,但会忽略相同的值。有很多这样的例子,including this one of mine。喜欢的东西,你可以这样做:

SELECT safe_to_date(tgl) tgl 
FROM tmpx 
WHERE safe_to_date(tgl) < date '1981-12-31'; 

或者如果你喜欢:

SELECT tgl 
FROM (
    SELECT safe_to_date(tgl) tgl 
    FROM tmpx 
) 
WHERE tgl < date '1981-12-31'; 

你的函数只能找YYYYMMDD格式字符串,或者你可以通过在你要检查的格式,如果你不希望它变得灵活。

+0

fyi,转储表中的数据来自导入txt文件。这就是为什么第三行需要执行,其中'where子句'(WHERE to_date(tgl,'YYYYMMDD') thesuhu

+0

子查询应该首先得到结果2行 – thesuhu

+0

@thesuhu - 但这取决于优化器的决定。您可以从SQL \ * Loader或外部表定义中调用'safe_to_date()'类型的函数。或者只是使用'between'版本,如果优化器选择不同,那么可能会有一天会中断。但实际上,如果您在该领域获取任何无效数据,则您不知道其他数据是否正确或错误,但发生验证。 –