【问题标题】:When querying against a view, a filtering clause in the view's definition is being ignored查询视图时,视图定义中的过滤子句将被忽略
【发布时间】:2017-09-18 19:57:23
【问题描述】:

我有一张表SCHEDULES,列有LDATESCHTYPEID。如果SCHTYPEID = 1,则LDATE 包含数字格式的日期(例如,今天的日期为 20170918)。如果SCHTYPEID = 2,则LDATE 包含0。

经常在编写查询时,我会将LDATE 转换为实际日期并过滤掉零,如下所示:

SELECT TO_DATE(LDATE, 'YYYYMMDD') LDATE
FROM SCHEDULES
WHERE SCHTYPEID = 1;

但是,当我把它放到视图中时

CREATE VIEW FOO (THE_DATE)
AS SELECT TO_DATE(LDATE, 'YYYYMMDD')
  FROM SCHEDULES
  WHERE SCHTYPEID = 1;

并像这样查询它:

SELECT *
FROM FOO
WHERE THE_DATE = TO_DATE(20170918, 'YYYYMMDD');

我收到错误“ORA-01840:输入值不足以用于日期格式”。像这样查询它:

SELECT * FROM FOO;

工作得很好。但是,每当我尝试过滤它时,Oracle 似乎都会忽略视图中SCHTYPEID 上的过滤器,它包含SCHTYPEID = 2 的记录,从而导致错误。

我能做些什么来确保我只会查询填充了LDATE 列的行吗?

【问题讨论】:

  • 我很好奇您在运行 SELECT /*+NO_MERGE(FOO)*/ * FROM FOO WHERE THE_DATE = TO_DATE(20170918, 'YYYYMMDD'); 时是否遇到同样的问题;
  • @EdmCoff,我在运行您的查询时仍然收到错误消息。
  • 移除隐式转换不会有什么坏处(to_date 不需要number)。听起来您有一些记录,其中schtypeid = 1,但LDATE 没有与您的格式模型对应的格式'YYYYMMDD'
  • @PatrickBacon - 那么在这种情况下,接受答案中的重写如何不会遇到同样的问题?
  • @MartinSmith 我在检查问题时发布了答案。我在12c。我认为结果集正在使用 case 语句方法返回,但是有一些异常被抛出但操作没有注意到。

标签: oracle


【解决方案1】:

您可以让您的视图处理这两种情况(即使WHERE 子句将数据限制为SCHTYPEID = 1),如下所示:

CREATE VIEW FOO(THE_DATE) AS
  SELECT CASE WHEN SCHTYPEID = 1 THEN TO_DATE(LDATE, 'YYYYMMDD') ELSE NULL END
    FROM SCHEDULES
   WHERE SCHTYPEID = 1;

【讨论】:

  • 是的,也是很好的解决方案,但是您缺少 SCHTYPEID = 2 行! ;)
  • 这个解决方案似乎确实有效,但我仍然不知道为什么。不过谢谢!
  • @TedFilippidis 原始视图将其过滤掉。问题可能是视图被内联,并且视图中的选择中的表达式在视图内的过滤器删除它们之前对行进行评估。
  • 那么为什么这个答案中的修复会起作用?看起来类似于 SQL Server connect.microsoft.com/SQLServer/feedback/details/537419/… 中的此问题,但我不使用 Oracle,也不知道这是否是该产品的预期可能行为。
  • 忽略我的评论,我错了,它可以评估之前的TO_DATE,特别是如果它使用LDATE上的索引
【解决方案2】:
CREATE VIEW FOO (THE_DATE)
AS SELECT TO_DATE(LDATE, 'YYYYMMDD')
  FROM SCHEDULES
  WHERE SCHTYPEID = 1
UNION ALL
SELECT TO_DATE('19000101', 'YYYYMMDD')
  FROM SCHEDULES
  WHERE SCHTYPEID = 2

【讨论】:

  • 感谢您的回答,但这会导致相同的行为。当我简单地运行“select *”时,它可以工作,但是当我尝试过滤特定日期或日期范围时,我得到输入值错误。
  • 那么你需要决定一个意味着零的日期,例如将 null 替换为 TO_DATE('1-1-1900','dd-mm-yyyy)
  • @dunnza 在上述更改后仍然无法为您工作?我很好奇
  • 我尝试了您的解决方案,但仍然出现相同的输入错误!再次感谢您试一试。
【解决方案3】:

将日期放入 DATE 列并没有什么坏处

即使视图定义使用CASE 语句有条件地转换ldate 列,sql 语句也会导致错误。

  • 您可以将日期放入DATE 列。这是最好的方法。
  • 假设您不能执行上述操作,您可能对此列有约束(可能是条件约束)。这可能是下一个最佳解决方案。
  • 假设您无法向表中添加约束,一些应用程序具有围绕分配给各种字段的值集的功能(例如,Oracle 应用程序描述性弹性域属于此类)。

  • 假设您可能不想/不能添加值集,您可以让自定义实用程序包在事后检查数据。这不太理想。

这是我对这个问题的检查:


`SCOTT@dev>CREATE TABLE schedules
  2      AS
  3          ( SELECT
  4              hiredate ldate,
  5              to_number(TO_CHAR(hiredate,'YYYYMMDD') ) ldate_number,
  6              1 schtypeid
  7          FROM
  8              emp
  9          UNION ALL
 10          SELECT
 11              TO_DATE(NULL),
 12              to_number(NULL),
 13              2
 14          FROM
 15              dual
 16          UNION ALL
 17          SELECT
 18              hiredate,
 19              to_number(substr(
 20                  TO_CHAR(hiredate,'YYYYMMDD'),
 21                  1,
 22                  4
 23              ) ),
 24              1
 25          FROM
 26              emp
 27          );

Table SCHEDULES created.

 SCOTT@dev>SELECT
      2      COUNT(1)
      3  FROM
      4      schedules;
    COUNT(1)  
    29    

创建一个实用程序包来检查/查看这些转换异常。我借用了许多人使用的逻辑(请参阅这些链接):

Justin Cave 的解决方案:How to handle to_date exceptions in a SELECT statment to ignore those rows?

Nicholas Krasnov 的解决方案:What exact exception to be caugth while calling TO_DATE in pl/sql code

    SCOTT@dev>CREATE OR REPLACE PACKAGE util AS
      2      FUNCTION to_date_exception (
      3          p_char_literal   IN VARCHAR2,
      4          p_date_format    IN VARCHAR2
      5      ) RETURN VARCHAR2;
      6  
      7      FUNCTION my_to_date (
      8          p_char_literal   IN VARCHAR2,
      9          p_date_format    IN VARCHAR2
     10      ) RETURN DATE;
     11  
     12  END;
     13  /

    Package UTIL compiled

    SCOTT@dev>CREATE OR REPLACE PACKAGE BODY util AS
      2  
      3      FUNCTION to_date_exception (
      4          p_char_literal   IN VARCHAR2,
      5          p_date_format    IN VARCHAR2
      6      ) RETURN VARCHAR2 IS
      7          l_check_date      DATE;
      8          l_error_code      VARCHAR(20);
      9          l_error_message   VARCHAR2(200);
     10      BEGIN
     11          l_check_date := TO_DATE(p_char_literal,p_date_format);
     12           -- NULL will be returned when cast works
     13          RETURN NULL;
     14      EXCEPTION
     15          WHEN OTHERS THEN
     16              l_error_code := 'ORA' || TO_CHAR(sqlcode);
     17              l_error_message := sqlerrm;
     18              RETURN l_error_code;
     19      END;
     20  
     21      FUNCTION my_to_date (
     22          p_char_literal  IN VARCHAR2,
     23          p_date_format IN VARCHAR2
     24      ) RETURN DATE IS
     25          l_date   DATE;
     26      BEGIN
     27          l_date := TO_DATE(p_char_literal,p_date_format);
     28          RETURN l_date;
     29      EXCEPTION
     30          WHEN OTHERS THEN
     31              RETURN TO_DATE(NULL);
     32      END;
     33  
     34  END;
     35  /

    Package Body UTIL compiled

SCOTT@dev>SELECT
  2      util.to_date_exception(ldate_number,'YYYYMMDD') excptn,
  3      util.my_to_date(ldate_number,'YYYYMMDD') the_date_all,
  4      ldate
  5  FROM
  6      schedules
  7  WHERE
  8      schtypeid = 1;
EXCPTN    THE_DATE_ALL             LDATE                    
          17-DEC-1980 12:00:00 AM  17-DEC-1980 12:00:00 AM  
          20-FEB-1981 12:00:00 AM  20-FEB-1981 12:00:00 AM  
          22-FEB-1981 12:00:00 AM  22-FEB-1981 12:00:00 AM  
          02-APR-1981 12:00:00 AM  02-APR-1981 12:00:00 AM  
          28-SEP-1981 12:00:00 AM  28-SEP-1981 12:00:00 AM  
          01-MAY-1981 12:00:00 AM  01-MAY-1981 12:00:00 AM  
          09-JUN-1981 12:00:00 AM  09-JUN-1981 12:00:00 AM  
          09-DEC-1982 12:00:00 AM  09-DEC-1982 12:00:00 AM  
          17-NOV-1981 12:00:00 AM  17-NOV-1981 12:00:00 AM  
          08-SEP-1981 12:00:00 AM  08-SEP-1981 12:00:00 AM  
          12-JAN-1983 12:00:00 AM  12-JAN-1983 12:00:00 AM  
          03-DEC-1981 12:00:00 AM  03-DEC-1981 12:00:00 AM  
          03-DEC-1981 12:00:00 AM  03-DEC-1981 12:00:00 AM  
          23-JAN-1982 12:00:00 AM  23-JAN-1982 12:00:00 AM  
ORA-1840                           17-DEC-1980 12:00:00 AM  
ORA-1840                           20-FEB-1981 12:00:00 AM  
ORA-1840                           22-FEB-1981 12:00:00 AM  
ORA-1840                           02-APR-1981 12:00:00 AM  
ORA-1840                           28-SEP-1981 12:00:00 AM  
ORA-1840                           01-MAY-1981 12:00:00 AM  
ORA-1840                           09-JUN-1981 12:00:00 AM  
ORA-1840                           09-DEC-1982 12:00:00 AM  
ORA-1840                           17-NOV-1981 12:00:00 AM  
ORA-1840                           08-SEP-1981 12:00:00 AM  
ORA-1840                           12-JAN-1983 12:00:00 AM  
ORA-1840                           03-DEC-1981 12:00:00 AM  
ORA-1840                           03-DEC-1981 12:00:00 AM  
ORA-1840                           23-JAN-1982 12:00:00 AM  


28 rows selected. 

原始视图定义:

SCOTT@dev>CREATE OR REPLACE VIEW FOO
  2  AS SELECT TO_DATE(ldate_number, 'FXYYYYMMDD') the_date,
  3      ldate
  4    FROM SCHEDULES
  5    WHERE SCHTYPEID = 1;

View FOO created.

SCOTT@dev>SELECT
  2      COUNT(1)
  3  FROM
  4      foo;
COUNT(1)  
28        


SCOTT@dev>SELECT
  2      *
  3  FROM
  4      foo
  5  WHERE
  6      the_date = TO_DATE(20170918,'YYYYMMDD');

Error starting at line : 1 in command -
SELECT
    *
FROM
    foo
WHERE
    the_date = TO_DATE(20170918,'YYYYMMDD')
Error report -
ORA-01840: input value not long enough for date format

**************************************

使用CASE 语句的建议视图定义:

SCOTT@dev>CREATE OR REPLACE VIEW FOO(THE_DATE) AS
  2    SELECT CASE WHEN SCHTYPEID = 1 THEN TO_DATE(ldate_number, 'YYYYMMDD') ELSE NULL END
  3      FROM SCHEDULES
  4     WHERE SCHTYPEID = 1;

View FOO created.

SCOTT@dev>SELECT
  2      COUNT(1)
  3  FROM
  4      foo;
COUNT(1)
28


    SCOTT@dev>list
  1  SELECT
  2     the_date 
  3  FROM
  4      foo
  5  WHERE
  6*     the_date = TO_DATE(20170918,'YYYYMMDD')
SCOTT@dev>/

Error starting at line : 1 in command -
SELECT
   the_date 
FROM
    foo
WHERE
    the_date = TO_DATE(20170918,'YYYYMMDD')
Error report -
ORA-01840: input value not long enough for date format

从这里可以看出(使用 Oracle 12c),我认为CASE 语句方法存在问题,应该通过确保错误数据不会进入此列来解决根本问题。

【讨论】:

  • 感谢您对此事的看法。首先,我必须说我正在处理的数据来自供应商的应用程序。我无法以任何方式、形状或形式更改架构。
  • 第二个,我觉得你是在找一个不存在的问题。您对 case 语句的视图失败的原因是您故意插入了错误数据。您已将几条记录插入到刚刚年份的schedules 表中。当然,它仍然无法被 'YYYYMMDD' 格式化。在我原来的帖子中,我说如果schtypeid = 1,那么该列有20170922形式的数据。如果schtypeid = 2,那么它是0。这就是我的数据。我曾经见过它没有您建议的错误。
  • 已确认..谢谢
猜你喜欢
  • 2012-09-18
  • 2017-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多