Kami memiliki persyaratan dalam proyek untuk menyimpan semua revisi (Riwayat Perubahan) untuk entitas dalam database. Saat ini kami memiliki 2 proposal yang dirancang untuk ini:
mis. untuk Entitas "Karyawan"
Desain 1:
-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"
-- Holds the Employee Revisions in Xml. The RevisionXML will contain
-- all data of that particular EmployeeId
"EmployeeHistories (EmployeeId, DateModified, RevisionXML)"
Desain 2:
-- Holds Employee Entity
"Employees (EmployeeId, FirstName, LastName, DepartmentId, .., ..)"
-- In this approach we have basically duplicated all the fields on Employees
-- in the EmployeeHistories and storing the revision data.
"EmployeeHistories (EmployeeId, RevisionId, DateModified, FirstName,
LastName, DepartmentId, .., ..)"
Apakah ada cara lain untuk melakukan hal ini?
Masalah dengan "Desain 1" adalah kita harus mengurai XML setiap kali ketika Anda perlu mengakses data. Ini akan memperlambat proses dan juga menambahkan beberapa batasan seperti kita tidak dapat menambahkan gabungan pada bidang data revisi.
Dan masalah dengan "Desain 2" adalah bahwa kita harus menduplikasi setiap bidang pada semua entitas (Kami memiliki sekitar 70-80 entitas yang ingin kami pertahankan revisi).
sql
database
database-design
versioning
Ramesh Soni
sumber
sumber
Jawaban:
sumber
SELECT * FROM EmployeeHistory WHERE LastName = 'Doe'
hasil sederhana dalam pemindaian tabel penuh . Bukan ide terbaik untuk mengukur aplikasi.Saya pikir pertanyaan kunci untuk ditanyakan di sini adalah 'Siapa / Apa yang akan menggunakan sejarah'?
Jika sebagian besar untuk pelaporan / sejarah yang dapat dibaca manusia, kami telah menerapkan skema ini di masa lalu ...
Buat tabel yang disebut 'AuditTrail' atau sesuatu yang memiliki bidang berikut ...
Anda kemudian dapat menambahkan kolom 'LastUpdatedByUserID' ke semua tabel Anda yang harus disetel setiap kali Anda melakukan pembaruan / masukkan pada tabel.
Anda kemudian dapat menambahkan pemicu ke setiap tabel untuk menangkap setiap sisipan / pembaruan yang terjadi dan membuat entri di tabel ini untuk setiap bidang yang diubah. Karena tabel ini juga diberikan 'LastUpdateByUserID' untuk setiap pembaruan / masukkan, Anda dapat mengakses nilai ini di pemicu dan menggunakannya saat menambahkan ke tabel audit.
Kami menggunakan bidang RecordID untuk menyimpan nilai bidang kunci dari tabel yang sedang diperbarui. Jika itu adalah kunci gabungan, kami hanya melakukan penggabungan string dengan tanda '~' di antara bidang.
Saya yakin sistem ini mungkin memiliki kekurangan - untuk basis data yang banyak diperbarui, kinerjanya mungkin sangat bagus, tetapi untuk aplikasi web saya, kami mendapatkan lebih banyak pembacaan daripada penulisan dan tampaknya berkinerja cukup baik. Kami bahkan menulis sedikit utilitas VB.NET untuk secara otomatis menulis pemicu berdasarkan definisi tabel.
Hanya pemikiran saja!
sumber
sysname
mungkin tipe data yang lebih cocok untuk nama tabel dan kolom.Artikel History Tables di blog Programmer Database mungkin berguna - mencakup beberapa poin yang diangkat di sini dan membahas penyimpanan delta.
Edit
Dalam esai History Tables , penulis ( Kenneth Downs ), merekomendasikan mempertahankan tabel sejarah setidaknya tujuh kolom:
Kolom yang tidak pernah berubah, atau yang riwayatnya tidak diperlukan, tidak boleh dilacak di tabel riwayat untuk menghindari kembung. Menyimpan delta untuk nilai numerik dapat membuat kueri berikutnya lebih mudah, meskipun dapat diturunkan dari nilai lama dan baru.
Tabel histori harus aman, dengan pengguna non-sistem dicegah untuk memasukkan, memperbarui atau menghapus baris. Hanya pembersihan berkala yang didukung untuk mengurangi ukuran keseluruhan (dan jika diizinkan oleh use case).
sumber
Kami telah menerapkan solusi yang sangat mirip dengan solusi yang disarankan Chris Roberts, dan itu bekerja dengan cukup baik bagi kami.
Satu-satunya perbedaan adalah kami hanya menyimpan nilai baru. Nilai lama setelah semua disimpan di baris riwayat sebelumnya
Katakanlah Anda memiliki tabel dengan 20 kolom. Dengan cara ini Anda hanya perlu menyimpan kolom persis yang telah berubah daripada harus menyimpan seluruh baris.
sumber
Hindari Desain 1; itu tidak sangat berguna sekali Anda harus misalnya rollback ke versi lama dari catatan - baik secara otomatis atau "secara manual" menggunakan konsol administrator.
Saya tidak benar-benar melihat kerugian dari Desain 2. Saya pikir yang kedua, tabel Riwayat harus berisi semua kolom yang ada di tabel, Catatan pertama. Misalnya dalam mysql Anda dapat dengan mudah membuat tabel dengan struktur yang sama dengan tabel lain (
create table X like Y
). Dan, ketika Anda akan mengubah struktur tabel Records di database langsung Anda, Anda harusalter table
tetap menggunakan perintah - dan tidak ada upaya besar dalam menjalankan perintah ini juga untuk tabel History Anda.Catatan
RevisionId
kolom yang ditambahkan ;ModifiedBy
- pengguna yang membuat revisi tertentu. Anda mungkin juga ingin memiliki bidangDeletedBy
untuk melacak siapa yang menghapus revisi tertentu.DateModified
seharusnya berarti - baik itu berarti di mana revisi khusus ini dibuat, atau itu akan berarti ketika revisi khusus ini digantikan oleh yang lain. Yang pertama mengharuskan bidang berada di tabel Catatan, dan tampaknya lebih intuitif pada pandangan pertama; solusi kedua namun tampaknya lebih praktis untuk catatan yang dihapus (tanggal ketika revisi khusus ini dihapus). Jika Anda mencari solusi pertama, Anda mungkin akan membutuhkan bidang keduaDateDeleted
(hanya jika Anda membutuhkannya tentu saja). Tergantung Anda dan apa yang sebenarnya ingin Anda rekam.Operasi dalam Desain 2 sangat sepele:
MemodifikasiJika Anda memilih Design 2, semua perintah SQL yang diperlukan untuk melakukannya akan sangat sangat mudah, demikian juga perawatannya! Mungkin, akan jauh lebih mudah jika Anda menggunakan kolom bantu (
RevisionId
,DateModified
) juga di tabel Catatan - untuk menjaga kedua tabel pada struktur yang sama persis (kecuali untuk kunci unik)! Ini akan memungkinkan untuk perintah SQL sederhana, yang akan toleran terhadap perubahan struktur data:Jangan lupa menggunakan transaksi!
Sedangkan untuk penskalaan , solusi ini sangat efisien, karena Anda tidak mengubah data dari XML bolak-balik, hanya menyalin seluruh baris tabel - kueri yang sangat sederhana, menggunakan indeks - sangat efisien!
sumber
Jika Anda harus menyimpan riwayat, buat tabel bayangan dengan skema yang sama dengan tabel yang Anda lacak dan kolom 'Tanggal Revisi' dan 'Jenis Revisi' (mis. 'Hapus', 'pembaruan'). Tulis (atau hasilkan - lihat di bawah) seperangkat pemicu untuk mengisi tabel audit.
Cukup mudah untuk membuat alat yang akan membaca kamus data sistem untuk tabel dan menghasilkan skrip yang menciptakan tabel bayangan dan serangkaian pemicu untuk mengisinya.
Jangan mencoba menggunakan XML untuk ini, penyimpanan XML jauh lebih efisien daripada penyimpanan tabel database asli yang digunakan pemicu jenis ini.
sumber
Ramesh, saya terlibat dalam pengembangan sistem berdasarkan pendekatan pertama.
Ternyata menyimpan revisi sebagai XML mengarah ke pertumbuhan basis data yang sangat besar dan secara signifikan memperlambat segalanya.
Pendekatan saya adalah memiliki satu tabel per entitas:
di mana IsActive adalah tanda dari versi terbaru
Jika Anda ingin mengaitkan beberapa info tambahan dengan revisi, Anda bisa membuat tabel terpisah berisi info itu dan menautkannya dengan tabel entitas menggunakan relasi PK \ FK.
Dengan cara ini Anda dapat menyimpan semua versi karyawan dalam satu tabel. Kelebihan dari pendekatan ini:
Perhatikan bahwa Anda harus mengizinkan kunci primer tidak unik.
sumber
Cara saya melihat ini dilakukan di masa lalu adalah miliki
Anda tidak pernah "memperbarui" pada tabel ini (kecuali untuk mengubah validitas isCurrent), cukup masukkan baris baru. Untuk EmployeeId yang diberikan, hanya 1 baris yang dapat memiliki isCurrent == 1.
Kompleksitas mempertahankan ini dapat disembunyikan oleh pandangan dan "bukan" pemicu (dalam oracle, saya anggap hal serupa RDBMS lainnya), Anda bahkan dapat pergi ke tampilan terwujud jika tabel terlalu besar dan tidak dapat ditangani oleh indeks) .
Metode ini ok, tetapi Anda bisa berakhir dengan beberapa pertanyaan kompleks.
Secara pribadi, saya sangat menyukai cara Desain 2 Anda untuk melakukannya, yang merupakan cara saya melakukannya di masa lalu juga. Sederhana untuk dipahami, mudah diimplementasikan, dan mudah dipelihara.
Itu juga menciptakan sangat sedikit overhead untuk database dan aplikasi, terutama ketika melakukan permintaan baca, yang kemungkinan akan Anda lakukan 99% dari waktu.
Ini juga akan cukup mudah untuk secara otomatis membuat tabel sejarah dan pemicu untuk dipelihara (dengan asumsi itu akan dilakukan melalui pemicu).
sumber
Revisi data adalah aspek konsep ' valid-time ' dari Database Temporal. Banyak penelitian telah dilakukan, dan banyak pola dan pedoman telah muncul. Saya menulis balasan panjang dengan banyak referensi untuk pertanyaan ini bagi mereka yang tertarik.
sumber
Saya akan berbagi dengan Anda desain saya dan ini berbeda dari kedua desain Anda karena membutuhkan satu tabel untuk setiap jenis entitas. Saya menemukan cara terbaik untuk mendeskripsikan desain database adalah melalui ERD, ini milik saya:
Dalam contoh ini kami memiliki entitas bernama karyawan . tabel pengguna menyimpan catatan pengguna Anda dan entitas dan entitas_revision adalah dua tabel yang menyimpan riwayat revisi untuk semua jenis entitas yang akan Anda miliki di sistem Anda. Begini cara desain ini bekerja:
Dua bidang dari entity_id dan revision_id
Setiap entitas di sistem Anda akan memiliki id entitas unik sendiri. Entitas Anda mungkin melalui revisi tetapi entity_idnya akan tetap sama. Anda harus menyimpan id entitas ini di meja karyawan Anda (sebagai kunci asing). Anda juga harus menyimpan tipe entitas Anda di tabel entitas (mis. 'Karyawan'). Sekarang seperti untuk revision_id, seperti namanya, itu melacak revisi entitas Anda. Cara terbaik yang saya temukan untuk ini adalah dengan menggunakan employee_id sebagai revision_id Anda. Ini berarti Anda akan memiliki id revisi duplikat untuk berbagai jenis entitas, tetapi ini bukan masalah bagi saya (saya tidak yakin dengan kasus Anda). Satu-satunya catatan penting yang harus dibuat adalah bahwa kombinasi dari entity_id dan revision_id harus unik.
Ada juga bidang keadaan dalam tabel entity_revision yang menunjukkan keadaan revisi. Hal ini dapat memiliki salah satu dari tiga negara:
latest
,obsolete
ataudeleted
(tidak bergantung pada tanggal revisi membantu Anda banyak untuk meningkatkan pertanyaan Anda).Satu catatan terakhir tentang revision_id, saya tidak membuat kunci asing yang menghubungkan employee_id ke revision_id karena kami tidak ingin mengubah tabel entitas_revision untuk setiap jenis entitas yang mungkin kami tambahkan di masa depan.
INSERSI
Untuk setiap karyawan yang ingin Anda masukkan ke dalam basis data, Anda juga akan menambahkan catatan ke entitas dan entitas_revisi . Dua catatan terakhir ini akan membantu Anda melacak oleh siapa dan kapan catatan telah dimasukkan ke dalam basis data.
MEMPERBARUI
Setiap pembaruan untuk catatan karyawan yang ada akan diimplementasikan sebagai dua sisipan, satu di tabel karyawan dan satu di entitas_revision. Yang kedua akan membantu Anda untuk mengetahui oleh siapa dan kapan catatan telah diperbarui.
PENGHAPUSAN
Untuk menghapus karyawan, catatan dimasukkan ke entitas_revision yang menyatakan penghapusan dan dilakukan.
Seperti yang Anda lihat dalam desain ini tidak ada data yang pernah diubah atau dihapus dari database dan yang lebih penting setiap jenis entitas hanya membutuhkan satu tabel. Secara pribadi saya menemukan desain ini sangat fleksibel dan mudah dikerjakan. Tetapi saya tidak yakin tentang Anda karena kebutuhan Anda mungkin berbeda.
[MEMPERBARUI]
Setelah mendukung partisi dalam versi MySQL baru, saya percaya desain saya juga dilengkapi dengan salah satu pertunjukan terbaik juga. Satu dapat mempartisi
entity
tabel menggunakantype
bidang sementara partisientity_revision
menggunakanstate
bidangnya. Ini akan meningkatkanSELECT
pertanyaan sejauh ini sambil menjaga desain tetap sederhana dan bersih.sumber
Jika memang jejak audit adalah yang Anda butuhkan, saya akan condong ke solusi tabel audit (lengkap dengan salinan denormalized dari kolom penting di tabel lain, misalnya,
UserName
). Perlu diingat, pengalaman pahit itu mengindikasikan bahwa satu meja audit akan menjadi hambatan besar di jalan; mungkin sepadan dengan upaya untuk membuat tabel audit individual untuk semua tabel yang diaudit.Jika Anda perlu melacak versi historis (dan / atau masa depan) yang sebenarnya, maka solusi standar adalah untuk melacak entitas yang sama dengan beberapa baris menggunakan beberapa kombinasi nilai awal, akhir, dan durasi. Anda dapat menggunakan tampilan untuk membuat mengakses nilai saat ini nyaman. Jika ini adalah pendekatan yang Anda ambil, Anda dapat mengalami masalah jika referensi data versi Anda bisa berubah tetapi data tidak berversi.
sumber
Jika Anda ingin melakukan yang pertama, Anda mungkin ingin menggunakan XML untuk tabel Karyawan juga. Sebagian besar basis data yang lebih baru memungkinkan Anda melakukan kueri ke dalam bidang XML sehingga ini tidak selalu menjadi masalah. Dan mungkin lebih mudah untuk memiliki satu cara untuk mengakses data karyawan terlepas dari apakah itu versi terbaru atau versi sebelumnya.
Saya akan mencoba pendekatan kedua. Anda bisa menyederhanakan ini dengan hanya memiliki satu tabel Karyawan dengan bidang DateModified. EmployeeId + DateModified akan menjadi kunci utama dan Anda dapat menyimpan revisi baru dengan hanya menambahkan baris. Dengan cara ini, pengarsipan versi yang lebih lama dan mengembalikan versi dari arsip juga lebih mudah.
Cara lain untuk melakukan ini bisa menjadi model datavault oleh Dan Linstedt. Saya melakukan proyek untuk biro statistik Belanda yang menggunakan model ini dan itu bekerja dengan sangat baik. Tapi saya tidak berpikir itu langsung berguna untuk penggunaan basis data sehari-hari. Anda mungkin mendapatkan beberapa ide dari membaca makalahnya.
sumber
Bagaimana tentang:
Anda membuat kunci utama (EmployeeId, DateModified), dan untuk mendapatkan catatan "saat ini" Anda cukup memilih MAX (DateModified) untuk setiap karyawanid. Menyimpan IsCurrent adalah ide yang sangat buruk, karena pertama-tama, dapat dihitung, dan kedua, terlalu mudah bagi data untuk keluar dari sinkronisasi.
Anda juga dapat membuat tampilan yang hanya berisi daftar catatan terbaru, dan sebagian besar menggunakannya saat bekerja di aplikasi Anda. Yang menyenangkan tentang pendekatan ini adalah Anda tidak memiliki duplikat data, dan Anda tidak harus mengumpulkan data dari dua tempat berbeda (saat ini di Karyawan, dan diarsipkan di EmployeesHistory) untuk mendapatkan semua sejarah atau kembalikan, dll) .
sumber
Jika Anda ingin mengandalkan data riwayat (untuk alasan pelaporan), Anda harus menggunakan struktur seperti ini:
Atau solusi global untuk aplikasi:
Anda dapat menyimpan revisi Anda juga dalam XML, maka Anda hanya memiliki satu catatan untuk satu revisi. Ini akan terlihat seperti:
sumber
Kami memiliki persyaratan serupa, dan yang kami temukan adalah sering kali pengguna hanya ingin melihatnya apa yang telah diubah, belum tentu mengembalikan perubahan apa pun.
Saya tidak yakin kasus penggunaan Anda, tetapi yang kami lakukan adalah membuat dan mengaudit tabel yang diperbarui secara otomatis dengan perubahan pada entitas bisnis, termasuk nama yang ramah dari setiap referensi dan enumerasi kunci asing.
Setiap kali pengguna menyimpan perubahan mereka, kami memuat ulang objek lama, menjalankan perbandingan, mencatat perubahan, dan menyimpan entitas (semua dilakukan dalam satu transaksi basis data jika ada masalah).
Ini tampaknya bekerja sangat baik untuk pengguna kami dan menyelamatkan kami dari sakit kepala memiliki tabel audit yang benar-benar terpisah dengan bidang yang sama dengan entitas bisnis kami.
sumber
Sepertinya Anda ingin melacak perubahan pada entitas tertentu dari waktu ke waktu, mis. ID 3, "bob", "123 main street", lalu ID 3 lainnya, "bob" "234 elm st", dan seterusnya, pada dasarnya bisa untuk memuntahkan riwayat revisi yang menunjukkan setiap alamat "bob" telah ada di.
Cara terbaik untuk melakukan ini adalah memiliki bidang "saat ini" pada setiap catatan, dan (mungkin) cap waktu atau FK ke tabel tanggal / waktu.
Sisipan kemudian harus mengatur "is current" dan juga unset the "is current" pada catatan "is current" sebelumnya. Kueri harus menentukan "sekarang", kecuali jika Anda ingin semua riwayat.
Ada beberapa perubahan lebih lanjut untuk ini jika tabelnya sangat besar, atau sejumlah besar revisi diharapkan, tetapi ini adalah pendekatan yang cukup standar.
sumber