【问题标题】:Valid date verification in SQLSQL 中的有效日期验证
【发布时间】:2018-09-12 14:04:43
【问题描述】:

我有一列以 (yyyymmdd) 的格式保存 LossDate。我需要验证如果该月以 30 天结束,那么损失日期为 31 的情况下没有报告任何损失。我有数百万条记录。帮助将不胜感激。

LossDate
--------
20120128
20150520
20180631

查询应该返回最后一条无效记录,因为 200806 在 30 天后结束。

【问题讨论】:

  • 列数据类型?
  • 它是 db2 中的数字
  • DB2 有一个 LAST_DAY() 函数 (ibm.com/support/knowledgecenter/en/SSEPEK_11.0.0/sqlref/src/tpc/…),可以让您确定您的 LossDate 是否等于当月的 LAST_DAY。但是因为它是numeric,我认为您必须将其转换为实际日期或正确表示日期的字符串。 DB2 对待数字实体不同于字符串实体,因为它与“日期”相关,因此 20180631 可能与 '20180631' 不同。也许使用TO_CHAR(LossDate)TIMESTAMP_FORMAT(TO_CHAR(LossDate),'YYYYMMDD')
  • 但是为什么你的“日期”值是20180631?这不是一个有效的日期吗?那么,您为什么要在当月最后一天之后的日期报告亏损呢?那么二月呢?
  • 正确,旧系统,数据输入错误,因为没有触发器。所以我们正在清理无效数据。

标签: sql db2


【解决方案1】:

您可以在下面创建一个 ISDATE 函数,然后在 SELECT * FROM TABLE WHERE DB_IS_DATE(LOSSDATE) = 0 等查询中使用它来查找所有无效日期

CREATE OR REPLACE FUNCTION IS_DATE(YYYYMMDD INTEGER)
RETURNS SMALLINT
LANGUAGE SQL CONTAINS SQL DETERMINISTIC NO EXTERNAL ACTION
RETURN
  CASE
    WHEN YYYYMMDD/10000 BETWEEN 1 AND 9999
    AND(    ( MOD(YYYYMMDD/100, 100) IN (1,3,5,7,8,10,12) AND MOD(YYYYMMDD,100) BETWEEN 1 AND 31 )  -- Jan,Mar,May,Jul,Aug,Oct,Dec have 31 days
         OR ( MOD(YYYYMMDD/100, 100) IN (4,6,9,11)        AND MOD(YYYYMMDD,100) BETWEEN 1 AND 30 )  -- Apr,Jun,Sep,Nov             have 30 days
         OR ( MOD(YYYYMMDD/100, 100) = 2                  AND MOD(YYYYMMDD,100) BETWEEN 1 AND 28 )  -- Feb has 28 days
         OR ( MOD(YYYYMMDD/100, 100) = 2                  AND MOD(YYYYMMDD,100) BETWEEN 1 AND 29    --   unless is a leap year. i.e.
              AND ( ( MOD(YYYYMMDD/10000,4) = 0 AND MOD(YYYYMMDD/10000,100) <> 0)                   --   year is divisable by 4 but not 100
                OR  MOD(YYYYMMDD/10000,400) = 0 )                                                   --     or year is divisable by 400
            )       
         )
    THEN 1
    ELSE 0
  END

如果您使用的是 Db2 Warehouse,则可以从函数返回 BOOLEAN。例如

CREATE OR REPLACE FUNCTION IS_DATE(YYYYMMDD INTEGER)
RETURNS BOOLEAN
LANGUAGE SQL CONTAINS SQL DETERMINISTIC NO EXTERNAL ACTION
RETURN
        YYYYMMDD/10000 BETWEEN 1 AND 9999
    AND(    ( MOD(YYYYMMDD/100, 100) IN (1,3,5,7,8,10,12) AND MOD(YYYYMMDD,100) BETWEEN 1 AND 31 )  -- Jan,Mar,May,Jul,Aug,Oct,Dec have 31 days
         OR ( MOD(YYYYMMDD/100, 100) IN (4,6,9,11)        AND MOD(YYYYMMDD,100) BETWEEN 1 AND 30 )  -- Apr,Jun,Sep,Nov             have 30 days
         OR ( MOD(YYYYMMDD/100, 100) = 2                  AND MOD(YYYYMMDD,100) BETWEEN 1 AND 28 )  -- Feb has 28 days
         OR ( MOD(YYYYMMDD/100, 100) = 2                  AND MOD(YYYYMMDD,100) BETWEEN 1 AND 29    --   unless is a leap year. i.e.
              AND ( ( MOD(YYYYMMDD/10000,4) = 0 AND MOD(YYYYMMDD/10000,100) <> 0)                   --   year is divisable by 4 but not 100
                OR  MOD(YYYYMMDD/10000,400) = 0 )                                                   --     or year is divisable by 400
            )       
         )

如果输入是有效日期,则该函数可用于将输入转换为 DATE。例如

SELECT i, IS_DATE(i) AS IS_DATE
,      CASE WHEN IS_DATE(i) = 1 THEN DATE(TO_DATE(DIGITS(DECIMAL(i,8,0)),'YYYYMMDD')) END AS DATE
FROM TABLE(VALUES(-999),(0),(1),(00010101),(99991231),(20180101),(20180228),(20160229),(20180229),(20000229),(19000229)) as D(i)

返回

 I        IS_DATE DATE
 -------- ------- ----------
     -999       0 NULL
        0       0 NULL
        1       0 NULL
    10101       1 0001-01-01
 99991231       1 9999-12-31
 20180101       1 2018-01-01
 20180228       1 2018-02-28
 20160229       1 2016-02-29
 20180229       0 NULL
 20000229       1 2000-02-29
 19000229       0 NULL

该函数还将接受“YYYYMMDD”格式的字符串。 Db2 将为您将字符串转换为整数。 如果你有一串例如“YYYY-MM-DD”格式,你可以创建一个调用上述函数的函数的字符版本如

CREATE OR REPLACE FUNCTION IS_DATE(YYYYMMDD VARCHAR(10))
RETURNS INTEGER
LANGUAGE SQL CONTAINS SQL DETERMINISTIC NO EXTERNAL ACTION
RETURN
   CASE WHEN REGEXP_LIKE(YYYYMMDD,'[0-9]{4}?-[0-9]{2}?-[0-9]{2}?') = 1 
        THEN IS_DATE(INTEGER(REPLACE(YYYYMMDD,'-',''))) ELSE 0 END

另一种选择是创建一个使用内置 Db2 函数的 UDF,但会捕获它们可能生成的任何错误。例如

CREATE OR REPLACE FUNCTION IS_DATE(input VARCHAR(32), format VARCHAR(32)) 
RETURNS INTEGER
LANGUAGE SQL CONTAINS SQL DETERMINISTIC ALLOW PARALLEL NO EXTERNAL ACTION
BEGIN 
  DECLARE CONTINUE HANDLER FOR SQLEXCEPTION, SQLWARNING
    RETURN 0;
  RETURN CASE WHEN TO_DATE(input, format) >= '0001-01-01' THEN 1 ELSE 0 END;
END
@

【讨论】:

  • 我对我的帖子进行了一些编辑。以上应该是最终版(希望)
【解决方案2】:

对于您的具体问题,以下将返回错误行:

select * 
from table 
where substr(lossdate,5,2) in ('04','06','09','11')
    and substr(lossdate,7,2) > '30'

根据您的输入界面,您可能需要在 31 天的月份内重复此操作,显示大于 31 的值。

select * 
from table 
where substr(lossdate,5,2) in ('01','03','07','08','10','12')  
    and substr(lossdate,7,2) > '31'

2 月有点棘手。逐步开始,识别所有超过 29 天的 2 月条目,您可以立即删除/修复这些条目。

select * 
from table 
where substr(lossdate,5,2) = '02' 
    and substr(lossdate,7,2) > '29'

然后,您需要对 2 月日期大于 28 且不是闰年的其余行重复此操作。在过去的 100 年中,任何能被 4 整除的年份都是闰年,因此您可以以此识别余数(假设您已更正/删除了已找到的错误 2 月条目):

select * 
from table 
where substr(lossdate,5,2) = '02' 
    and mod(substr(lossdate,1,4),4)) <> 0 
    and substr(lossdate,7,2) > '28' 

【讨论】:

  • 非常感谢。
  • 请注意,如果您正在处理数百万行,我不确定这将如何执行。
  • 它将进行 tablescan 并占用一些 cpu 来执行所有 mod() 和 substr()。为了避免 4 次不同的扫描,我将 OR WHERE 条件放在一起。
  • (为了避免您的查询锁定其他人,也许用 UR 运行它。)
猜你喜欢
  • 1970-01-01
  • 2018-04-10
  • 1970-01-01
  • 2018-08-29
  • 1970-01-01
  • 1970-01-01
  • 2020-06-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多