Хранение и извлечение исторических данных с использованием SQL / реляционной базы данных - PullRequest
1 голос
/ 22 июня 2011

Учитывая эту таблицу:

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_IT =: Given_dept AND START_DATE <=: данное_дата) при применении к отсортированному индексу (DEPT_ID, START_DATE, END_DATE, PERSON_ID) приводит к данным с несортированным END_DATE, поэтому (данный момент: <дата_ДАТЫ) требуется последовательное сканирование результата. </p>

Мое другое требование - ввести следующее ограничение: человек не может работать в двух отделах одновременно или дважды в одном и том же отделе. Это означает следующее:

-- 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 ]

4 голосов
/ 23 июня 2011

В качестве отправной точки я рекомендую книгу Рика Снодграсса «Разработка ориентированных на время приложений баз данных в SQL», доступную как , для бесплатной загрузки в формате PDF . Похоже, вы можете перейти прямо в главе 5 и прочитать главы 6 и 7 (но не отклоняйте альтернативные подходы в последующих главах).

Что касается реализации, в настоящее время postgreSQL в целом имеет хорошую временную поддержку и поддержку отложенных ограничений (что жизненно важно - в SQL! - для таких понятий, как последовательные ключи).

Обратите внимание, что существуют другие модели для временных баз данных, например Дата Дарвен Лоренцос .

1 голос
/ 23 июня 2011

Можно ли изменить структуру таблицы 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_DATE или END_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
1 голос
/ 23 июня 2011

Вы пытались добавить еще один индекс для DEPT_ID и END_DATE? Если вы используете MySQL 5+, он может выполнить слияние индекса и использовать как этот индекс, так и индекс DEPT_ID, START_DATE, PERSON_ID.

Что касается вашего второго вопроса, я думаю, что единственный способ применить этот тип ограничений - через логику приложения или триггер вставки / обновления.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...