Menerapkan sistem versi dengan MySQL

15

Saya tahu ini telah ditanyakan di sini dan di sini , tetapi saya memiliki ide yang sama dengan implementasi yang berbeda dan saya butuh bantuan.

Awalnya saya punya blogstoriesmeja saya dengan struktur ini:

| Column    | Type        | Description                                    |
|-----------|-------------|------------------------------------------------|
| uid       | varchar(15) | 15 characters unique generated id              |
| title     | varchar(60) | story title                                    |
| content   | longtext    | story content                                  |
| author    | varchar(10) | id of the user that originally wrote the story |
| timestamp | int         | integer generated with microtime()             |

Setelah saya memutuskan ingin menerapkan beberapa sistem versi untuk setiap cerita di blog, hal pertama yang muncul di benak saya adalah membuat tabel berbeda untuk mengadakan pengeditan ; setelah itu, saya pikir saya bisa memodifikasi tabel yang ada untuk menyimpan versi daripada mengedit . Ini adalah struktur yang muncul di pikiran saya:

| Column        | Type          | Description                                       |
|------------   |-------------  |------------------------------------------------   |
| story_id      | varchar(15)   | 15 characters unique generated id                 |
| version_id    | varchar(5)    | 5 characters unique generated id                  |
| editor_id     | varchar(10)   | id of the user that commited                      |
| author_id     | varchar(10)   | id of the user that originally wrote the story    |
| timestamp     | int           | integer generated with microtime()                |
| title         | varchar(60)   | current story title                               |
| content       | longtext      | current story text                                |
| coverimg      | varchar(20)   | cover image name                                  |

Alasan mengapa saya datang ke sini:

  • The uidbidang tabel awal adalah UNIK di meja. Sekarang, story_idsudah tidak unik lagi. Bagaimana saya harus menghadapinya? (Saya pikir saya bisa mengatasi story_id = xdan kemudian menemukan versi terbaru, tetapi itu tampaknya sangat memakan sumber daya, jadi tolong beri saran Anda)
  • author_idnilai bidang berulang di setiap setiap baris tabel. Di mana dan bagaimana saya harus menyimpannya?

Edit

Proses pembuatan kode unik ada di CreateUniqueCodefungsi:

trait UIDFactory {
  public function CryptoRand(int $min, int $max): int {
    $range = $max - $min;
    if ($range < 1) return $min;
    $log = ceil(log($range, 2));
    $bytes = (int) ($log / 8) + 1;
    $bits = (int) $log + 1;
    $filter = (int) (1 << $bits) - 1;
    do {
        $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
        $rnd = $rnd & $filter;
    } while ($rnd >= $range);
    return $min + $rnd;
  }
  public function CreateUID(int $length): string {
    $token = "";
    $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
    $codeAlphabet.= "0123456789";
    $max = strlen($codeAlphabet) - 1;
    for ($i=0; $i < $length; $i++) {
        $token .= $codeAlphabet[$this->CryptoRand(0, $max)];
    }
    return $token;
  }
}

Kode ini ditulis dalam Hack , dan pada awalnya ditulis dalam PHP oleh @Scott dalam jawabannya .

Kolom author_iddan editor_id dapat berbeda, karena ada pengguna dengan izin yang cukup untuk mengedit cerita siapa pun.

Pemenang
sumber

Jawaban:

23

Menganalisis skenario — yang menyajikan karakteristik yang terkait dengan subjek yang dikenal sebagai basis data temporal - dari perspektif konseptual, orang dapat menentukan bahwa: (a) Versi Blog Story yang “ada” dan (b) Versi Story Blog yang “dulu” , meskipun sangat menyerupai, adalah entitas dari tipe yang berbeda.

Selain itu, ketika bekerja pada tingkat abstraksi yang logis, fakta (diwakili oleh baris) dari jenis yang berbeda harus disimpan dalam tabel yang berbeda. Dalam kasus yang dipertimbangkan, bahkan ketika sangat mirip, (i) fakta tentang Versi "sekarang" berbeda dari (ii) fakta tentang Versi "masa lalu" .

Karena itu saya sarankan mengelola situasi dengan dua tabel:

  • satu didedikasikan khusus untuk Versi "saat ini" atau "sekarang" dari Cerita Blog , dan

  • yang terpisah, tetapi juga terkait dengan yang lain, untuk semua Versi "sebelumnya" atau "masa lalu" ;

masing-masing dengan (1) jumlah kolom yang sedikit berbeda dan (2) kelompok kendala yang berbeda.

Kembali ke lapisan konseptual, saya menganggap bahwa — di lingkungan bisnis Anda — Penulis dan Editor adalah gagasan yang dapat digambarkan sebagai Peran yang dapat dimainkan oleh Pengguna , dan aspek-aspek penting ini bergantung pada derivasi data (melalui operasi manipulasi tingkat logis) dan interpretasi (yang dilakukan oleh Blog Stories, pembaca dan penulis, di tingkat eksternal sistem informasi yang terkomputerisasi, dengan bantuan satu atau lebih program aplikasi).

Saya akan merinci semua faktor ini dan poin terkait lainnya sebagai berikut.

Peraturan bisnis

Menurut pemahaman saya tentang persyaratan Anda, formulasi aturan bisnis berikut (disatukan dalam hal jenis entitas yang relevan dan jenis hubungan timbal baliknya) sangat membantu dalam membangun skema konseptual yang sesuai :

  • Seorang Pengguna menulis BlogStories nol-satu-atau-banyak
  • Sebuah BlogStory memegang nol-satu-atau-banyak BlogStoryVersions
  • Seorang Pengguna menulis BlogStoryVersions nol-satu-atau-banyak

Diagram Eksposisi IDEF1X

Akibatnya, dalam rangka untuk menjelaskan saran saya berdasarkan perangkat grafis, saya telah membuat sebuah sampel IDEF1X sebuah diagram yang berasal dari aturan bisnis dirumuskan di atas dan fitur lain yang tampaknya bersangkutan. Itu ditunjukkan pada Gambar 1 :

Gambar 1 - Versi Blog Cerita Diagram IDEF1X

Mengapa BlogStory dan BlogStoryVersion dikonseptualisasikan sebagai dua jenis entitas yang berbeda?

Karena:

  • Contoh BlogStoryVersion (yaitu, "masa lalu" satu) selalu memegang nilai untuk properti UpdatedDateTime , sementara kemunculan BlogStory (yaitu, "sekarang") tidak pernah memegangnya.

  • Selain itu, entitas dari tipe-tipe tersebut secara unik diidentifikasi oleh nilai-nilai dari dua set properti yang berbeda: BlogStoryNumber (dalam kasus kemunculan BlogStory ), dan BlogStoryNumber plus CreatedDateTime (dalam kasus contoh BlogStoryVersion ).


a Definisi Integrasi untuk Information Modeling ( IDEF1X ) adalah teknik pemodelan data yang sangat dianjurkan yang didirikan sebagai standar pada Desember 1993 oleh Amerika Serikat Institut Nasional Standar dan Teknologi (NIST). Hal ini didasarkan pada materi teori awal yang ditulis oleh satu-satunya pencetus dari model relasional , yaitu, Dr EF Codd ; pada tampilan Entity-Relationship data, yang dikembangkan oleh Dr. PP Chen ; dan juga pada Teknik Desain Basis Data Logis, yang dibuat oleh Robert G. Brown.


Ilustrasi tata letak SQL-DDL yang logis

Kemudian, berdasarkan analisis konseptual yang disajikan sebelumnya, saya menyatakan desain tingkat logis di bawah ini:

-- You should determine which are the most fitting 
-- data types and sizes for all your table columns 
-- depending on your business context characteristics.

-- Also you should make accurate tests to define the most
-- convenient index strategies at the physical level.

-- As one would expect, you are free to make use of 
-- your preferred (or required) naming conventions.    

CREATE TABLE UserProfile (
    UserId          INT      NOT NULL,
    FirstName       CHAR(30) NOT NULL,
    LastName        CHAR(30) NOT NULL,
    BirthDate       DATETIME NOT NULL,
    GenderCode      CHAR(3)  NOT NULL,
    UserName        CHAR(20) NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT UserProfile_PK  PRIMARY KEY (UserId),
    CONSTRAINT UserProfile_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
        FirstName,
        LastName,
        BirthDate,
        GenderCode
    ), 
    CONSTRAINT UserProfile_AK2 UNIQUE (UserName) -- ALTERNATE KEY.
);

CREATE TABLE BlogStory (
    BlogStoryNumber INT      NOT NULL,
    Title           CHAR(60) NOT NULL,
    Content         TEXT     NOT NULL,
    CoverImageName  CHAR(30) NOT NULL,
    IsActive        BIT(1)   NOT NULL,
    AuthorId        INT      NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT BlogStory_PK              PRIMARY KEY (BlogStoryNumber),
    CONSTRAINT BlogStory_AK              UNIQUE      (Title), -- ALTERNATE KEY.
    CONSTRAINT BlogStoryToUserProfile_FK FOREIGN KEY (AuthorId)
        REFERENCES UserProfile (UserId)
);

CREATE TABLE BlogStoryVersion  (
    BlogStoryNumber INT      NOT NULL,
    CreatedDateTime DATETIME NOT NULL,
    Title           CHAR(60) NOT NULL,
    Content         TEXT     NOT NULL,
    CoverImageName  CHAR(30) NOT NULL,
    IsActive        BIT(1)   NOT NULL,
    AuthorId        INT      NOT NULL,
    UpdatedDateTime DATETIME NOT NULL,
    --
    CONSTRAINT BlogStoryVersion_PK              PRIMARY KEY (BlogStoryNumber, CreatedDateTime), -- Composite PK.
    CONSTRAINT BlogStoryVersionToBlogStory_FK   FOREIGN KEY (BlogStoryNumber)
        REFERENCES BlogStory (BlogStoryNumber),
    CONSTRAINT BlogStoryVersionToUserProfile_FK FOREIGN KEY (AuthorId)
        REFERENCES UserProfile (UserId),
    CONSTRAINT DatesSuccession_CK               CHECK       (UpdatedDateTime > CreatedDateTime) --Let us hope that MySQL will finally enforce CHECK constraints in a near future version.
);

Diuji dalam SQL Fiddle ini yang berjalan pada MySQL 5.6.

The BlogStorytable

Seperti yang Anda lihat dalam desain demo, saya telah mendefinisikan kolom BlogStoryPRIMARY KEY (PK for brevity) dengan datatype INT. Dalam hal ini, Anda mungkin ingin memperbaiki proses otomatis bawaan yang menghasilkan dan menetapkan nilai numerik untuk kolom tersebut di setiap penyisipan baris. Jika Anda tidak keberatan meninggalkan celah sesekali dalam rangkaian nilai ini, maka Anda dapat menggunakan atribut AUTO_INCREMENT , yang biasa digunakan dalam lingkungan MySQL.

Saat memasukkan semua BlogStory.CreatedDateTimetitik data individual Anda, Anda dapat memanfaatkan fungsi SEKARANG () , yang mengembalikan nilai Tanggal dan Waktu yang saat ini ada di server database pada saat operasi INSERT yang tepat. Bagi saya, praktik ini jelas lebih cocok dan kurang rentan terhadap kesalahan daripada penggunaan rutin eksternal.

Asalkan, sebagaimana dibahas dalam komentar (sekarang dihapus), Anda ingin menghindari kemungkinan mempertahankan BlogStory.Titlenilai duplikat, Anda harus menyiapkan batasan UNIK untuk kolom ini. Karena kenyataan bahwa Judul yang diberikan dapat dibagikan oleh beberapa (atau bahkan semua) BlogStoryVersions "masa lalu" , maka batasan UNIK tidak boleh dibuat untuk BlogStoryVersion.Titlekolom.

Saya menyertakan BlogStory.IsActivekolom tipe BIT (1) (meskipun TINYINT mungkin juga digunakan) jika Anda perlu menyediakan fungsionalitas DELETE "lunak" atau "logis".

Detail tentang BlogStoryVersiontabel

Di sisi lain, PK BlogStoryVersiontabel terdiri dari (a) BlogStoryNumberdan (b) kolom bernama CreatedDateTimeitu, tentu saja, menandai instan tepat di mana BlogStorybaris menjalani INSERT.

BlogStoryVersion.BlogStoryNumber, selain sebagai bagian dari PK, juga dibatasi sebagai KUNCI LUAR NEGERI (FK) yang merujuk BlogStory.BlogStoryNumber, konfigurasi yang menegakkan integritas referensial antara baris dari dua tabel ini. Dalam hal ini, BlogStoryVersion.BlogStoryNumbertidak diperlukan penerapan generasi otomatis karena, jika ditetapkan sebagai FK, nilai-nilai yang DICANTUMKAN ke dalam kolom ini harus "diambil dari" yang sudah terlampir dalam BlogStory.BlogStoryNumbermitra terkait .

The BlogStoryVersion.UpdatedDateTimekolom harus mempertahankan, seperti yang diharapkan, titik waktu ketika sebuah BlogStorybaris dimodifikasi dan, sebagai akibatnya, ditambahkan ke BlogStoryVersionmeja. Karenanya, Anda dapat menggunakan fungsi SEKARANG () dalam situasi ini juga.

The Interval dipahami antara BlogStoryVersion.CreatedDateTimedan BlogStoryVersion.UpdatedDateTimemengungkapkan seluruh Periode selama mana BlogStoryberturut-turut adalah “hadir” atau “saat ini”.

Pertimbangan untuk Versionkolom

Hal ini dapat berguna untuk dianggap BlogStoryVersion.CreatedDateTimesebagai kolom yang menyimpan nilai yang mewakili Versi “masa lalu” dari BlogStory . Saya menganggap ini jauh lebih bermanfaat daripada VersionIdatau VersionCode, karena lebih ramah pengguna dalam arti bahwa orang cenderung lebih akrab dengan konsep waktu . Misalnya, penulis atau pembaca blog dapat merujuk ke BlogStoryVersion dengan cara yang serupa dengan yang berikut:

  • “Saya ingin melihat spesifik Versi dari BlogStory diidentifikasi oleh Nomor 1750 yang Dibuat pada 26 August 2015di 9:30”.

Peran Penulis dan Editor : Derivasi dan interpretasi data

Dengan pendekatan ini, Anda dapat dengan mudah membedakan yang memegang “asli” AuthorIddari beton BlogStory memilih “awal” Versi dari tertentu BlogStoryIdDARI BlogStoryVersiontable berdasarkan menerapkan fungsi MIN () ke BlogStoryVersion.CreatedDateTime.

Dengan cara ini, setiap BlogStoryVersion.AuthorIdnilai yang terkandung dalam semua baris Versi "nanti" atau "berhasil" menunjukkan, tentu saja, pengenal Penulis dari masing-masing Versi yang ada, tetapi orang juga dapat mengatakan bahwa nilai tersebut adalah, pada saat yang sama, menunjukkan yang Peran yang dimainkan oleh terlibat Pengguna sebagai Editor dari “asli” Versi dari BlogStory .

Ya, AuthorIdnilai yang diberikan dapat dibagi oleh beberapa BlogStoryVersionbaris, tetapi ini sebenarnya adalah sepotong informasi yang mengatakan sesuatu yang sangat signifikan tentang masing-masing Versi , sehingga pengulangan kata datum tidak menjadi masalah.

Format kolom DATETIME

Adapun tipe data DATETIME, ya, Anda benar, “ MySQL mengambil dan menampilkan nilai DATETIME dalam YYYY-MM-DD HH:MM:SSformat ' ' , tetapi Anda dapat dengan percaya diri memasukkan data terkait dengan cara ini, dan ketika Anda harus melakukan kueri, Anda hanya perlu make penggunaan built-in tanggal dan waktu fungsi untuk, antara lain, menunjukkan nilai-nilai tentang dalam format yang sesuai untuk pengguna Anda. Atau Anda tentu saja dapat melakukan pemformatan data semacam ini melalui kode program aplikasi Anda.

Implikasi BlogStoryoperasi UPDATE

Setiap kali BlogStorybaris mengalami UPDATE, Anda harus memastikan bahwa nilai-nilai yang sesuai yang "hadir" sampai modifikasi terjadi kemudian dimasukkan ke dalam BlogStoryVersiontabel. Karenanya, saya sangat menyarankan untuk memenuhi operasi ini dalam satu TRANSAKSI ASAM untuk menjamin bahwa mereka diperlakukan sebagai Unit Kerja yang tidak dapat dibagi. Anda mungkin juga mempekerjakan PEMICU, tetapi mereka cenderung membuat segala sesuatunya berantakan.

Memperkenalkan kolom VersionIdatau aVersionCode

Jika Anda memilih (karena keadaan bisnis atau preferensi pribadi) untuk memasukkan kolom BlogStory.VersionIdatau BlogStory.VersionCodeuntuk membedakan BlogStoryVersions , Anda harus merenungkan kemungkinan berikut:

  1. A VersionCodedapat diminta menjadi UNIK dalam (i) seluruh BlogStorytabel dan juga dalam (ii) BlogStoryVersion.

    Oleh karena itu, Anda harus menerapkan metode yang telah diuji dengan cermat dan benar-benar andal untuk menghasilkan dan menetapkan setiap Codenilai.

  2. Mungkin, VersionCodenilai bisa diulang dalam BlogStorybaris yang berbeda , tetapi tidak pernah diduplikasi bersamaan dengan nilai yang sama BlogStoryNumber. Misalnya, Anda dapat memiliki:

    • a BlogStoryNumber 3- Versi83o7c5c dan, secara bersamaan,
    • a BlogStoryNumber 86- Versi83o7c5c dan
    • a BlogStoryNumber 958- Version83o7c5c .

Kemungkinan selanjutnya membuka alternatif lain:

  1. Menyimpan VersionNumberuntuk BlogStories, jadi mungkin ada:

    • BlogStoryNumber 23- Versi1, 2, 3… ;
    • BlogStoryNumber 650- Versi1, 2, 3… ;
    • BlogStoryNumber 2254- Versi1, 2, 3… ;
    • dll.

Memegang versi "asli" dan "berikutnya" dalam satu tabel

Meskipun mempertahankan semua BlogStoryVersions di tabel dasar individu yang sama adalah mungkin, saya sarankan untuk tidak melakukannya karena Anda akan mencampur dua jenis fakta (konseptual) yang berbeda, yang dengan demikian memiliki efek samping yang tidak diinginkan pada

  • kendala dan manipulasi data (pada tingkat logis), bersama dengan
  • pemrosesan dan penyimpanan terkait (pada tingkat fisik).

Tetapi, dengan syarat Anda memilih untuk mengikuti tindakan itu, Anda masih dapat memanfaatkan banyak gagasan yang dirinci di atas, misalnya:

  • sebuah komposit PK yang terdiri dari kolom INT ( BlogStoryNumber) dan kolom DATETIME ( CreatedDateTime);
  • penggunaan fungsi server untuk mengoptimalkan proses terkait, dan
  • Peran yang dapat diturunkan dari Penulis dan Editor .

Melihat bahwa, dengan melanjutkan pendekatan semacam itu, suatu BlogStoryNumbernilai akan digandakan segera setelah Versi "yang lebih baru" ditambahkan, opsi yang dan yang dapat Anda evaluasi (yang sangat mirip dengan yang disebutkan di bagian sebelumnya) adalah membangun sebuah BlogStoryPK. terdiri dari kolom BlogStoryNumberdan VersionCode, dengan cara ini Anda akan dapat secara unik mengidentifikasi setiap Versi dari BlogStory . Dan Anda dapat mencoba dengan kombinasi BlogStoryNumberdan VersionNumberjuga.

Skenario serupa

Anda dapat menemukan jawaban saya untuk pertanyaan bantuan ini, karena saya juga mengusulkan kemampuan sementara yang memungkinkan dalam database terkait untuk menangani skenario yang sebanding.

MDCCL
sumber
2

Salah satu opsi adalah menggunakan Versi Normal Form (vnf). Keuntungannya termasuk:

  • Data saat ini dan semua data masa lalu berada di tabel yang sama.
  • Kueri yang sama digunakan untuk mengambil data saat ini atau data yang saat ini pada tanggal tertentu.
  • Referensi kunci asing untuk data berversi berfungsi sama dengan untuk data tidak berversi.

Manfaat tambahan dalam kasus Anda, karena data versi diidentifikasi secara unik dengan menjadikan tanggal efektif (tanggal perubahan dibuat) bagian dari kunci, bidang version_id yang terpisah tidak diperlukan.

Berikut ini penjelasan untuk tipe entitas yang sangat mirip.

Rincian lebih lanjut dapat ditemukan di presentasi slide di sini dan dokumen yang tidak lengkap di sini

TommCatt
sumber
1

Hubungan Anda

(story_id, version_id, editor_id, author_id, timestamp, judul, konten, coverimg)

tidak dalam bentuk normal ke-3. Untuk setiap versi cerita Anda, author_id adalah sama. Jadi, Anda perlu dua hubungan untuk mengatasi ini

(story_id, author_id)
(story_id, version_id, editor_id, timestamp, judul, konten, coverimg)

Kunci dari relasi pertama adalah story_id, kunci relasi kedua adalah kunci gabungan (story_id, version_id). Jika Anda tidak suka tombol gabungan maka Anda hanya dapat menggunakan version_idsebagai kunci

keajaiban173
sumber
2
Ini sepertinya tidak menyelesaikan masalah saya, itu hanya menekankan mereka
Victor
Jadi itu bahkan tidak menjawab nilai author_id bidang kueri yang berulang di setiap setiap baris tabel. Di mana dan bagaimana saya harus menyimpannya ?
miracle173
2
Saya tidak begitu mengerti apa yang dinyatakan oleh jawaban Anda. Mungkin karena saya bukan penutur asli bahasa Inggris, jadi bisakah Anda mencoba menjelaskannya dengan kata-kata yang lebih sederhana?
Victor
Ini berarti bahwa Anda harus menghindari pengulangan nomor author_id (jika story_id sama untuk dua baris, author_id mereka juga sama) dan membagi tabel Anda dalam dua tabel seperti yang dijelaskan dalam posting saya. Jadi, Anda dapat menghindari pengulangan author_id.
miracle173