Desain basis data: bagaimana menangani masalah "arsip"?

18

Saya cukup yakin banyak aplikasi, aplikasi kritis, bank dan sebagainya melakukan ini setiap hari.

Gagasan di balik semua itu adalah:

  • semua baris harus memiliki riwayat
  • semua tautan harus tetap koheren
  • seharusnya mudah untuk membuat permintaan untuk mendapatkan kolom "saat ini"
  • klien yang telah membeli barang-barang usang masih harus melihat apa yang telah mereka beli meskipun produk ini bukan bagian dari katalog lagi

dan seterusnya.

Inilah yang ingin saya lakukan, dan saya akan menjelaskan masalah yang saya hadapi.

Semua tabel saya akan memiliki kolom-kolom itu:

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

Dan inilah ide untuk operasi CRUD:

  • buat = masukkan baris baru dengan id_origin= id, date of creation= sekarang, start date of validity= sekarang, end date of validity= null (= berarti ini adalah rekaman aktif saat ini)
  • pembaruan =
    • baca = baca semua catatan dengan end date of validity== null
    • perbarui catatan "saat ini" end date of validity= null dengan end date of validity= sekarang
    • buat yang baru dengan nilai-nilai baru, dan end date of validity= null (= berarti ini adalah rekaman aktif saat ini)
  • delete = perbarui catatan "saat ini" end date of validity = null dengan end date of validity= sekarang

Jadi, inilah masalah saya: dengan banyak asosiasi. Mari kita ambil contoh dengan nilai:

  • Tabel A (id = 1, id_origin = 1, mulai = sekarang, akhir = nol)
  • Tabel A_B (mulai = sekarang, akhir = nol, id_A = 1, id_B = 48)
  • Tabel B (id = 48, id_origin = 48, mulai = sekarang, akhir = nol)

Sekarang saya ingin memperbarui tabel A, record id = 1

  • Saya menandai catatan id = 1 dengan ujung = sekarang
  • Saya memasukkan nilai baru ke dalam tabel A dan ... sialan saya telah kehilangan relasi saya A_B kecuali jika saya menduplikasi relasinya juga ... ini akan berakhir pada tabel:

  • Tabel A (id = 1, id_origin = 1, mulai = sekarang, akhir = sekarang + 8juta)

  • Tabel A (id = 2, id_origin = 1, mulai = sekarang + 8 juta, akhir = nol)
  • Tabel A_B (mulai = sekarang, akhir = nol, id_A = 1, id_B = 48)
  • Tabel A_B (mulai = sekarang, akhir = nol, id_A = 2, id_B = 48)
  • Tabel B (id = 48, id_origin = 48, mulai = sekarang, akhir = nol)

Dan ... yah saya punya masalah lain: hubungan A_B: haruskah saya tandai (id_A = 1, id_B = 48) sebagai usang atau tidak (A - id = 1 sudah usang, tetapi bukan B - 48)?

Bagaimana cara menghadapinya?

Saya harus merancang ini dalam skala besar: produk, mitra, dan sebagainya.

Apa pengalaman Anda dalam hal ini? Bagaimana yang akan Anda lakukan (bagaimana Anda melakukannya)?

- Edit

Saya telah menemukan artikel yang sangat menarik ini , tetapi tidak berurusan dengan "usang usang" (= apa yang sebenarnya saya tanyakan)

Olivier Pons
sumber
Bagaimana dengan menyalin data dari catatan yang diperbarui sebelum diperbarui ke yang baru dengan id baru menjaga daftar sejarah yang ditautkan dengan bidang id_hist_prev. Jadi id dari catatan saat ini tidak pernah berubah
Alih-alih yang menciptakan kembali roda, pernahkah Anda mempertimbangkan untuk menggunakan, misalnya, Flashback Data Archive on Oracle?
Jack Douglas

Jawaban:

4

Tidak jelas bagi saya apakah persyaratan ini untuk tujuan audit atau hanya referensi sejarah sederhana seperti dengan CRM dan keranjang belanja.

Either way, pertimbangkan memiliki tabel main dan main_archive untuk setiap area utama di mana ini diperlukan. "Utama" hanya akan memiliki entri saat ini / aktif sedangkan "main_archive" akan memiliki salinan semua yang pernah masuk ke utama. Sisipkan / perbarui ke main_archive dapat menjadi pemicu dari memasukkan / memperbarui ke utama. Menghapus terhadap main_archive kemudian dapat berjalan melintasi periode waktu yang lebih lama, jika pernah.

Untuk masalah referensial seperti Cust X membeli Produk Y, cara termudah untuk menyelesaikan masalah referensial Anda dari cust_archive -> product_archive adalah tidak pernah menghapus entri dari product_archive. Secara umum, churn harus jauh lebih rendah di meja itu sehingga ukurannya tidak terlalu buruk.

HTH.


sumber
2
Jawaban yang bagus, tetapi saya ingin menambahkan bahwa manfaat lain dari memiliki tabel arsip adalah bahwa mereka cenderung didenormalisasi, membuat pelaporan data seperti itu jauh lebih efisien. Pertimbangkan juga kebutuhan pelaporan aplikasi Anda dengan pendekatan ini.
maple_shaft
1
Di sebagian besar database saya mendesain semua tabel 'utama' memiliki awalan nama produk seperti LP_, dan setiap tabel penting memiliki yang setara LH_, dengan pemicu menyisipkan baris historis pada menyisipkan, memperbarui, menghapus. Ini tidak berfungsi untuk semua kasus tetapi sudah menjadi model yang solid untuk hal-hal yang saya lakukan.
Saya setuju - jika sebagian besar kueri adalah untuk baris "saat ini", Anda mungkin akan mendapatkan keuntungan dengan mempartisi arus dari sejarah dalam dua tabel. Suatu pandangan dapat menyatukan mereka kembali sebagai suatu kenyamanan. Dengan cara ini halaman data dengan baris saat ini semuanya bersama-sama dan mungkin tetap dalam cache lebih baik, dan Anda tidak harus terus-menerus memenuhi syarat permintaan untuk data saat ini dengan logika tanggal.
onupdatecascade
1
@onupdatecascade: Perhatikan bahwa (setidaknya dalam beberapa RDBMS) Anda dapat menempatkan indeks pada UNIONtampilan itu, yang memungkinkan Anda melakukan hal-hal keren seperti memberlakukan batasan unik di kedua catatan saat ini dan sejarah.
Jon of All Trades
5 tahun kemudian, saya telah melakukan banyak hal dan sepanjang waktu saya mendapatkan kembali ide Anda. Satu-satunya hal yang saya ubah adalah pada tabel sejarah, saya memiliki kolom " id", dan " id_ref". id_refadalah referensi ke ide sebenarnya dari tabel. Contoh: persondan person_h. di person_hsaya punya " id", dan " id_ref" di mana id_refterkait dengan ' person.id' jadi saya dapat memiliki banyak baris dengan yang sama person.id(= ketika satu baris persondimodifikasi) dan semua idtabel saya semuanya otomatis.
Olivier Pons
2

Ini memiliki beberapa tumpang tindih dengan pemrograman fungsional; khususnya konsep kekekalan.

Anda memiliki satu tabel yang dipanggil PRODUCTdan yang lainnya disebut PRODUCTVERSIONatau serupa. Ketika Anda mengganti produk yang tidak Anda lakukan pembaruan, Anda cukup memasukkan PRODUCTVERSIONbaris baru . Untuk mendapatkan yang terbaru, Anda dapat mengindeks tabel dengan nomor versi (desc), timestamp (desc), atau Anda dapat memiliki bendera (LatestVersion ).

Sekarang jika Anda memiliki sesuatu yang mereferensikan suatu produk, Anda dapat memutuskan tabel mana yang ditunjuknya. Apakah itu menunjuk ke PRODUCTentitas (selalu merujuk ke produk ini) atau kePRODUCTVERSION entitas (hanya merujuk ke versi produk ini)?

Itu jadi rumit. Bagaimana jika Anda memiliki gambar produk? Mereka harus menunjuk ke tabel versi, karena mereka dapat diubah, tetapi dalam banyak kasus, mereka tidak akan dan Anda tidak ingin menggandakan data yang tidak perlu. Itu berarti Anda membutuhkan PICTUREmeja dan hubungan PRODUCTVERSIONPICTUREbanyak-ke-banyak.


sumber
1

Saya sudah menerapkan semua hal dari sini dengan 4 bidang yang ada di semua tabel saya:

  • Indo
  • date_creation
  • date_validity_start
  • date_validity_end

Setiap kali catatan harus dimodifikasi, saya menduplikatnya, menandai catatan yang digandakan sebagai "lama" = date_validity_end=NOW()dan yang sekarang sebagai yang baik date_validity_start=NOW()dan date_validity_end=NULL.

Triknya adalah tentang hubungan banyak ke banyak dan satu ke banyak: itu bekerja tanpa menyentuh mereka! Ini semua tentang kueri yang lebih kompleks: untuk meminta catatan pada tanggal yang tepat (= tidak sekarang), saya punya untuk setiap bergabung, dan untuk tabel utama, untuk menambahkan kendala-kendala itu:

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

Begitu juga dengan produk dan atribut (banyak ke banyak hubungan):

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)
)
Olivier Pons
sumber
0

Bagaimana dengan ini? Tampaknya sederhana dan cukup efektif untuk apa yang telah saya lakukan di masa lalu. Di tabel "riwayat" Anda, gunakan PK yang berbeda. Jadi, bidang "CustomerID" Anda adalah PK di tabel Pelanggan Anda, tetapi di tabel "history", PK Anda adalah "NewCustomerID". "CustomerID" menjadi bidang hanya baca lainnya. Itu membuat "CustomerID" tidak berubah dalam Riwayat dan semua hubungan Anda tetap utuh.

Dua dimensi
sumber
Ide yang sangat bagus Apa yang saya lakukan sangat mirip: Saya menggandakan rekaman, dan menandai yang baru sebagai "usang" sehingga catatan saat ini masih sama. Catatan saya ingin membuat pemicu pada setiap tabel tetapi mysql melarang modifikasi tabel saat Anda menjadi pemicu tabel ini. PostGRESQL melakukan ini. SQL server melakukan ini. Oracle melakukan ini. Singkatnya MySQL masih memiliki jalan yang sangat panjang, dan lain kali saya akan berpikir dua kali ketika memilih server database saya.
Olivier Pons