Bagaimana versi mengontrol catatan dalam database

176

Katakanlah saya memiliki catatan dalam database dan bahwa admin dan pengguna normal dapat melakukan pembaruan.

Adakah yang bisa menyarankan pendekatan / arsitektur yang baik bagaimana mengontrol versi setiap perubahan dalam tabel ini sehingga dimungkinkan untuk mengembalikan catatan ke revisi sebelumnya.

Niels Bosma
sumber

Jawaban:

164

Katakanlah Anda memiliki FOOtabel yang admin dan pengguna dapat perbarui. Sebagian besar waktu Anda dapat menulis kueri terhadap tabel FOO. Hari hari menyenangkan.

Lalu, saya akan membuat FOO_HISTORYtabel. Ini memiliki semua kolom FOOtabel. Kunci utama sama dengan FOO plus kolom RevisionNumber. Ada kunci asing dari FOO_HISTORYke FOO. Anda juga dapat menambahkan kolom yang terkait dengan revisi seperti UserId dan RevisionDate. Isi RevisionNumbers dengan cara yang terus meningkat di semua *_HISTORYtabel (yaitu dari urutan Oracle atau yang setara). Jangan mengandalkan hanya ada satu perubahan dalam sedetik (yaitu jangan dimasukkan RevisionDateke kunci utama).

Sekarang, setiap kali Anda memperbarui FOO, tepat sebelum Anda melakukan pembaruan Anda memasukkan nilai-nilai lama ke dalam FOO_HISTORY. Anda melakukan ini pada tingkat dasar dalam desain Anda sehingga pemrogram tidak dapat secara tidak sengaja melewatkan langkah ini.

Jika Anda ingin menghapus satu baris dari FOOAnda ada beberapa pilihan. Entah kaskade dan hapus semua riwayat, atau lakukan penghapusan logis dengan menandai FOOsebagai terhapus.

Solusi ini bagus ketika Anda sebagian besar tertarik pada nilai saat ini dan hanya sesekali dalam sejarah. Jika Anda selalu membutuhkan riwayat maka Anda dapat menempatkan tanggal mulai dan berakhir yang efektif dan menyimpan semua catatan FOOitu sendiri. Setiap permintaan kemudian perlu memeriksa tanggal tersebut.

WW.
sumber
1
Anda dapat melakukan pembaruan tabel audit dengan pemicu basis data jika lapisan akses data Anda tidak langsung mendukungnya. Juga, tidak sulit untuk membangun pembuat kode untuk membuat pemicu yang menggunakan introspeksi dari kamus data sistem.
ConcernedOfTunbridgeWells
44
Saya woyuld merekomendasikan bahwa Anda benar-benar memasukkan data baru , bukan yang sebelumnya, sehingga tabel riwayat memiliki semua data. Meskipun menyimpan data yang berulang, itu menghilangkan kasus-kasus khusus yang diperlukan untuk berurusan dengan pencarian di kedua tabel ketika data historis diperlukan.
Nerdfest
6
Secara pribadi saya akan merekomendasikan untuk tidak menghapus apa pun (tunda ini untuk kegiatan rumah tangga tertentu) dan memiliki kolom "jenis tindakan" untuk menentukan apakah itu memasukkan / memperbarui / menghapus. Untuk penghapusan, Anda menyalin baris seperti biasa, tetapi menempatkan "hapus" di kolom jenis tindakan.
Neil Barnwell
3
@Hydrargyrum Tabel yang memegang nilai saat ini akan berkinerja lebih baik daripada tampilan tabel bersejarah. Anda mungkin juga ingin mendefinisikan kunci asing yang merujuk pada nilai saat ini.
WW.
2
There is a foreign key from FOO_HISTORY to FOO': ide buruk, saya ingin menghapus catatan dari foo tanpa mengubah riwayat. tabel histori harus dimasukkan hanya untuk penggunaan normal.
Jasen
46

Saya pikir Anda mencari versi konten catatan database (seperti yang dilakukan StackOverflow ketika seseorang mengedit pertanyaan / jawaban). Titik awal yang baik mungkin melihat beberapa model database yang menggunakan pelacakan revisi .

Contoh terbaik yang terlintas dalam pikiran adalah MediaWiki, mesin Wikipedia. Bandingkan diagram database di sini , terutama tabel revisi .

Bergantung pada teknologi apa yang Anda gunakan, Anda harus menemukan beberapa algoritma diff / merge yang baik.

Periksa pertanyaan ini apakah itu untuk .NET.

CMS
sumber
30

Di dunia BI, Anda bisa mencapai ini dengan menambahkan tanggal mulai dan tanggal berakhir ke tabel yang ingin Anda versi. Ketika Anda memasukkan catatan pertama ke dalam tabel, tanggal mulai diisi, tetapi tanggal akhir adalah nol. Saat Anda menyisipkan catatan kedua, Anda juga memperbarui tanggal berakhirnya catatan pertama dengan tanggal mulai dari catatan kedua.

Ketika Anda ingin melihat catatan saat ini, Anda memilih yang endDate adalah nol.

Ini kadang-kadang disebut tipe 2 Perlahan Mengubah Dimensi . Lihat juga TupleVersioning

Dave Neeley
sumber
Bukankah meja saya akan tumbuh cukup besar dengan menggunakan pendekatan ini?
Niels Bosma
1
Ya, tetapi Anda bisa mengatasinya dengan mengindeks dan / atau mempartisi tabel. Juga, hanya akan ada sedikit meja besar. Sebagian besar akan jauh lebih kecil.
ConcernedOfTunbridgeWells
Jika saya tidak salah satu-satunya kejatuhan di sini adalah bahwa itu membatasi perubahan hanya sekali per detik, benar?
pimbrouwers
@pimbrouwers ya, itu akhirnya tergantung pada ketepatan bidang dan fungsi yang mengisi mereka.
Dave Neeley
9

Tingkatkan ke SQL 2008.

Coba gunakan Pelacakan Perubahan SQL, di SQL 2008. Alih-alih timestamping dan peretasan kolom batu nisan, Anda dapat menggunakan fitur baru ini untuk melacak perubahan pada data di database Anda.

MSDN SQL 2008 Change Tracking

D3vtr0n
sumber
7

Hanya ingin menambahkan bahwa salah satu solusi bagus untuk masalah ini adalah dengan menggunakan database Temporal . Banyak vendor database menawarkan fitur ini di luar kotak atau melalui ekstensi. Saya telah berhasil menggunakan ekstensi tabel temporal dengan PostgreSQL tetapi orang lain juga memilikinya. Setiap kali Anda memperbarui catatan dalam database, basis data berpegang pada versi sebelumnya dari catatan itu juga.

wuher
sumber
6

Dua pilihan:

  1. Memiliki tabel histori - masukkan data lama ke dalam tabel histori ini setiap kali dokumen asli diperbarui.
  2. Tabel audit - simpan nilai sebelum dan sesudah - hanya untuk kolom yang dimodifikasi dalam tabel audit bersama dengan informasi lain seperti siapa yang diperbarui dan kapan.
alok
sumber
5

Anda dapat melakukan audit pada tabel SQL melalui pemicu SQL. Dari pemicu Anda dapat mengakses 2 tabel khusus ( dimasukkan dan dihapus ). Tabel ini berisi baris persis yang dimasukkan atau dihapus setiap kali tabel diperbarui. Dalam pemicu SQL Anda bisa mengambil baris yang dimodifikasi ini dan memasukkannya ke dalam tabel audit. Pendekatan ini berarti bahwa audit Anda transparan kepada programmer; tidak memerlukan upaya dari mereka atau pengetahuan implementasi.

Bonus tambahan dari pendekatan ini adalah bahwa audit akan terjadi terlepas dari apakah operasi sql terjadi melalui DLL akses data Anda, atau melalui kueri SQL manual; (karena audit dilakukan pada server itu sendiri).

Dokter Jones
sumber
3

Anda tidak mengatakan database apa, dan saya tidak melihatnya di tag postingan. Jika itu untuk Oracle, saya dapat merekomendasikan pendekatan yang ada di Designer: gunakan tabel jurnal . Jika itu untuk database lain, well, pada dasarnya saya merekomendasikan cara yang sama juga ...

Cara kerjanya, jika Anda ingin mereplikasi di DB lain, atau mungkin jika Anda hanya ingin memahaminya, adalah bahwa untuk tabel ada juga tabel bayangan yang dibuat, hanya tabel database normal, dengan spesifikasi bidang yang sama , ditambah beberapa bidang tambahan: seperti tindakan apa yang terakhir diambil (string, nilai tipikal "INS" untuk disisipkan, "UPD" untuk pembaruan dan "DEL" untuk dihapus), waktu untuk kapan tindakan terjadi, dan id pengguna untuk siapa yang melakukan Itu.

Melalui pemicu, setiap tindakan ke setiap baris dalam tabel menyisipkan baris baru di tabel jurnal dengan nilai baru, tindakan apa yang diambil, kapan, dan oleh pengguna apa. Anda tidak pernah menghapus baris apa pun (setidaknya tidak selama beberapa bulan terakhir). Ya itu akan tumbuh besar, dengan mudah jutaan baris, tetapi Anda dapat dengan mudah melacak nilai untuk setiap catatan pada setiap titik waktu sejak jurnal dimulai atau baris jurnal lama terakhir dibersihkan, dan siapa yang membuat perubahan terakhir.

Dalam Oracle semua yang Anda butuhkan dihasilkan secara otomatis sebagai kode SQL, yang harus Anda lakukan adalah mengkompilasi / menjalankannya; dan ia datang dengan aplikasi CRUD dasar (sebenarnya hanya "R") untuk memeriksanya.

Bart
sumber
2

Saya juga melakukan hal yang sama. Saya membuat database untuk rencana pelajaran. Paket ini membutuhkan fleksibilitas versi perubahan atom. Dengan kata lain, setiap perubahan, tidak peduli seberapa kecil, untuk rencana pelajaran harus diizinkan tetapi versi lama harus tetap utuh juga. Dengan begitu, pencipta pelajaran dapat mengedit rencana pelajaran saat siswa menggunakannya.

Cara kerjanya adalah bahwa sekali seorang siswa telah melakukan pelajaran, hasil mereka melekat pada versi yang mereka selesaikan. Jika perubahan dilakukan, hasilnya akan selalu mengarah ke versi mereka.

Dengan cara ini, jika kriteria pelajaran dihapus atau dipindahkan, hasilnya tidak akan berubah.

Cara saya saat ini melakukan ini adalah dengan menangani semua data dalam satu tabel. Biasanya saya hanya memiliki satu bidang id, tetapi dengan sistem ini, saya menggunakan id dan sub_id. Sub_id selalu tetap dengan baris, melalui pembaruan dan penghapusan. Id bertambah secara otomatis. Perangkat lunak rencana pelajaran akan ditautkan ke sub_id terbaru. Hasil siswa akan ditautkan ke id. Saya juga menyertakan cap waktu untuk melacak ketika perubahan terjadi, tetapi tidak perlu menangani versi.

Satu hal yang mungkin saya ubah, setelah saya mengujinya, adalah saya dapat menggunakan ide null endDate yang disebutkan sebelumnya. Di sistem saya, untuk menemukan versi terbaru, saya harus mencari maks (id). Sistem lain hanya mencari endDate = null. Tidak yakin apakah manfaatnya memiliki bidang tanggal yang lain.

Dua sen saya.

Yordania
sumber
2

Sementara @WW. Jawabannya adalah jawaban yang baik. Cara lain adalah membuat kolom versi dan menyimpan semua versi Anda di tabel yang sama.

Untuk satu pendekatan meja Anda juga:

  • Gunakan bendera untuk menunjukkan ala Word Press terbaru
  • ATAU melakukan yang lebih besar dari versi jahat outer join.

Contoh SQL dari outer joinmetode menggunakan angka revisi adalah:

SELECT tc.*
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- path in this case is our natural id.

Kabar buruknya adalah di atas membutuhkan outer joinsambungan luar dan bisa lambat. Kabar baiknya adalah bahwa membuat entri baru secara teoritis lebih murah karena Anda dapat melakukannya dalam satu operasi tulis tanpa transaksi (dengan asumsi database Anda adalah atomik).

Contoh membuat revisi baru untuk '/stuff'mungkin:

INSERT INTO text_content (id, path, data, revision, revision_comment, enabled, create_time, update_time)
(
SELECT
(md5(random()::text)) -- {id}
, tc.path
, 'NEW' -- {data}
, (tc.revision + 1)
, 'UPDATE' -- {comment}
, 't' -- {enabled}
, tc.create_time
, now() 
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- {path}
)

Kami menyisipkan dengan menggunakan data lama. Ini sangat berguna jika Anda hanya ingin memperbarui satu kolom dan menghindari penguncian dan atau transaksi yang optimis.

Pendekatan bendera dan pendekatan tabel sejarah membutuhkan dua baris yang harus dimasukkan / diperbarui.

Keuntungan lain dengan outer joinpendekatan angka revisi adalah bahwa Anda selalu dapat refactor ke pendekatan beberapa tabel nanti dengan pemicu karena pemicu Anda pada dasarnya harus melakukan sesuatu seperti di atas.

Adam Gent
sumber
2

Alok menyarankan di Audit tableatas, saya ingin menjelaskannya di posting saya.

Saya mengadopsi desain tabel tunggal tanpa skema ini pada proyek saya.

Skema:

  • id - INTEGER AUTO INCREMENT
  • nama pengguna - STRING
  • tablename - STRING
  • nilai lama - TEKS / JSON
  • nilai baru - TEXT / JSON
  • Createdon - DATETIME

Tabel ini dapat menyimpan catatan sejarah untuk setiap tabel di satu tempat, dengan riwayat objek lengkap dalam satu catatan. Tabel ini dapat diisi menggunakan pemicu / pengait di mana data berubah, menyimpan snapshot nilai lama dan baru dari baris target.

Pro dengan desain ini:

  • Jumlah tabel yang lebih sedikit untuk dikelola untuk manajemen riwayat.
  • Menyimpan snapshot penuh dari setiap baris status lama dan baru.
  • Mudah dicari di setiap tabel.
  • Dapat membuat partisi berdasarkan tabel.
  • Dapat menentukan kebijakan penyimpanan data per tabel.

Kontra dengan desain ini:

  • Ukuran data bisa besar, jika sistem sering berubah.
Hassan Farid
sumber