【问题标题】:SQL Oracle, get difference result IN and BETWEENSQL Oracle,得到不同的结果 IN 和 BETWEEN
【发布时间】:2016-06-16 16:40:55
【问题描述】:

有人能解释一下这个案例吗? 例如,我有一个包含如下数据的转储表:

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

结果是这样的(没有第 3 行)

TGL
19810909
19761026

谢谢。

【问题讨论】:

    标签: oracle


    【解决方案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 格式字符串,或者如果您不希望它灵活,您可以传入要检查的格式。

    【讨论】:

    • fyi,转储表中的数据来自导入 txt 文件。关键是为什么要执行第三行,其中“where 子句”(WHERE to_date(tgl,'YYYYMMDD')
    • 子查询应该先得到结果 2 行
    • @thesuhu - 但这取决于优化者来决定。您可以从 SQL*Loader 或外部表定义中调用 safe_to_date() 类型的函数。或者只使用您的between 版本,如果优化器选择不同,它可能会在某一天中断。但实际上,如果您在该字段中获得任何无效数据,您不知道其他数据是正确的还是错误的,但碰巧验证了。
    • 感谢您的建议。我试试看。
    • 如何防止oracle重写我的查询?我使用了 /*+ NO_QUERY_TRANSFORMATION */ 但仍然没有受到影响。
    猜你喜欢
    • 2017-08-10
    • 1970-01-01
    • 2013-09-09
    • 2013-09-07
    • 1970-01-01
    • 2023-03-19
    • 2017-05-27
    • 1970-01-01
    • 2019-06-25
    相关资源
    最近更新 更多