Dezbaterea despre proiect: care sunt modalitățile bune de a stoca și manipula obiecte versate?

Încep intenționat acest lucru destul de vag la început. Caut o discuție și ce probleme sunt importante mai mult decât căut răspunsuri grele.

Sunt în mijlocul proiectării unei aplicații care face ceva de genul gestionării portofoliului. Design-ul pe care îl am până acum

  • Problem: a problem that needs to be solved
  • Solution: a proposed solution to one or more problems
  • Relationship: a relationship among two problems, two solutions, or a problem and a solution. Further broken down into:
    • Parent-child - some sort of categorization/tree hierarchy
    • Overlap - the degree to which two solutions or two problems really address the same concept
    • Addresses - the degree to which a problem addresses a solution

Întrebarea mea este despre natura temporală a acestor lucruri. Problemele creează, apoi se estompează. Soluțiile au o dată de rezolvare așteptată, dar pot fi modificate în momentul în care acestea sunt dezvoltate. Gradul unei relații s-ar putea schimba în timp, pe măsură ce problemele și soluțiile vor evolua.

Deci, întrebarea: care este cel mai bun design pentru versiunea acestor lucruri, pentru a putea obține o perspectivă atât curentă, cât și istorică a portofoliului meu?

Mai târziu: poate că ar trebui să fac o întrebare mai specifică, deși răspunsul lui @ Eric Beard merită un răspuns.

Am analizat trei modele de baze de date. Voi suficient de fiecare pentru a arăta dezavantajele lor. Întrebarea mea este: care să alegi sau să te gândești la ceva mai bun?

1: Probleme (și separat, Soluții) sunt auto-referențiale în versiune.

table problems
  int id | string name | text description | datetime created_at | int previous_version_id

  foreign key previous_version_id -> problems.id

Acest lucru este problematic deoarece de fiecare dată când vreau o nouă versiune, trebuie să repet întregul rând, inclusiv acea coloană lungă description .

2: Creați un nou tip de relație: Versiune.

table problems
  int id | string name | text description | datetime created_at

Acest lucru muta pur și simplu relația din tabelele Probleme și soluții în tabelul Relații. Aceeași problemă de duplicare, dar poate un pic mai "curat", deoarece am deja un concept de relație abstractă.

3: Folosiți o structură asemănătoare cu Subversion; mutați toate atributele Problemă și Soluție într-un tabel separat și le versifli.

table problems
  int id

table attributes
  int id | int thing_id | string thing_type | string name | string value | datetime created_at | int previous_version_id

  foreign key (thing_id, thing_type) -> problems.id or solutions.id
  foreign key previous_version_id -> attributes.id

Aceasta înseamnă că pentru a încărca versiunea curentă a unei probleme sau soluții, trebuie să preiau toate versiunile atributului, să le sortez după dată și apoi să utilizez cea mai recentă. Asta nu poate fi teribil. Ceea ce mi se pare foarte rău este că nu pot verifica aceste atribute în baza de date. Această coloană value trebuie să fie text liber. Pot face ca coloana name să fie o referință într-un tabel distinct attribute_names care are o coloană type /em> tipul corect din tabelul attributes .

mai târziu încă: răspuns la comentariile lui @Eric Beard despre cheile externe cu mai multe tabele:

Din păcate, ceea ce am descris este simplist: există doar două tipuri de Lucruri (Probleme și Soluții). Am de fapt aproximativ 9 sau 10 tipuri diferite de Lucruri, așadar aș avea 9 sau 10 coloane de chei străine sub strategia ta. Am vrut să folosesc moștenire cu o singură masă, dar Lucrurile nu au atât de puține lucruri comune încât ar fi extrem de risipitoare să le combine într-o singură masă.

0
fr hi bn

4 răspunsuri

Hmm, sună cam ca acest site ...

În ceea ce privește proiectarea unei baze de date, un sistem de tipărire asemănător cu SVN, unde nu faceți niciodată actualizări, doar inserții (cu un număr de versiune) atunci când lucrurile se schimbă, ar putea fi ceea ce aveți nevoie. Acest lucru se numește MVCC, controlul concurenței multiple. Un wiki este un alt exemplu bun al acestui lucru.

0
adăugat

@Gaius

foreign key (thing_id, thing_type) -> problems.id or solutions.id

Aveți grijă cu aceste chei externe "multidirecționale". Experiența mea a demonstrat că performanța interogării suferă dramatic atunci când condiția de conectare trebuie să verifice tipul înainte de a determina ce tabelă să se alăture. Nu pare atât de elegantă, dar de nul

problem_id and solution_id 

va funcționa mult mai bine.

Desigur, performanța interogării va suferi și un design MVCC atunci când trebuie să adăugați cecul pentru a obține cea mai recentă versiune a unei înregistrări. Compromisul este că nu trebuie să vă faceți griji cu privire la disputa cu actualizări.

0
adăugat
Votați ca acele chei străine care merg la diferite mese, astfel încât să confundați optimizatorul. Este, de asemenea, rău când ștergeți o înregistrare părinte și pentru RI.
adăugat autor WW., sursa

Presupun că există

Opțiunea 4: hibridul

Deplasați atributele comune ale unui lucru într-o tabelă cu o singură moștenire, apoi adăugați un tabel custom_attributes . Acest lucru simplifică cheile externe, reduce duplicarea și permite flexibilitate. Nu rezolvă problemele de siguranță de tip pentru atributele suplimentare. De asemenea, se adaugă o mică complexitate, deoarece există două modalități ca un lucru să aibă un atribut acum.

Însă, dacă description și alte câmpuri mari rămân în tabelul Things, de asemenea, nu rezolvă problema spațiului de duplicare.

table things
  int id | int type | string name | text description | datetime created_at | other common fields...
  foreign key type -> thing_types.id

table custom_attributes
  int id | int thing_id | string name | string value
  foreign key thing_id -> things.id
0
adăugat

Este o idee bună să alegeți o structură de date care să facă întrebările frecvente pe care le cereți modelului ușor de răspuns. Este cel mai probabil că sunteți interesat de poziția actuală, de cele mai multe ori. În mod ocazional, veți dori să căutați în istorie pentru probleme și soluții particulare.

Aș avea tabele pentru probleme, soluții și relații care reprezintă poziția actuală. Ar fi, de asemenea, un tabel problem_history , solution_history etc. Acestea ar fi tabelele de probleme ale copiilor, dar vor conține, de asemenea, coloane suplimentare pentru VersionNumber și EffectiveDate . Cheia ar fi ( ProblemId , VersionNumber ).

Când actualizați o problemă, ați scrie valorile vechi în tabela problem_history . Punctul în interogări de timp este, prin urmare, posibil deoarece puteți selecta înregistrarea problem_history care este valabilă ca la o anumită dată.

În cazul în care am făcut acest lucru înainte, am creat, de asemenea, o vizualizare pentru UNION problem și problem_history , deoarece uneori este utilă în diverse interogări.

Opțiunea 1 face dificilă interogarea situației actuale, deoarece toate datele dvs. istorice sunt amestecate cu datele dvs. actuale.

Opțiunea 3 va fi rău pentru performanța interogării și va fi nedrept să codificați în timp ce veți accesa o mulțime de rânduri pentru ceea ce ar trebui să fie doar o simplă interogare.

0
adăugat