【问题标题】:Not a valid month不是一个有效的月份
【发布时间】:2016-10-20 15:15:19
【问题描述】:

我无法弄清楚为什么我会收到无效月份错误。我可以使用 to_date 返回每个字段而不会出错,但是当我添加 between 过滤器时它会爆炸。我正在使用甲骨文。奇怪的是不是每张唱片都炸了

SELECT *
  FROM timedetail_dates
 WHERE empnum = '501331134'
   AND (TO_DATE ('10/14/2016 04:00', 'mm/dd/yyyy hh24:mi')
           BETWEEN TO_DATE (timein_date, 'mm/dd/yyyy hh24:mi')
               AND TO_DATE (timeout_date, 'mm/dd/yyyy hh24:mi')
       )
   AND tsdate BETWEEN '09-oct-2016' AND '22-oct-2016'
   AND timedetail_dates.DURATION > 0;

【问题讨论】:

  • 哪个 dbms? (TO_DATE 是产品特定的功能。)
  • 可以给Oracle加标签吗
  • timein_datetimeout_datetsdate 列的数据类型是什么?我的猜测是 tsdate 是 DATE 或 TIMESTAMP 数据类型,您需要在 '09-oct-2016''22-oct-2016' 字符串周围添加 to_date。
  • 这两个字段的数据类型是 varchar。 tsdate 是一个日期字段。 timeout_date 时间在示例中是午夜,这不起作用。如果我使用不同的日期,其他记录返回没有错误
  • 为什么要将 DATE 存储在 VARCHAR 列中?

标签: sql oracle


【解决方案1】:

将您的日期包装在 TO_DATE 的 BETWEEN 子句中,就像

AND tsdate BETWEEN TO_DATE('09-oct-2016', 'dd-MON-yyyy') AND TO_DATE('22-oct-2016', 'dd-MON-yyyy')

此外,如果您的 tsdate 列不是日期类型,您也需要将其包装在您在列中存储数据的格式中。

【讨论】:

  • 它肯定发生在中间部分。 timein_date 和 timeout_date 来自 varchar 字段中的视图。
  • 好的 - 您是否尝试在 BETWEEN 语句中使用 to_date?
  • 不,但即使我注释掉部分之间的 tsdate,它仍然会出错
  • 那么现在的行为不同了吗?在您的 OP 中,您说只有在添加 BETWEEN 逻辑时才会出错。
  • AND (TO_DATE ('10/14/2016 04:00', 'mm/dd/yyyy hh24:mi') BETWEEN TO_DATE (timein_date, 'mm/dd/yyyy hh24:mi') AND TO_DATE (timeout_date, 'mm/dd/yyyy hh24:mi')
【解决方案2】:

错误原因:

听起来您的表格中的日期无效。当正在解析的字符串不正确时,您会收到此错误。这是一个生成它的示例查询。

SELECT TO_DATE('22-10-2016 05:31', 'mm/dd/yyyy hh24:mi') FROM DUAL;

防止这种情况再次发生:

a_horse_with_no_name implies 一样,使用VARCHAR(或VARCHAR2 或任何文本类型)通常是时间数据的不良数据类型。这是部分原因:很难防止无效数据进入数据库。

首先要防止不良数据进入数据库:

  1. 理想的解决方案是将这些列转换为实际的时间类型(在修复现有数据之后),例如TIMESTAMP(可能是WITH TIME ZONE)或DATE
  2. 如果这不可行,则添加一个约束或触发器,它会在错误的格式上引发错误。请注意,您需要弄清楚“正确”的格式是什么。这里的部分问题可能是您有非美国用户(或者可能是喜欢 ISO 8601 的精通计算机的人),并且应用程序需要强制执行特定格式或自动解析为您决定的标准
  3. 如果您真的无法阻止不同的格式进入并且无法转换为实际日期,那么您可以存储一个带有格式的额外列。您仍然需要验证日期是否与同一行中包含的任何格式实际匹配。
  4. 您还应该查看正在向该数据库添加数据的所有应用程序。它们可能缺乏对输入的验证或包含允许创建无效数据的错误。

修复现有数据:

要找到有问题的行,试试这个。首先创建一个函数:

CREATE OR REPLACE FUNCTION DATETIME_IS_VALID_FOR_FORMAT(
  TEXT_DATETIME VARCHAR2,
  DATETIME_FORMAT VARCHAR2
)
  RETURN VARCHAR2
  IS
    DUMMYVAR DATE;
  BEGIN
    -- The assignment is only here to get the function to compile
    DUMMYVAR := TO_DATE(TEXT_DATETIME, DATETIME_FORMAT);
    RETURN 'TRUE';
  EXCEPTION
    WHEN OTHERS THEN
      RETURN 'FALSE';
  END;
/

现在SELECT 这是'FALSE' 所在的行:

SELECT *
FROM timedetail_dates
WHERE
  DATETIME_IS_VALID_FOR_FORMAT(timein_date, 'mm/dd/yyyy hh24:mi') != 'TRUE' OR
  DATETIME_IS_VALID_FOR_FORMAT(timeout_date, 'mm/dd/yyyy hh24:mi') != 'TRUE'

如果由于数据库权限低而无法创建函数,则必须改用DBMS_OUTPUT。在下面的匿名块中替换ID_COLUMN,您可以使用它来查找坏行:

DECLARE
  FUNCTION DATETIME_IS_VALID_FOR_FORMAT(
    TEXT_DATETIME VARCHAR2,
    DATETIME_FORMAT VARCHAR2
  )
    RETURN VARCHAR2
    IS
      DUMMYVAR DATE;
    BEGIN
      -- The assignment is only here to get the function to compile
      DUMMYVAR := TO_DATE(TEXT_DATETIME, DATETIME_FORMAT);
      RETURN 'TRUE';
    EXCEPTION
      WHEN OTHERS THEN
        RETURN 'FALSE';
    END;
BEGIN
  FOR T_ROW IN (SELECT * FROM timedetail_dates) LOOP
    IF (
      DATETIME_IS_VALID_FOR_FORMAT(T_ROW.TIMEIN_DATE, 'mm/dd/yyyy hh24:mi') != 'TRUE' OR
      DATETIME_IS_VALID_FOR_FORMAT(T_ROW.TIMEOUT_DATE, 'mm/dd/yyyy hh24:mi') != 'TRUE'
    ) THEN
      -- Replace ID_COLUMN with your actual primary key
      DBMS_OUTPUT.PUT_LINE('Bad row: '||T_ROW.ID_COLUMN);
    END IF;
  END LOOP;
END;
/

请注意,您可能需要做一些准备才能让您的客户端开始捕获来自DBMS_OUTPUT.PUT_LINE 的输出。 (这取决于客户端,但您必须在 SQL*Plus 和 Oracle SQL Developer 中打开它。)另外请注意,在块完成之前不会显示任何输出。

【讨论】:

    猜你喜欢
    • 2012-10-11
    • 1970-01-01
    • 2012-03-23
    • 2017-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多