MySQL первичные ключи с двумя полями ID

У меня есть таблица MySQL, peopleэто выглядит так:

id | object_id | name | sex   | published
----------------------------------------------
1 |     1     | fred | male  | [timestamp]
2 |     2     | john | male  | [timestamp]

Причина у меня две ids в том, что в моем приложении CRUD пользователь может редактировать существующий объект, в этом случае он становится черновиком, так что у меня есть две строки (черновая запись и уже существующая запись) с одинаковыми object_id, что-то вроде этого:

id | object_id | name | sex      | published
----------------------------------------------
2 |     2     | john | male     | [timestamp]
3 |     2     | john | female   | NULL

Это позволяет мне отслеживать черновики записей и статус публикации. Когда ряд с id из 3 опубликовано, его published поле будет помечено, а уже опубликованная строка удалена.

У каждого человека также есть история работы, поэтому у меня есть таблица history:

id | person_object_id | job
----------------------------------
1 |         2        | dev
2 |         2        | accountant

Это история работы Джона. Я имею в виду Джона object_id в person_object_id поле, потому что, если я сослался на его id Я бы рискнул разделить две таблицы, если бы удалил одну из строк Джона, как в моем примере выше.

Поэтому мой вопрос: не является ли неэффективным ссылаться на таблицу, как я делаю выше, используя не первичный ключ (object_id вместо id)? Как я могу ссылаться на первичный ключ, когда мне требуется неуникальный идентификатор для отслеживания черновиков / опубликованных строк?

2

Решение

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

Возьмите следующие данные о сотрудниках:

EmpNo FirstName LastName Birthdate HireDate Payrate DeptNo

Теперь перед вами стоит задача поддерживать версии данных по мере их изменения. Затем вы можете добавить поле даты, которое показывает, когда изменились данные:

EmpNo EffDate FirstName LastName Birthdate HireDate Payrate DeptNo

Поле «Дата вступления в силу» показывает дату вступления в силу каждой конкретной строки.

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

Одно очевидное решение состоит в том, чтобы сделать комбинацию EmpNo и нового поля EffDate первичным ключом.

Хорошо, это решает проблему PK, но теперь как насчет внешних ключей в других таблицах, которые относятся к конкретным сотрудникам? Можем ли мы добавить поле EffDate в эти таблицы?

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

Многие схемы были реализованы для решения этой проблемы (см. Запись в «Википедии»Медленно изменяющийся размер«для списка нескольких наиболее популярных).

Вот простое решение, которое позволяет вам создавать версии ваших данных и оставлять ссылки на внешние ключи в покое.

Во-первых, мы понимаем, что не все данные когда-либо изменятся и поэтому никогда не будут обновляться. В нашем примере кортежа это статические данные: EmpNo, FirstName, Birthdate, HireDate. Данные, которые могут измениться, — это LastName, Payrate, DeptNo.

Но это означает, что статические данные, такие как FirstName, зависят от EmpNo — исходного PK. Изменяемые или динамические данные, такие как LastName (которые могут измениться из-за брака или усыновления), зависят от EmpNo и EffDate. Наш кортеж уже не во второй нормальной форме!

Итак, мы нормализуем. Мы знаем, как это сделать, верно? С закрытыми глазами. Дело в том, что когда мы закончим, у нас будет основная таблица сущностей с одной и только одной строкой для каждого определения сущности. Все внешние ключи могут ссылаться на эту таблицу для одного конкретного сотрудника — так же, как и в случае нормализации по любой другой причине. Но теперь у нас также есть таблица версий со всеми данными, которые могут время от времени изменяться.

Теперь у нас есть два кортежа (по крайней мере, два — возможно, были выполнены другие процессы нормализации), чтобы представлять нашу сущность сотрудника.

EmpNo(PK) FirstName Birthdate  HireDate
=====     ========= ========== ==========
1001      Fred      1990-01-01 2010-01-01

EmpNo(PK) EffDate(PK)    LastName Payrate DeptNo
=====     ========       ======== ======= ======
1001      2010-01-01     Smith    15.00   Shipping
1001      2010-07-01     Smith    16.00   IT

Запрос на восстановление исходного кортежа со всеми версионными данными прост:

select  e.EmpNo, e.FirstName, v.LastName, e.Birthdate, e.Hiredate, v.Payrate, v.DeptNo
from    Employees e
join    Emp_Versions v
on  v.EmpNo = e.EmpNo;

Запрос на восстановление исходного кортежа только с самыми последними данными не очень сложен:

select  e.EmpNo, e.FirstName, v.LastName, e.Birthdate, e.Hiredate, v.Payrate, v.DeptNo
from    Employees e
join    Emp_Versions v
on  v.EmpNo = e.EmpNo
and v.EffDate =(
select  Max( EffDate )
from    Emp_Versions
where   EmpNo = v.EmpNo );

Не позволяйте подзапросу пугать вас. Тщательное изучение показывает, что он находит нужную строку версии с поиском по индексу вместо сканирования, которое генерирует большинство других методов. Попробуйте — это быстро (хотя, конечно, пробег может варьироваться в зависимости от разных СУБД).

Но вот где это действительно хорошо. Предположим, вы хотели посмотреть, как выглядят данные на определенную дату. Как будет выглядеть этот запрос? Просто возьмите запрос выше и сделайте небольшое дополнение:

select  e.EmpNo, e.FirstName, v.LastName, e.Birthdate, e.Hiredate, v.Payrate, v.DeptNo
from    Employees e
join    Emp_Versions v
on  v.EmpNo = e.EmpNo
and v.EffDate =(
select  Max( EffDate )
from    Emp_Versions
where   EmpNo = v.EmpNo
and EffDate <= :DateOfInterest ); --> Just this difference

Эта последняя строка позволяет «вернуться во времени», чтобы увидеть, как выглядели данные в любое конкретное время в прошлом. И, если DateOfInterest — текущее системное время, он возвращает текущие данные. Это означает, что запрос для просмотра текущих данных и запрос для просмотра прошлых данных фактически являются одним и тем же запросом.

1

Другие решения

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

0

По вопросам рекламы [email protected]