Как следует отделять таблицы измерений от таблиц фактов, если вы не строите хранилище данных? - PullRequest
4 голосов
/ 16 августа 2011

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

Я создаю приложение для учета сотрудников.

База данных будет содержать организационную информацию. Информация в основном определяется в трех таблицах: местоположения, отделы и отделы. Однако есть и другие с похожими проблемами. Во-первых, мне нужно сохранить доступные значения для этих таблиц. Это позволит использовать доступные значения в приложении при управлении сотрудником и управлять этими значениями при добавлении / удалении отделов и т.п. Например, таблица Locations может выглядеть так:

LocationId | LocationName | LocationStatus
1 | New York | Active
2 | Denver | Inactive
3 | New Orleans | Active

Затем мне нужно сохранить эти значения для каждого сотрудника и сохранить его историю. Моей первой мыслью было создание таблиц LocationHistory, DivisionHistory и DepartmentHistory. Я не могу точно определить, почему, но это показалось мне плохим дизайном. Следующим моим намерением было создать набор таблиц DimLocation / FactLocation, DimDivision / FactDivision, DimDepartment / FactDepartment. Я не верю, что это имеет смысл. Я также рассмотрел именование их как комбинация Employee, то есть EmployeeLocations, EmployeeDivisions и т. Д. Независимо от соглашения об именовании для этих таблиц, я думаю, что данные будут выглядеть аналогично упрощенной версии, которую я имею ниже:

EmployeeId | LocationId | EffectiveDate | EndDate
1 | 3 | 2008-07-01 | NULL
1 | 2 | 2007-04-01 | 2008-06-30

Я понимаю, что любое из воображаемых решений, которые я описал выше, могло бы работать, но я действительно стремлюсь создать дизайн, который другим будет легко поддерживать с интуитивно понятной, знакомой структурой. Я хотел бы получить помощь этого сообщества, мнения и опыт в этом вопросе. Я открыт и приветствую любые предложения для рассмотрения. Например, я должен даже хранить доступные значения для этих трех таблиц в базе данных? Должны ли они поддерживаться на уровне кода приложения / бизнес-логики? Нужно ли мне просто увидеть слово «История», повторяющееся три раза?

Спасибо!

Ответы [ 2 ]

3 голосов
/ 16 августа 2011

Во-первых, я не вижу проблем в описании их как таблиц измерений и фактов за пределами склада:)

С точки зрения концептуализации и понимания отношений, я лично считаю, что использование дат начала / окончания очень легко для понимания людьми. Разрешение таблиц фактов агента и местоположения, а затем таблиц отображения, зависящих от времени, таких как Agent_At_Location и т. Д. Однако у них есть проблемы, достойные внимания.

  1. Если EndDate равен 2008-08-30, был ли сотрудник в этом месте ДО 30 августа или ДО и ВКЛЮЧЕНО 30 августа.

  2. Работа с перекрывающимися периодами дат в запросах может привести к запутанным запросам, но, что более важно, к медленным запросам.


Первый кажется просто условным, но он может иметь определенные последствия при работе с другими данными. Например, предположим, что конечная дата 2008-08-30 означает, что они находятся в этом месте ДО и ВКЛЮЧЕНО 30 августа. Затем вы присоединяетесь к их ежедневным агентским данным за этот день (например, когда они действительно прибыли на работу, уехали на перерывы и т. Д.). Вам необходимо присоединиться к ON AgentDailyData.EventTimeStamp < '2008-08-30' + 1, чтобы включить все события, которые произошли в этот день.

Это потому, что EventTimeStamp данных измеряется не в днях, а, вероятно, в минутах или секундах.

Если вы считаете, что Конечная дата '2008-08-30' означает, что Агент находился в этом местоположении ДО, но НЕ ВКЛЮЧИЛ 30 августа, объединению не требуется + 1. На самом деле вам не нужно знать, связана ли дата с ДНЕМ, или может включать компонент времени или нет. Вам просто нужно TimeStamp < EndDate.

С помощью ЭКСКЛЮЗИВНЫХ маркеров конца все ваши запросы упрощаются и никогда не требуют + 1 day или + 1 hour для обработки краевых условий.


Второй гораздо сложнее решить. Простейший способ разрешения перекрывающегося периода заключается в следующем:

SELECT
  CASE WHEN TableA.InclusiveFrom > TableB.InclusiveFrom THEN TableA.InclusiveFrom ELSE TableB.InclusiveFrom END AS [NetInclusiveFrom],
  CASE WHEN TableA.ExclusiveFrom < TableB.ExclusiveFrom THEN TableA.ExclusiveFrom ELSE TableB.ExclusiveFrom END AS [NetExclusiveFrom],
FROM
  TableA
INNER JOIN
  TableB
    ON  TableA.InclusiveFrom < TableB.ExclusiveFrom
    AND TableA.ExclusiveFrom > TableB.InclusiveFrom

-- Where InclusiveFrom is the StartDate
-- And   ExclusiveFrom is the EndDate, up to but NOT including that date

Проблема с этим запросом заключается в индексации. Первое условие TableA.InclusiveFrom < TableB.ExclusiveFrom может быть разрешено с помощью индекса. Но это может дать Массивный диапазон дат. И затем, для каждой из этих записей, ExclusiveDate могут быть примерно чем угодно, и, конечно, не в том порядке, который мог бы помочь быстро разрешить TableA.ExclusiveFrom > TableB.InclusiveFrom

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

    ON  TableA.InclusiveFrom <  TableB.ExclusiveFrom
    AND TableA.InclusiveFrom >= TableB.InclusiveFrom - 30
    AND TableA.ExclusiveFrom >  TableB.InclusiveFrom

Условие TableA.ExclusiveFrom > TableB.InclusiveFrom STILL не может использовать индексы. Но вместо этого мы ограничили количество строк, которые могут быть возвращены с помощью поиска TableA.InclusiveFrom. Данные не более чем за 30 дней, потому что мы знаем, что мы ограничили продолжительность максимум 30 днями.

Примером этого является разбивка ассоциаций по календарному месяцу (максимальная продолжительность 31 день).

EmployeeId | LocationId | EffectiveDate | EndDate
    1      |     2      |  2007-04-01   | 2008-05-01
    1      |     2      |  2007-05-01   | 2008-06-01
    1      |     2      |  2007-06-01   | 2008-06-25

(Representing Employee 1 being in Location 2 from 1st April to (but not including) 25th June.)

Это фактически компромисс; использование дискового пространства для увеличения производительности.

Я даже видел, как это приводит к тому, что на самом деле не хранятся диапазоны дат, а хранится фактическое отображение для каждого дня. По сути, это похоже на ограничение максимальной продолжительности до 1 дня ...

EmployeeId | LocationId | EffectiveDate
    1      |     2      |  2007-06-23  
    1      |     2      |  2007-06-24  
    1      |     3      |  2007-06-25  
    1      |     3      |  2007-06-26  

Инстинктивно я изначально восстал против этого. Но в последующих ETL, Warehousing, Reporting и т. Д. Я действительно нашел его очень мощным, адаптируемым и обслуживаемым. Я на самом деле видел, как люди делают меньше ошибок в коде, пишут код за меньшее время, код в конечном итоге работает быстрее и гораздо лучше адаптируется к меняющимся потребностям клиентов.

Только две нижние стороны были:
1. Занято больше дискового пространства (но по сравнению с таблицами фактов меньше)
2. Вставки и обновления для этого сопоставления были медленнее

Фактическое замедление для вставок и обновлений имеет значение только один раз, когда эта модель использовалась для представления постоянно меняющейся сети процессов; где приложение хотело изменить отображение примерно 30 раз в секунду. Даже тогда это сработало, но затрачивало больше процессорного времени, чем было идеально.

1 голос
/ 16 августа 2011

Если вы хотите быть эффективными и вести историю, делайте эти вещи.Есть несколько решений этой проблемы, но я продолжаю возвращаться к этому:

  1. Помните, что каждая строка представляет отдельную сущность, если вы исправляете эту сущность, это нормально, но не используйте повторно и ID для нового местоположения.Настройте его таким образом, чтобы вместо удаления местоположения вы помечали его как удаленное с небольшим количеством и скрывали его от интерфейса, чтобы при историческом обращении оно все еще было.

  2. Создатьтаблица истории, которая включает текущее значение или нет записей, если значение не установлено в данный момент.Пусть внешний ключ привязывается к сотруднику и привязывается к местоположению.

  3. Создайте столбец в таблице сотрудников, который указывает на текущее активное местоположение в истории.Когда вам нужно узнать местоположение сотрудников, вы присоединяетесь к таблице истории на основе этого идентификатора.Когда вам нужно получить всю историю сотрудника, к которому вы присоединяетесь, из таблицы истории.

  4. Эта структура сохраняет все нормализованными и дает вам простой способ найти текущее значение безнеобходимость сравнивать даты.

  5. Что касается использования истории слов, подумайте о ней в разных терминах: поскольку она содержит как текущий элемент, так и исторические элементы, на самом деле это простосоединительный стол, который хранит старый предмет.Таким образом, вы можете назвать его как EmployeeLocations.

...