Дизайн бази даних: як вирішити проблему «архіву»?


18

Я впевнений, що багато додатків, критичних програм, банків і так далі роблять це щодня.

Ідея, що стоїть за цим:

  • усі рядки повинні мати історію
  • всі посилання повинні залишатися узгодженими
  • повинно бути легко робити запити, щоб отримати "поточні" стовпці
  • клієнти, які придбали застарілі речі, все одно повинні побачити, що вони купили, хоча цей товар вже не є частиною каталогу

і так далі.

Ось, що я хочу зробити, і я поясню проблеми, з якими стикаюся.

Усі мої таблиці матимуть ці стовпці:

  • id
  • id_origin
  • date of creation
  • start date of validity
  • start end of validity

Ось ідеї для операцій CRUD:

  • create = вставити новий рядок з id_origin= id, date of creation= зараз, start date of validity= зараз, end date of validity= null (= означає, що це поточний активний запис)
  • update =
    • read = читати всі записи з end date of validity== null
    • оновіть "поточний" запис end date of validity= null з end date of validity= зараз
    • створити новий з новими значеннями, і end date of validity= null (= означає, що це поточний активний запис)
  • delete = оновити "поточний" запис end date of validity= null з end date of validity= зараз

Тож ось моя проблема: з багатьма-багатьма асоціаціями. Візьмемо приклад зі значеннями:

  • Таблиця A (id = 1, id_origin = 1, start = now, end = null)
  • Таблиця A_B (start = now, end = null, id_A = 1, id_B = 48)
  • Таблиця B (id = 48, id_origin = 48, start = now, end = null)

Тепер я хочу оновити таблицю A, запис id = 1

  • Я відмічаю запис id = 1 з end = now
  • Я вставляю нове значення в таблицю A і ... чорт я втратив своє відношення A_B, якщо теж не дублюю відношення ... це закінчиться таблицею:

  • Таблиця A (id = 1, id_origin = 1, start = now, end = now + 8mn)

  • Таблиця A (id = 2, id_origin = 1, start = now + 8mn, end = null)
  • Таблиця A_B (start = now, end = null, id_A = 1, id_B = 48)
  • Таблиця A_B (start = now, end = null, id_A = 2, id_B = 48)
  • Таблиця B (id = 48, id_origin = 48, start = now, end = null)

І ... ну, у мене є ще одна проблема: відношення A_B: повинен я позначити (id_A = 1, id_B = 48) як застарілий чи ні (A - id = 1 застарілий, але не B - 48)?

Як з цим боротися?

Я мушу розробити це у великих масштабах: продукти, партнери тощо.

Який ваш досвід щодо цього? Як би ви зробили (як це зробили)?

- Редагувати

Я знайшов цю дуже цікаву статтю , але вона не належним чином стосується "каскадної застарілості" (= те, що я прошу насправді)


Як щодо копіювання даних оновленого запису до його оновлення до нового з новим ідентифікатором, що зберігає пов'язаний список історії з полем id_hist_prev. Тож ідентифікатор поточного запису ніколи не змінюється

Замість того, щоб переосмислити колесо, чи розглядали ви, наприклад, Flash Archive Data Archive на Oracle?
Джек Дуглас

Відповіді:


4

Мені не зрозуміло, чи ці вимоги призначені для аудиторських цілей або просто прості історичні довідки, такі як CRM та торгові візки.

У будь-якому випадку розглянемо, чи є основна та main_archive таблиця для кожної основної області, де це потрібно. "Main" матиме лише поточні / активні записи, тоді як "main_archive" матиме копію всього, що коли-небудь переходить у основний. Вставка / оновлення в main_archive може бути ініціатором для вставки / оновлення в main. Після цього видалення з main_archive може запускатися протягом більш тривалого періоду, якщо взагалі.

Для таких референтних питань, як Купе X, придбаний Product Y, найпростіший спосіб вирішити свою референтну турботу cust_archive -> product_archive - ніколи не видаляти записи з product_archive. Як правило, в цій таблиці шпур повинен бути набагато нижчим, тому розмір не повинен викликати особливих проблем.

HTH.


2
Чудова відповідь, але я хотів би додати, що ще однією перевагою наявності архівної таблиці є те, що вони, як правило, денормалізовані, що робить звітність про такі дані набагато ефективнішою. Розгляньте потреби звітування вашої заявки і при такому підході.
maple_shaft

1
У більшості баз даних я проектую, що всі "основні" таблиці мають префікс назви продукту, як LP_і кожна важлива таблиця має еквівалент LH_, тригери вставляють історичні рядки на вставлення, оновлення, видалення. Це працює не у всіх випадках, але це була міцна модель для речей, які я роблю.

Я погоджуюся - якщо більшість запитів стосуються "поточних" рядків, ви, мабуть, отримаєте перевагу perf, розділивши поточний з історії на дві таблиці. Вид може об'єднати їх назад, як зручність. Таким чином, сторінки даних із поточними рядками збираються разом і, ймовірно, краще залишаються в кеші, і вам не доведеться постійно кваліфікувати запити на поточні дані з логікою дати.
onupdatecascade

1
@onupdatecascade: Зауважте, що (принаймні, у деяких RDBMS) ви можете ставити індекси на цей UNIONвид, що дозволяє робити цікаві речі, такі як примусове застосування унікального обмеження як у поточних, так і в історичних записах.
Йон усіх торгів

Через 5 років я зробив багато речей і весь час повертав вам свою ідею. Єдине, що я змінив - це те, що в таблицях історії у мене стовпці " id" і " id_ref". id_refє посиланням на фактичну ідею таблиці. Приклад: personі person_h. у person_hмене є " id" і " id_ref", де id_refце пов'язано з " person.id", тому я можу мати багато рядків з однаковим person.id(= коли рядок з personмодифікованим), і всі idз моїх таблиць є autoinc.
Олів’є Понс

2

Це має певне збіг з функціональним програмуванням; конкретно концепція незмінності.

У вас є одна таблиця, яка називається, PRODUCTа інша - PRODUCTVERSIONабо інша . Коли ви змінюєте продукт, ви не оновлюєте, ви просто вставляєте новий PRODUCTVERSIONрядок. Щоб отримати останню інформацію, ви можете проіндексувати таблицю за номером версії (desc), часовою позначкою (desc) або прапором ( LatestVersion).

Тепер якщо у вас є щось, що посилається на продукт, ви можете вирішити, на яку таблицю він вказує. Чи вказує це на PRODUCTсутність (завжди посилається на цей товар) або на PRODUCTVERSIONсутність (стосується лише цієї версії товару)?

Це ускладнюється. Що робити, якщо у вас є фотографії товару? Вони повинні вказувати на таблицю версій, оскільки їх можна було змінити, але в багатьох випадках вони не будуть, і ви не хочете дублювати дані без потреби. Це означає, що вам потрібна PICTUREтаблиця та PRODUCTVERSIONPICTUREстосунки «багато-до-багатьох».


1

Я реалізував всі речі з тут з 4 полями , які знаходяться на всі мої таблицях:

  • ід
  • date_creation
  • date_validity_start
  • date_validity_end

Кожен раз, коли запис повинен бути змінений, я його дублюю, відзначаю дублюваний запис як "старий" =, date_validity_end=NOW()а поточний - як хороший date_validity_start=NOW()і date_validity_end=NULL.

Трюк у тому, що стосується багато до багатьох і одне до багатьох: воно працює, не торкаючись їх! Це все про складніші запити: для запиту запису в точну дату (= не зараз), я маю для кожного об'єднання та для основної таблиці додати ці обмеження:

WHERE (
  (date_validity_start<=:dateparam AND date_validity_end IS NULL)
  OR
  (date_validity_start<=:dateparam AND date_validity_start>=:dateparam)
)

Так що з продуктами та атрибутами (співвідношення багато до багатьох):

SELECT p.*,a.*

FROM products p

JOIN products_attributes pa
ON pa.id_product = p.id
AND (
  (pa.date_validity_start<=:dateparam AND pa.date_validity_end IS NULL)
  OR
  (pa.date_validity_start<=:dateparam AND pa.date_validity_start>=:dateparam)
)

JOIN attributes a
ON a.id = pa.id_attribute
AND (
  (a.date_validity_start<=:dateparam AND a.date_validity_end IS NULL)
  OR
  (a.date_validity_start<=:dateparam AND a.date_validity_start>=:dateparam)
)

WHERE (
  (p.date_validity_start<=:dateparam AND p.date_validity_end IS NULL)
  OR
  (p.date_validity_start<=:dateparam AND p.date_validity_start>=:dateparam)
)

0

Як щодо цього? Це здається простим і досить ефективним для того, що я робив у минулому. У таблиці "Історія" використовуйте інший ПК. Отже, ваше поле "CustomerID" - ПК у таблиці клієнтів, але у таблиці "історія" ваш ПК - "NewCustomerID". "CustomerID" стає лише іншим полем для читання. Це залишає "CustomerID" незмінним в історії, і всі ваші стосунки залишаються цілими.


Дуже приємна ідея. Те, що я зробив, дуже схоже: я копіюю запис, а новий відзначаю як "застарілий", так що поточний запис залишається таким же. Примітка. Я хотів створити тригер для кожної таблиці, але mysql забороняє модифікувати таблицю, коли ви перебуваєте в тригері цієї таблиці. PostGRESQL зроби це. SQL-сервер зроби це. Oracle зроби це. Ну коротко кажучи, у MySQL ще дуже довгий шлях, і наступного разу я подумаю двічі, вибираючи сервер бази даних.
Олів'є Понс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.