存储DateTime的首选方式


18

我们可以通过两种方式存储日期和时间信息。存储DateTime信息的最佳方法是什么?

使用DateTime将日期和时间存储在2个单独的列中还是1个列中

您能解释一下为什么这种方法更好吗?

(链接到MySQL文档以供参考,该问题是一般性的,不特定于MySQL)
日期和时间类型:日期和时间


3
这在很大程度上取决于您所使用的数据库系统。物有所值:Oracle选择将其作为一列(作为DATETIME数据类型)来进行,在这种情况下,使用其内置的支持肯定会比将这些信息作为NUMBER数据类型存储在2列中更好(即使您仅给定查询需要1个部分(日期或时间)。
克里斯·约翰斯顿

5
对于SQL Server,可以优先选择拆分的一种情况是按日期分组。即使集合索引提供了所需的顺序 ,也可以在没有排序的情况下使用流聚合来启用复合索引date,timegroup by date但不能使用索引进行排序。datetimegroup by cast(datetime as date)
马丁·史密斯,

1
请注意,任何有关“时间”值的数学运算都需要知道日期和时区-例如,两次之间的距离取决于当天是否包含DST事件,某些日子有23或25小时以及还存在leap秒。
彼得斯(Peteris)2016年

Answers:


23

最好将数据存储在单列中,因为它们是密不可分的。时间点是一条信息,而不是两条。

许多产品在幕后使用的一种存储日期/时间数据的常用方法是将其转换为十进制值,其中“日期”是十进制值的整数部分,而“时间”是小数部分值。因此,将1900-01-01 00:00:00存储为0.0,并将2016年9月20日9:34:00存储为42631.39861。42631是自1900-01-01以来的天数。.39861是从午夜起经过的时间的一部分。不要直接使用小数类型来执行此操作,而应使用显式的日期/时间类型;我的观点只是一个例证。

将数据存储在两个单独的列中意味着您想在任何时候查看给定的时间点早于或晚于存储的值,就需要合并两个列的值。

如果单独存储这些值,则总是会遇到难以检测的“错误”。例如以下内容:

IF OBJECT_ID('tempdb..#DT') IS NOT NULL
DROP TABLE #DT;
CREATE TABLE #DT
(
    dt_value DATETIME NOT NULL
    , d_value DATE NOT NULL
    , t_value TIME(0) NOT NULL
);


DECLARE @d DATETIME = '2016-09-20 09:34:00';

INSERT INTO #DT (dt_value, d_value, t_value)
SELECT @d, CONVERT(DATE, @d), CONVERT(TIME(0), @d);

SET @d = '2016-09-20 11:34:00';

INSERT INTO #DT (dt_value, d_value, t_value)
SELECT @d, CONVERT(DATE, @d), CONVERT(TIME(0), @d);

/* show all rows with a date after 2016-07-01 11:00 am */
SELECT *
FROM #DT dt
WHERE dt.dt_value >= '2016-07-01 11:00:00';

/* show all rows with a date after 2016-07-01 11:00 am */
SELECT *
FROM #DT dt
WHERE dt.d_value >= CONVERT(DATE, '2016-07-01')
    AND dt.t_value >= CONVERT(TIME(0), '11:00:00');

在上面的代码中,我们正在创建一个测试表,用两个值填充它,然后针对该数据执行简单的查询。第一个SELECT返回两行,但是第二个SELECT仅返回单行,这可能不是期望的结果:

在此处输入图片说明

@ypercube在注释中指出,过滤值在离散列中的日期/时间范围的正确方法是:

WHERE dt.d_value > CONVERT(DATE, '2016-07-01') /* note there is no time component here */
    OR (
        dt.d_value = CONVERT(DATE, '2016-07-01') 
        AND dt.t_value >= CONVERT(TIME(0), '11:00:00')
    )

如果出于分析目的需要将时间成分分开,则可以考虑为该值的时间部分添加一个已计算的,持久的列:

ALTER TABLE #DT
ADD dt_value_time AS CONVERT(TIME(0), dt_value) PERSISTED;

SELECT *
FROM #dt;

在此处输入图片说明

然后可以按一天中的时间对持久化的列进行索引,以进行快速排序等。

如果出于显示目的考虑将日期和时间分为两个字段,则应意识到格式化应在客户端而不是服务器上进行。


11

我将对其他答案提出不同意见。

如果同时需要日期和时间两个部分,即如果一个条目包含一个但不包含另一个,则该条目无效(或者一个条目为NULL,而另一个不包含NULL),则将其存储在单个列中是有道理的,因为其他给出的原因答案。

但是,一个或两个组件可能是单独可选的。在这种情况下,将其存储在单个列中将是不正确的。这样做将迫使您以任意方式表示NULL值,例如将时间存储为00:00:00。

以下是几个示例:

  • 您正在记录车辆旅程以扣除里程税。知道行程的确切时间会很有用,但是如果员工没有记下来并且忘记了,则日期仍应自己记录(必需日期,可选时间)。

  • 您正在进行一项调查,以了解人们什么时候吃午餐,并要求参与者填写午餐时间(包括日期)样本的表格。有些人不愿意填写日期,并且您不想丢弃数据,因为这是您真正关心的时间(可选日期,所需时间)。

有关替代方法,请参见此相关问题


RFC 3339中,有一个记录“未知本地偏移量”的约定。我认为它还不能完全涵盖“未知时间”的用例,但是已经很接近了。下一节“当地时间不合格”甚至更近了,但还不够。
日内瓦

是的,由于这个原因,我正在盯着重构我的架构。考虑一下租车的情况。要从出租公司接车-公司需要开放;因此您可以指定取件的日期和时间。但是,许多都有钥匙箱。所以你下班后下车。因此,如果营业地点在周日关闭;有一个下车日期;但不是时间。存储0值(例如,上午12点)将不起作用,因为某些位置一直营业到午夜,这在其他情况下是有效值。
里斯

5

除非有特定的业务/应用程序需求,否则我总是喜欢将其存储为单个列。以下是我的观点-

  • 从时间戳中提取时间不是问题
  • 如果我们可以将两者都存储在一起,为什么还要花时间添加额外的列
  • 为了避免每次查询时都添加日期和时间。

1
@a_horse_with_no_name在这里有一个要点。我认为“从datetimestamp提取时间戳不是问题”应该改写为从timestamp提取时间戳不是问题”。“时间戳记”通常表示日期和时间(通常是时区)。
ypercubeᵀᴹ

是的,同意@ypercubeᵀᴹ。时间戳通常表示日期和时间。我明确提到了DateTimeStamp一词,因此任何人都可以理解我们在谈论日期和时间。但是你也是正确的。修改了答案。
Ashwini Mohan

3

在SQL Server中,最好将DataTime存储为一个字段。如果在DataTime列上创建索引,则可以将其用作日期搜索和日期时间搜索。因此,如果需要限制在特定日期存在的所有记录,则仍可以使用索引而不必执行任何特殊操作。如果您需要查询时间部分,则将无法使用相同的索引,因此,如果您有一个业务案例,其中您比日期时间更关心一天中的时间,则应将其分开存储,因为您需要创建它索引并提高性能。


1

确实,可惜没有标准的跨DBMS类型(例如INT和VARCHAR用于整数和字符串值)。到目前为止,我已经遇到的2种跨数据库方法是使用VARCHAR / CHAR列将DataTime值存储为根据ISO 8601(更方便,更易读)标准格式化的字符串,并使用BIGINT将它们存储为POSIX时间戳(存储更多)高效,更快,更易于数学操作)。


2
是的,确实存在:timestamp这就是SQL标准定义的内容。将时间戳记存储为字符串是一个非常糟糕的建议
a_horse_with_no_name

0

阅读了大量内容后,BIGINT中的UTC Unix时间似乎是最佳的解决方案。VARCHAR中的TZDB时间戳ID,用于存储时区(如果需要)。一些争论:

  1. TIMESTAMP和DATETIME在后台执行了大量的花哨的转换,这些转换似乎很复杂且不清楚。服务器有时从本地时间切换到UTC,或者从服务器时间切换回时间。每个函数的一堆隐藏开销。

  2. BIGINT(8kb)至少比xxxxxx.xxxxxx格式存储所需的DECIMAL轻或浅,MySQL实际将存储为两个INT +。并且足以存储未来的几个世纪。

  3. 几乎所有主要的编程语言都具有标准函数库,可用于Unix时间。

  4. 使用BIGINT进行数学运算应该比在任何硬件上的其他任何事物都快或快。

当然,以上所有内容都与大型国际项目有关。对于较小的东西,使用所选框架的默认格式似乎已经足够了。


2
在后台执行了许多似乎很...不清楚的头转换 ”-您在谈论哪个DBMS?对于一timestamp列,(在数据库层)没有“花哨的转换”发生,并且timestamp with time zone对此进行了充分的文档记录和解释(至少对于Oracle和Postgres而言)
a_horse_with_no_name

1
“几乎所有主要的编程语言都有标准函数库,可以与Unix时间一起使用。” 可是你把所有关于日期,日期时间和时间戳的库和函数SQL / DBMS有,您选择使用BIGINT的...
ypercubeᵀᴹ
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.