Oracle TO_DATE throws ORA-01843 when used in WHERE but not in SELECT

huangapple go评论67阅读模式
英文:

Oracle TO_DATE throws ORA-01843 when used in WHERE but not in SELECT

问题

我有一个关于一个(相对)简单查询的问题,我不理解,希望有人可以帮助我解决。

以下是查询:

SELECT TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), 'YYYYMMDD'),
       TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), 'YYYYMMDD')
  FROM (SELECT * FROM babw WHERE ABWABTNR <> 'PASRZ') a
  WHERE  
    trunc(SYSDATE)
    BETWEEN 
     TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), 'YYYYMMDD')
    AND 
     TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), 'YYYYMMDD')

这个查询中令人好奇的是,它会引发一个ORA-01843错误,但仅与WHERE子句一起,如果我删除WHERE子句,则不会引发错误。

所以这个查询有效:

SELECT TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), 'YYYYMMDD'),
       TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), 'YYYYMMDD')
  FROM (SELECT * FROM babw WHERE ABWABTNR <> 'PASRZ') a

由于WHERE部分使用与SELECT部分完全相同的内容,我想知道这是如何可能的?

一些背景信息:

  • 这两列(ABWENDDAT,ABWBEGDAT)的数据类型是VARCHAR2(14)。
  • 我检查了列的内容,我们只有两个条目会触发此异常(条目是:99999999999999),但这两个条目都通过了WHERE ABWABTNR <> 'PASRZ'子句的筛选。
  • 我还确保了每个语句都返回了所有行,因此在执行语句后,我会遍历返回的所有行(直到结束)。

我还检查了Stack Overflow 并找到了一些与此类似的问题,但我没有找到一个对我有效的答案或解释这种行为的问题。

我认为这种行为的原因可能是执行计划(或执行的先后顺序)。因此,可以触发错误的两行在WHERE子句引发ORA-01843之后但在SELECT部分之前被过滤掉。这是否正确,如果是的话,是否有人有办法更改查询以使其正常工作?

提前谢谢您!

英文:

I have a problem with a (relativ) simple query which I do not understand, and I hope someone can help me on this.

Here we have the query:

SELECT TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;),
       TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
  FROM (SELECT * FROM babw WHERE ABWABTNR &lt;&gt; &#39;PASRZ&#39;) a
       WHERE  
        trunc(SYSDATE)
        BETWEEN 
         TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
        AND 
         TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;)

The curious thing in this query is that it throw a ORA-01843 but only with the WHERE clause if I remove the WHERE clause no error is thrown.

So this works

SELECT TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;),
       TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
  FROM (SELECT * FROM babw WHERE ABWABTNR &lt;&gt; &#39;PASRZ&#39;) a

Since the WHERE part is using the exact same things as the SELECT part I am asking myself how this is possible?

Some background information:

  • The datatype of the both columns (ABWENDDAT, ABWBEGDAT) is VARCHAR2(14)
  • I checked the content of the columns we only have two entries which will trigger this exception (entries are: 99999999999999) but this two entries are filtered with the WHERE ABWABTNR <> 'PASRZ' clause.
  • I also did make sure all rows (per statement) are returned, so after I execute a statement I go through all the rows (until the end) returned.

I also checked stackoverflow and found some questions which go in the same direction but I did not find a question with a answer which works for me or explain the behaviour.

I think the reason for this behaviour can be the execution plan (or precidence of execution). So the two rows which can trigger the error are filterd after the WHERE which throws the ORA-01843 but before the SELECT part. can this be true and if so, do someone have a idea how I can change the query so this is working?

Thank you in advance!

答案1

得分: 2

以下是您要翻译的内容:

可能的情况是,谓词

TRUNC(sysdate) BETWEEN TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), 'YYYYMMDD')
                    AND TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), 'YYYYMMDD')

被推入了您的内部查询中,该查询应该过滤掉非日期。

(SELECT * FROM babw WHERE ABWABTNR <> 'PASRZ') 

这意味着这个查询实际上变成了

SELECT * 
  FROM babw 
 WHERE ABWABTNR <> 'PASRZ'
   AND TRUNC(sysdate) BETWEEN TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), 'YYYYMMDD')
                          AND TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), 'YYYYMMDD')

导致您的错误

在以下情况下,要做的“最佳”事情按顺序是

  • 仅将日期存储在具有日期数据类型的列中
  • 查找并更正不正确的日期

在这两者都没有发生的情况下,您可以使用 no_push_pred 提示来避免将谓词推入内部查询

SELECT /*+ no_push_pred(a) */
      TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), 'YYYYMMDD'),
       TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), 'YYYYMMDD')
  FROM (SELECT * FROM babw WHERE ABWABTNR <> 'PASRZ') a
       WHERE  
        trunc(SYSDATE)
        BETWEEN 
         TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), 'YYYYMMDD')
        AND 
         TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), 'YYYYMMDD')
英文:

It is possible that the predicate

TRUNC(sysdate) BETWEEN TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
                    AND TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;)

is being pushed into your inner query, which should be filtering out the non-dates.

(SELECT * FROM babw WHERE ABWABTNR &lt;&gt; &#39;PASRZ&#39;) 

which means this query effectively becomes

SELECT * 
  FROM babw 
 WHERE ABWABTNR &lt;&gt; &#39;PASRZ&#39;
   AND TRUNC(sysdate) BETWEEN TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
                          AND TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;)

causing your error

The "best" things to do are, in order,

  • Only store dates in a column with a date datatype
  • Find and correct the incorrect dates

In the absence of either of these occurring you could use the no_push_pred hint to avoid pushing the predicate into the inner query

SELECT /*+ no_push_pred(a) */
      TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;),
       TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
  FROM (SELECT * FROM babw WHERE ABWABTNR &lt;&gt; &#39;PASRZ&#39;) a
       WHERE  
        trunc(SYSDATE)
        BETWEEN 
         TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
        AND 
         TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;)

答案2

得分: 2

SQL引擎选择重写您的查询,删除嵌套子查询,使您的第一个查询有效:

SELECT TO_DATE (SUBSTR (ABWENDDAT, 1, 8), 'YYYYMMDD'),
       TO_DATE (SUBSTR (ABWBEGDAT, 1, 8), 'YYYYMMDD')
FROM   babw
WHERE  ABWABTNR <> 'PASRZ'
AND    trunc(SYSDATE) BETWEEN TO_DATE (SUBSTR (ABWBEGDAT, 1, 8), 'YYYYMMDD')
                      AND     TO_DATE (SUBSTR (ABWENDDAT, 1, 8), 'YYYYMMDD');

并且BETWEEN子句在ABWABTNR比较之前进行评估。

您可以尝试使用提示来解决此问题。可以是:

  • 在外部查询中使用 /*+ no_push_pred(a) */;或
  • 在内部查询中使用 /*+ no_merge */

或者,您可以使用 ROWNUM 来实现内部查询的材料化:

SELECT TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), 'YYYYMMDD'),
       TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), 'YYYYMMDD')
FROM   (
  SELECT * FROM babw WHERE ABWABTNR <> 'PASRZ' AND ROWNUM > 0
) a
WHERE  trunc(SYSDATE) BETWEEN TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), 'YYYYMMDD')
                      AND     TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), 'YYYYMMDD');

或者,您可以使用 CASE 表达式:

SELECT TO_DATE (SUBSTR (ABWENDDAT, 1, 8), 'YYYYMMDD'),
       TO_DATE (SUBSTR (ABWBEGDAT, 1, 8), 'YYYYMMDD')
FROM   babw
WHERE  CASE
       WHEN ABWABTNR <> 'PASRZ'
       AND  trunc(SYSDATE) BETWEEN TO_DATE (SUBSTR (ABWBEGDAT, 1, 8), 'YYYYMMDD')
                           AND     TO_DATE (SUBSTR (ABWENDDAT, 1, 8), 'YYYYMMDD')
       THEN 1
       END = 1;
英文:

The SQL Engine is opting to rewrite your query without the nested sub-queries so your first query is effectively:

SELECT TO_DATE (SUBSTR (ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;),
       TO_DATE (SUBSTR (ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
FROM   babw
WHERE  ABWABTNR &lt;&gt; &#39;PASRZ&#39;
AND    trunc(SYSDATE) BETWEEN TO_DATE (SUBSTR (ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
                      AND     TO_DATE (SUBSTR (ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;)

and the BETWEEN clause is being evaluated before the ABWABTNR comparison.

You can try using hints to solve the issue. Either:

  • /*+ no_push_pred(a) */ in the outer query; or
  • /*+ no_merge */ in the inner query.

Or you can materialize the inner query using ROWNUM:

SELECT TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;),
       TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
FROM   (
  SELECT * FROM babw WHERE ABWABTNR &lt;&gt; &#39;PASRZ&#39; AND ROWNUM &gt; 0
) a
WHERE  trunc(SYSDATE) BETWEEN TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
                      AND     TO_DATE (SUBSTR (a.ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;);

Or you can use a CASE expression:

SELECT TO_DATE (SUBSTR (ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;),
       TO_DATE (SUBSTR (ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
FROM   babw
WHERE  CASE
       WHEN ABWABTNR &lt;&gt; &#39;PASRZ&#39;
       AND  trunc(SYSDATE) BETWEEN TO_DATE (SUBSTR (ABWBEGDAT, 1, 8), &#39;YYYYMMDD&#39;)
                           AND     TO_DATE (SUBSTR (ABWENDDAT, 1, 8), &#39;YYYYMMDD&#39;)
       THEN 1
       END = 1;

答案3

得分: 1

可以这样做是因为表中的每一行都具有 a.ABWENDDAT 和 a.ABWENDDAT,可以转换为具有该格式的日期。
如果您的 Oracle 版本 >12,则可以使用 TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8) default null on conversion error, 'YYYYMMDD')。如果有一行无法进行此转换,并且没有设置默认的转换错误为 null,那么该单行将引发异常。
或者,如果 Oracle 版本 <12,则应首先使用公共表达式筛选出那些无法将这些列中的数据转换为日期的行。

英文:

It is possible because not just every row in that table has both a.ABWENDDAT and a.ABWENDDAT such as to be convertible to date with that format.
In case you have Oracle version >12 then you may use TO_DATE (SUBSTR (a.ABWBEGDAT, 1, 8) default null on conversion error, 'YYYYMMDD'). If you have one row where this conversion will not be possible and you do not have default null on conversion error, then that single row will cause the exception.
Or, if Oracle version <12, then you should first filter out the rows in which the data in those columns cannot be converted to date using a common-table expression.

huangapple
  • 本文由 发表于 2023年5月24日 18:45:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/76322667.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定