使用SQL /关系数据库存储和检索历史数据

时间:2011-06-22 17:03:18

标签: mysql sql indexing constraints spatial

鉴于此表:

CREATE TABLE DeptPeopleHistory (
  DEPT_ID INTEGER,
  PERSON_ID INTEGER,
  START_DATE INTEGER,
  END_DATE INTEGER,
  UNIQUE(DEPT_ID, START_DATE, PERSON_ID), -- works as sorted index.
  UNIQUE(PERSON_ID, START_DATE),
  UNIQUE(PERSON_ID, END_DATE),
  CONSTRAINT (START_DATE < END_DATE)
);

我有两个需求。第一个是让所有在特定日期在特定部门工作的人。目前我使用这个(语义上正确的)查询:

SELECT PERSON_ID FROM DeptPeopleHistory
WHERE
  DEPT_IT = :given_dept AND
  START_DATE <= :given_date AND :given_date < END_DATE

这对于小型历史表或查询最近的数据来说速度很快,但对于大型历史表和旧数据来说速度很慢,因为优化器仅使用第一个索引,并且没有好的方法来处理END_DATE。我试图将END_DATE添加到第一个索引,但查询性能是相同的。我想这是因为当应用于排序索引(DEPT_ID,START_DATE,END_DATE,PERSON_ID)时,子过滤器(DEPT_IT =:given_dept AND START_DATE&lt; =:given_date)导致数据未排序END_DATE,因此(:given_date&lt; END_DATE )仍然需要对结果进行顺序扫描。

我的另一个需要是强制执行以下约束:一个人不能同时在两个部门工作,也不能在同一部门工作两次。这意味着以下内容:

-- This must work for previously empty data:
INSERT INTO DeptPeopleHistory(DEPT_ID, PERSON_ID, START_DATE, END_DATE)
                      VALUES (1,       1,         20100501,   20100520);

-- This should cause constraint violation because the person already
-- works at dept 1 on days from 20100517 to 20100519:
INSERT INTO DeptPeopleHistory(DEPT_ID,   PERSON_ID, START_DATE, END_DATE)
                      VALUES (:any_dept, 1,         20100517,   20100523);

指定此约束的另一种方法是,对于给定的PERSON_ID,START_DATE必须是最小值或等于另一条记录的END_DATE。

考虑到这两个需求,我们实际上需要一种有效的方法来处理非相交的范围。您是否知道通用SQL或某些特定数据库中的某些功能或构造可以处理这些需求?也许是一些“空间数据库”功能?

示例在MySQL中,但我需要适用于Oracle,SQL Server和FireBird的解决方案。解决方案无需在所有此类数据库中移植。

3 个答案:

答案 0 :(得分:4)

作为一个起点,我推荐Rick Snodgrass撰写的“在SQL中开发面向时间的数据库应用程序”一书,以a free PDF download的形式提供。看起来你可以跳到第5章并阅读第6章和第7章(但不要忽略后面章节中的替代方法)。

关于实现,postgreSQL目前通常具有良好的时间支持并支持可延迟约束(这在SQL中至关重要! - 对于序列密钥等概念而言)。

注意还有其他时态数据库模型,例如Date Darwen Lorentzos

答案 1 :(得分:1)

您是否尝试在DEPT_ID和END_DATE上添加其他索引?如果您使用的是MySQL 5+,它可以进行索引合并,并使用该索引和DEPT_ID,START_DATE,PERSON_ID索引。

至于你的第二个问题,我认为强制执行这种约束的唯一方法是通过应用程序逻辑或插入/更新触发器。

答案 2 :(得分:1)

是否可以将表格DeptPeopleHistory的结构更改为?:

CREATE TABLE DeptPeopleHistoryDetail (
  DEPT_ID INTEGER,
  PERSON_ID INTEGER,
  WORK_DATE INTEGER,               --- why is that INT and not DATE by the way?
  UNIQUE(WORK_DATE, PERSON_ID)
);

优点:

  • 您无需强制执行任何先前的UNIQUE约束,也不需要执行START_DATE < END_DATE约束。
  • 第二个复杂约束也被神奇地解决了。

缺点:

  • 上一个示例中的(1, 1, 20100501, 20100520)现在分为20行。我会说,这不是一个真正的问题。关系数据库旨在处理许多行。
  • 要查找部门中某人的START_DATEEND_DATE,必须运行查询。 (如果这太慢了,我怀疑,可以使用额外的表格)

哦,你的慢查询将写成:

SELECT PERSON_ID FROM DeptPeopleHistoryDetail
WHERE
  DEPT_IT = :given_dept AND
  WORK_DATE = :given_date 

使用您当前的DeptPeopleHistory设计,您可以尝试以下查询的效果吗?

SELECT H.PERSON_ID
FROM DeptPeopleHistory H
  JOIN
    ( SELECT PERSON_ID
           , MAX(START_DATE) AS LATEST_START_DATE
      FROM DeptPeopleHistory
      WHERE
        DEPT_IT = :given_dept AND
        START_DATE <= :given_date
      GROUP BY
        PERSON_ID
    ) AS grp
    ON  H.DEPT_IT = :given_dept
    AND grp.PERSON_ID = H.PERSON_ID
    AND grp.LATEST_START_DATE = H.START_DATE
WHERE 
   :given_date < H.END_DATE