现在的位置: 首页 > 综合 > 正文

Informix Dynamic Server 中的日期处理

2013年08月24日 ⁄ 综合 ⁄ 共 6360字 ⁄ 字号 评论关闭

IBM® Informix® Dynamic Server 有一些操纵日期和时间类型的函数。您可以利用这些函数来对业务数据进行更好的处理和分析。还可以添加新的函数,以简化一些基于日期的业务问题的解决方案。

简介

日期是一种复杂的信息。它表示一年中特定的一天。可以按星期、月份、季度等将日期分组。这种分组便于比较不同年份在一段特定时期的结果。

Informix Dynamic Server(IDS)提供了一些处理日期的功能。本文回顾当前已有的一些函数,并提供一些附加的有用的函数。

 



回页首

IDS 日期函数

IDS 包含两种“日期”数据类型。一种是 DATE,另一种是 DATETIMEDATE 表示一天,而 DATETIME 表示一个特定的时刻,其精度可以从年到秒。IDS 提供了以下函数来操纵这些类型:

  • DATE(VARCHAR(10)) 返回 DATE 类型
    该函数以一个字符串变量为参数,其格式由环境变量 DBDATE 指定,并返回一个 DATE 类型。US English 地区的缺省格式是“MDY4/”。

  • DATE(DATETIME) 返回 DATE 类型
    这个函数与上一个函数相同,但是其输入参数是可以为任意精度的 DATETIME

  • DATE(INTEGER) 返回 DATE 类型
    INTEGER 参数表示从 1899 年 12 月 31 日以来的天数。

  • DAY(DATE) 返回 INTEGER 类型
    DAY 函数返回月中的日,格式为 INTEGER

  • DAY(DATETIME)
    与上一个函数相同,只是这个函数的输入参数为任意精度的 DATETIME

  • EXTEND(DATE, precision) 返回 DATETIME 类型
    EXTEND 函数调整 DATE 参数的精度,并返回适当的 DATETIME。由于说起来有点儿模糊,这里举一个例子:
    EXTEND(DATE(1), YEAR TO SECOND)

  • EXTEND(DATETIME, precision) 返回 DATETIME 类型
    与上一个函数相同,但操作的对象是一个 DATETIME,而不是一个 DATE

  • MONTH(DATE) 返回 INTEGER 类型
    MONTH 从参数 DATE 中提取出月份。

  • MONTH(DATETIME) 返回 INTEGER 类型
    该函数从任意精度的 DATETIME 中提取出月份。

  • WEEKDAY(DATE) 返回 INTEGER 类型
    WEEKDAY 函数根据指定的 DATE 返回一个 INTEGER,表示星期几。0 表示星期天,6 表示星期六。

  • WEEKDAY(DATETIME) 返回 INTEGER 类型
    与上一个函数相同,但操作对象是 DATETIME

  • YEAR(DATE) 返回 INTEGER 类型
    该函数从指定的参数 DATE 中提取出年份。

  • YEAR(DATETIME) 返回 INTEGER 类型
    与上一个函数相同,但操作对象是 DATETIME

  • MDY(INTEGER, INTEGER, INTEGER) 返回 DATE 类型
    该函数根据三个 INTEGER 参数创建一个 DATE。这些参数分别指定月、日和年。注意,年是四位的整数。

  • TO_CHAR(DATE, VARCHAR(??)) 返回 VARCHAR(??) 类型
    该函数带一个 DATE 参数和一个格式参数,并返回一个表示日期的字符串,该字符串遵从要求的格式。格式字符串可以包括:

    • %A: 周
    • %B: 月
    • %d: 十进制表示的日
    • %Y: 4 位数表示的年
    • %R: 按 24 小时计的时间

     

  • TO_CHAR(DATE, VARCHAR(??)) 返回 VARCHAR(??) 类型
    同上。

  • TO_DATE(VARCHAR(??), VARCHAR(??)) 返回 DATE 类型
    这是 TO_CHAR 的逆向操作,使用相同的格式字符串作为第二个参数。

除了上述函数外,还有两个对日期的处理有影响的环境变量:

  • DBDATE:提供日期的终端用户格式。在 SQL 参考手册(第 3-25 页)中对此有描述。
  • DBCENTURY:定义当输入两位数而不是 4 位数的日期时,如何将年份展开。可以接受的值有 RPFC。它们分别表示 Current、Previous、Future 和 Closest。如果没有设置 DBCENTURY,则 R 为缺省值。在 SQL 参考手册(关于 IDS 10.0 的第 3-22 页)中描述了 DBCENTURY

最后,IDS 定义了两个内建的函数,用于返回当前日期值。CURRENT 返回一个 DATETIME 值,而 TODAY 则返回当天的日期。

 



回页首

使用日期函数

前面描述的那些函数提供了输入、输出、格式化和提取信息的功能。我想讨论的第一种有趣的用法是基于字符串的日期的输入。

函数 DATE() 接收一个字符串作为输入,但是根据 DBDATEDBCENTURY 的设置对它进行不同的处理。首先来看 DBCENTURY

DBCENTURY 的缺省值为 R。这意味着世纪由当前日期的世纪决定。下面的例子假设在缺省的 US English 地区运行:

SELECT date("9/2/92") FROM systables WHERE tabid = 1;
(constant)
09/02/2092
1 row(s) retrieved.

 

如果 DBCENTURY 被设为 P,则所指的世纪是离当前日期最近的一个世纪。按照这种设置,前面的例子变成:

SELECT date("9/2/92") FROM systables WHERE tabid = 1;
(constant)
09/02/1992
1 row(s) retrieved.

 

DBDATE 环境变量为日期转换提供了另一种可能的变化。对于 US English 地区,它的缺省值为“MDY4/”。这意味着日期字符串的各部分之间以“/”隔开,它们之间的顺序是月、日和年。注意,预期的年是 4 位数,但是也可以根据 DBCENTURY 设置的规则进行补足。可以修改 DBDATE 的值,以使用国际日期格式。则 DBDATE 值将是“Y4MD-”。除了对字符串类型的输入日期有影响外,这个值还影响日期到字符串的转换:

select order_date from orders WHERE order_num = 1001;
order_date
1998-05-20
1 row(s) retrieved.

 

要更详细地显示日期,可以使用 TO_CHAR 函数,并像上一节中描述的那样提供一个格式:

select to_char(order_date, "%d %B %Y") from orders WHERE order_num = 1001;
(expression)  20 May 1998
1 row(s) retrieved.

 

可以使用一些已有的函数来提取诸如月、日之类的值,并在用表达式定义表分段时使用那些值。也可以在 SQL 语句中将它们用于分组。例如,如果您想了解每月有多少订单,那么可以使用以下语句:

SELECT YEAR(order_date) year, MONTH(order_date) month, COUNT(*) count
FROM orders
GROUP BY 1, 2
ORDER BY 1, 2;

 

这种类型的分组在各种报告中都很有用。如果愿意利用 IDS 的一些基本的可扩展特性,还可以做更多的事情。

 



回页首

IDS 可扩展性

IDS 是最早扩展数据库的功能以适合用户环境的数据库。从 1997 年 IDS version 9.01 开始,可扩展性就可用了,而在 IDS version 10 中继续提供了可扩展性,并作了改进。可以创建新的数据类型、新的函数,甚至新的聚集。函数和聚集可以用 C、Java 或 SPL 编写。如果您想了解更多关于可扩展性的用途的信息,请参阅本文后面提供的参考资料。

我通常用 C 编写用户定义函数。但是对于本文,我用 SPL 编写用户定义函数。SPL 的优点在于它是 IDS 用户熟知的语言。在编写存储过程时同样也使用了这种语言。

 



回页首

函数索引

IDS V9.x 和更高版本支持函数索引(functional index)的概念。这意味着可以在函数的结果上创建索引。然后,可以使用索引来加快对 SQL 语句中包含函数的查询的处理。

内建函数是在 IDS 添加可扩展特性之前创建的。不能直接在内建函数的结果上创建索引。不过,可以将内建函数包装在一个 SPL 函数里面。例如,如果想在月上创建一个索引,那么可以创建一个 SPL 函数,如下所示:

CREATE FUNCTION udr_month(dt date)
RETURNING integer
WITH (NOT VARIANT)
RETURN MONTH(dt);
END FUNCTION;

 

有了这个包装器函数,便可以创建一个索引:

CREATE INDEX orders_month_ids ON orders(udr_month(order_date));

 

然后,可以使用利用了上述索引的 SQL 语句,比如:

SELECT * FROM orders WHERE udr_month(order_date) = 6;



回页首

新的日期函数

可以从日期中提取出更多信息:年中周、月中周和季度。

首先来看年中日函数。有了这样的函数,就可以按周报告活动,而不必为每个报告编写特定的存储过程,或编写定制的应用程序代码。可以使用 IDS 中内建的函数来构造这个函数。这样,这个函数看上去就非常简单了:

CREATE FUNCTION day_of_year(dt date)
RETURNS integer
WITH(NOT VARIANT)
RETURN(1 + dt - MDY(1, 1, YEAR(dt)) );
END FUNCTION;

 

实现该函数的关键之处在于一个日期实际上就是一个整数,这个整数表示从 1899 年 12 月 31 日以来的天数。这意味着如果得到 1 月 1 日的日期,那么就只需做一个简单的减法。

可以将这个函数用在 EXECUTE FUNCTION 语句中,或者用于在一个函数或存储过程中设置一个值,或者用在 SQL 语句中。

SELECT order_date, day_of_year(order_date) d_o_y FROM orders WHERE order_num = 1001;
order_date       d_o_y
05/20/1998         140
1 row(s) retrieved.

 

年中周函数要稍微复杂一些。它采用类似的计算,不过需要除以每周 7 天:

CREATE FUNCTION week_of_year(dt date)
RETURNS integer
WITH(NOT VARIANT)
DEFINE day1 date;
DEFINE nbdays int;
LET day1 = MDY(1, 1, YEAR(dt));
LET nbdays = dt - day1;
RETURN 1 + (nbdays + WEEKDAY(day1)) / 7;
END FUNCTION;

 

该函数的关键在于理解 WEEKDAY 内建函数提供的偏移量。WEEKDAY 函数为星期天返回 0,一直到为星期六返回 6。如果 1 月 1 日是星期天,那么我们知道 1 月 8 日是第二周的星期天。如果 1 月 1 日从另一个周中日开始,则意味着第一周更短一些。WEEKDAY 内建函数为我们提供了偏移量,以便计算出一个日期属于一年中的第几周。

week_of_year() 函数在一年中的最后一个星期和下一年的第一个星期上存在问题。例如,2004 年 12 月 31 日是星期五,2005 年 1 月 1 日是星期六。而 week_of_year 函数会提供以下结果:

EXECUTE FUNCTION week_of_year(date("12/31/2004") );
(expression)
          53
1 row(s) retrieved.
EXECUTE FUNCTION week_of_year(date("1/1/2005"));
(expression)
           1
1 row(s) retrieved.

 

这种行为正确吗?这要由您来决定。如果不正确,那么需要根据您的需求修改代码,以便解决这个问题。为此只需在 SPL 函数中添加几行代码。

如果想计算日期在一个月中属于第几周,那么可以使用相同的函数,不过不是以 1 月 1 日作为起始日期,而是以参数指定的日期当月的第 1 天作为起始日期:

CREATE FUNCTION week_of_month(dt date)
RETURNS integer
WITH(NOT VARIANT)
DEFINE day1 date;
DEFINE nbdays int;
LET day1 = MDY(MONTH(dt), 1, YEAR(dt));
LET nbdays = dt - day1;
RETURN 1 + (nbdays + WEEKDAY(day1)) / 7;
END FUNCTION;



回页首

quarter() 函数

有些数据库产品提供了 quarter() 函数。它通常返回一个 1 到 4 之间的数字。提供 quarter() 函数的问题是,它假设一个特定的日历:标准历年。

很多公司需要根据他们自己的业务年来计算季度,而业务年往往与标准历年不一致。甚至有些组织必须根据需要做的事情来计算季度。例如,一些学校必须计算日历季度、学年季度和业务季度。

我们首先来看历年季度的一个简单的实现:

CREATE FUNCTION quarter(dt date)
RETURNS integer
WITH(NOT VARIANT)
RETURN (YEAR(dt) * 100) + 1 + (MONTH(dt) - 1) / 3;
END FUNCTION;

 

在这个实现中,我将年也放在季度的表示中。例如,2005 年第 3 季度被表示为 200503。当 SQL 语句跨越多个年度时,这样做可以简化处理。也可以创建不同的实现,例如返回一个字符串,而不是返回整数。您必须根据自己的需求做决定。

如前所述,可能需要根据不是 1 月 1 日的起始日期来计算季度。在历年不同于季度年的情况下,这增加了一定的复杂性。例如,假设一家公司从 9 月 1 日开始它的会计年度。这意味着 2005 年 9 月 1 日实际上是 2006 年第一季度的开始,12 月 1 日是第二季度的开始,依此类推。

下面的代码展示了从 9 月 1 日开始新一年的实现。该代码很容易根据不同的起始日期进行调整:

CREATE FUNCTION bizquarter(dt date)
RETURNS integer
WITH(NOT VARIANT)
DEFINE yr int;
DEFINE mm int;
LET yr = YEAR(dt);
LET mm = MONTH(dt) + 4; -- sept. to jan. is 4 months
IF mm > 12 THEN
  LET yr = yr + 1;
  LET mm = mm - 12;
END IF
RETURN (yr * 100) + 1 + (mm - 1) / 3;
END FUNCTION;

 

与 quarter() 函数相比,该函数中增加的处理是将当前月向前移动了几个月,以便计算与业务年匹配的季度。

 



回页首

使用新函数

有了这些函数之后,可以将它们用在 SQL 语句中,就像它们是在 IDS 中内建的函数一样。例如:

SELECT quarter(order_date) quarter, count(*) count
FROM orders
GROUP BY 1
ORDER BY 1;
    quarter            count
     199802               16
     199803                7
2 row(s) retrieved.

 

而且可以在这些函数上创建索引:

CREATE INDEX orders_week_ids
ON orders(week_of_year(order_date));

 

这样便可以灵活地让数据库返回您想要的信息。IDS 可扩展性在很多其他领域也很有用。请参阅“参考资料”小节,以获得其他关于该主题的文章。

 



回页首

结束语

我们很容易扩展 IDS 的功能,以便提供更好的数据操纵。其结果是要编写的代码变少了,要执行的 SQL 语句也可能变少了,从而得到更好的性能。数据库不是商品。它是为您提供业务优势的战略性工具。

通过使用本文中的日期操纵技术,可以调整 IDS,使之适合您的环境。如果这里提供的日期函数不完全适合您的环境,那么可以很容易地修改这些函数。IDS 提供的灵活性意味着在应用程序的设计阶段就应该考虑 IDS 的功能。这样便可以得到更好的性能和可伸缩性,以及更简单的实现。

 

抱歉!评论已关闭.