日期时间字段
在 SQL 中,日期和时间字段在所有操作中都会被转换为 SmartEDB 的 datetime 类型。唯一的区别在于它们的输出格式。例如,日期字段 day 可以加上 5 天,结果仍为日期类型。
日期、时间和日期时间类型在内部以整数值表示。执行算术运算时,结果将为原始类型,但使用底层值进行算术运算。因此,日期时间的精度可能很重要。xSQL 的默认精度为 1,即 1 秒。但可以通过 setDateTimePrecision() 函数更改。无论使用日期类型还是日期时间类型,在操作中都会使用秒(或毫秒等)。
例如,考虑以下操作:
XSQL>select cast('02-02-2013' as date);
#1
------------------------------------------------------------------------------
02/02/2013 00:00:00
Selected records: 1
XSQL>select cast('02-02-2013' as time);
#1
------------------------------------------------------------------------------
02/02/2013 00:00:00
Selected records: 1
XSQL>select cast('02-02-2013' as date) + 5;
#1
------------------------------------------------------------------------------
02/02/2013 00:00:05
Selected records: 1
XSQL>select setDateTimePrecision(1000);
#1
------------------------------------------------------------------------------
1
Selected records: 1
XSQL>select cast('02-02-2013' as date) + 5;
#1
------------------------------------------------------------------------------
02/02/2013 00:00:00.005
Selected records: 1
插入日期和时间数据
可以使用多种字符串格式指定日期和时间,从而将数据插入日期、时间和日期时间字段中。(目前在 Windows 系统上,有效的日期值必须指定年份大于 1969 年。(请注意,在 Unix-Linux 平台以及 Windows 的下一个版本中,将内部使用 strptime() 函数,该函数没有此限制。)
例如,考虑以下表:
XSQL>create table dt(d date, t time);
日期和时间值可以使用格式为“M/D/Y h:m:s”
的字符串插入。如果 M 的值大于 1900,则该字符串被视为“Y/M/D h:m:s”
。例如:
XSQL>insert into dt values( '7/10/1970 15:15:15', '23:23:23');
XSQL>insert into dt values( '1970/7/30', '0');
XSQL>select * from dt;
d t
---------------------------------------------------------------
07/10/1970 15:15:15 23:23:23
07/30/1970 00:00:00 00:00:00
插入字符串也可以使用连字符或点分隔符,而非斜杠,来表示日期。例如:
XSQL>insert into dt(d) values( '6-10-1970');
XSQL>insert into dt(d) values( '1970.7.30');
XSQL>select * from dt;
d t
---------------------------------------------------------------
06/10/1970 00:00:00 00:00:00
07/30/1970 00:00:00 00:00:00
另一种选择是使用月份的缩写(前三个字母)并用连字符分隔;即格式为“日-月-年 时:分:秒”的字符串。例如:
XSQL>insert into dt(d) values( '30-sep-1970');
XSQL>select * from dt;
d t
---------------------------------------------------------------
09/30/1970 00:00:00 null
请注意,月份整数值 M 必须是从 1 开始的月份编号,即 1 代表 1 月,年份值 Y 必须是大于 1900 的 4 位整数;否则,如果 Y 的值小于 50,则视为 2000 年以后的年份,如果大于或等于 50,则视为 1900 年以后的年份。例如:
XSQL>insert into dt(d) values( '30-aug-20');
XSQL>insert into dt(d) values( '30-aug-80');
XSQL>select * from dt;
d t
---------------------------------------------------------------
08/30/2020 00:00:00 null
08/30/1980 00:00:00 null
小时 h 和分钟 m 的值必须分别为小于 24 和 60 的整数,秒 s 可以表示为整数或浮点数,但浮点数的小数部分会被截断。例如:
XSQL>insert into dt(t) values( '23:59:19.6');
XSQL>select * from dt;
d t
---------------------------------------------------------------
null 23:59:19
如果未提供时间部分,则该字符串被视为日期,时间部分将被设为零。如果该字符串无法成功解析为日期,则会尝试使用时间格式“h:m:s”
。
存储和处理高精度日期时间数据
例如,在使用高精度数据时,比如具有毫秒粒度的时间戳字段,建议使用 bigint 数据类型。也可以使用 char(9) 类型的字段来存储时间戳,例如
create table DailyQuotesIN (tTime char(9), ...)
SmartESQL 运行时会隐式地将 char(9) 类型转换为整数类型——无需对输入数据进行显式转换。不过,从性能角度来看,使用 bigint 数据类型更佳:
create table DailyQuotesIN (tTime bigint, ...)
使用 bigint 数据类型,您只需将 tTime 数据以整数值的形式加载,例如 093515984593000,并直接对这些数据进行任何操作,包括各种形式的输出。但请注意,若要从表示时间的字段中提取某些部分(例如小时或分钟),则不应使用标准的 SQL 函数;即不要使用 SQL 函数 hour() 或 minute()。而应使用整数除法。例如:
select tTime / 10000000000 as hour,
tTime / 100000000 % 100 as minute,
real(tTime % 10000000000) / 100000000 as second
from DailyQuotesIN;
将日期转换为整数
在使用 Date 类型的变量时,将 Date 值转换为表示自 1970 年 1 月 1 日 00:00:00 GMT 以来的毫秒数的长整型值可能会很有用。这可以通过使用 Date 类的 getTime() 方法来实现。以下代码片段演示了如何使用此方法(在 Java 中):
{
Calendar cal = Calendar.getInstance();
cal.set(2014, 1, 1, 9, 30);
cal.set(2014, 12, 30, 16, 00);
Date dstart = cal.getTime();
Date dfinish = cal.getTime();
long start = dstart.getTime();
long finish = dfinish.getTime();
System.out.println("Calculating high bids over symbols from time " + start + " to " + finish + ":");
SqlResultSet res = conn.executeQuery("SELECT Symbol, seq_ignore(seq_search(qTime, ?, ?)) as t,
seq_max (Bid@t) FROM Quotes", start, finish);
实现细节
SmartEDB 的日期时间类型以 8 字节整数存储,支持加减运算。该类型由应用程序解释,例如可以表示自设备启动以来的 CPU 时钟周期或自纪元以来的时间。高级组件(如 Java、Python 包装器 API、SQL API 和 xSQL 服务器)假定日期时间类型为 UTC 时间戳,即 UNIX 纪元时间。
纪元是用于测量时间的参考点。1970 年 1 月 1 日 00:00:00 UTC 被称为 Unix 纪元时间,许多系统和编程语言使用它作为参考点。Unix 纪元 0 是 1970 年 1 月 1 日 00:00:00 UTC,之前的时间用负数表示。
为了更精确地表示时间,SmartEDB 引入了“日期时间精度”属性,表示每秒的滴答数,默认值为 1(秒)。应用程序可以将分辨率设置为 1 到 1000000000(纳秒)。例如,“2023-07-04 12:00:00 GMT”的纪元时间为 1688472000。如果 dt_precision 设置为 1000(毫秒),则表示为 1688472000000。
SQL INSERT 操作符解析日期字符串为 C 语言时间结构,包含九个整型成员,表示分解后的日历日期和时间。
- tm_sec: 整型,表示分钟后的秒数
- tm_min: 整型,表示小时后的分钟数
- tm_hour: 整型,表示午夜后的小时数
- tm_mday: 整型,表示月份中的天数
- tm_mon: 整型,表示一月后的月份数(从0开始计数)
- tm_year: 整型,表示自1900年以来的年数
- tm_wday: 整型,表示周日后的天数(从0开始计数,0代表周日)
- tm_yday: 整型,表示自1月1日以来的天数(从0开始计数)
- tm_isdst: 整型,表示是否为夏令时(-1表示未知,0表示不是,1表示是)
SmartEDB 将 tm_isdst 设为 -1,表示夏令时信息不可用。tm 结构传递给 C 运行时的 mktime() 函数,将时间转换为当前时区的 UNIX 纪元时间(所有时区定义为 UTC 偏移量),然后乘以“日期时间精度”。
相反,SQL 引擎将存储的日期时间值重新转换为字符串时,先除以设置的精度,得到秒级纪元时间,再通过 localtime() 函数根据当前时区填充 tm 结构体,最后以格式化字符串形式表示。
尽管大多数系统使用 Unix 时间,但存在局限性。例如,在 QNX 7.0 等嵌入式操作系统中,mktime() 不支持 1970 年 1 月 1 日之前的日期(即负纪元时间)。为此,SmartEDB 使用时间偏移技术,将 1970 年前的日期上移至预定义年数,通过 mktime() 转换后再下移,确保数据库中 1970 年后的日期保存为 epoch_time * datetime_precision,1970 年前的日期保存为负数。读取时反向处理。注意,由于不同历年夏令时规则不同,这些值可能与纪元时间相差一小时。
某些应用程序需要将日期时间解释为秒/毫秒/纳秒间隔而非绝对日期。此时,8 字节值会格式化为小时:分钟:秒。为了适应这种需求,可以定义一个从 1970 年 1 月 1 日开始的时间间隔,在此范围内存储的日期时间将被视为时间间隔。默认情况下,间隔值为零(所有日期视为日历日期)。使用 SQL 函数 setMaxInterval(n_sec) 或 xSQL 命令 maxinterval n_sec 可调整间隔长度。例如,maxinterval 86400 表示将 1970 年 1 月 1 日前后 24 小时内的日期解释为时间间隔(允许负值,有助于时间值的算术运算)。