10. april 2003 - 20:37Der er
59 kommentarer og 2 løsninger
Fleksibel database-design
Jeg er ved at designe en database til et artikel-system. Nu er jeg nået dertil hvor jeg skal kategorisere artiklerne. Designet skal overholde tre regler.
Regel 1: en kategori kan have flere under- og overkategorier. Et eks.:
- Styresystemer - Microsoft Windows - Windows XP Home - Windows XP Professional - Windows 2000 Professional - Windows NT Workstation - Linux - ... - Scripting - Server-Side - ASP - ASP.NET - PHP - ... - ... - Client-Side - JavaScript - VBScript - Databaser - Cat - Cat - Cat - Cat - Cat - Cat - Cat - Cat - Cat
Regel 2: en artikel kan have flere kategorier. Regel 3: en artikel skal "arve" de underliggende kategorier. F.eks. skal alle artikler i VBScript-kategorien også være medlem af Scripting-kategorien.
Spørgsmållet er nu: Hvordan laver jeg et fleksibel design som overholder alle de 3 regler? Jeg har allerede nu lavet et design der overholder de første to regler. Regel nr. 3 skal jeg programmere mig ud af.
Mit nuværende design består grundlæggende af tre tabeller: tblCategories : her registreres kategorierne tblArticles: indeholder alle artikler tblArticleCategoryLink: relationen mellem artikler og kategorier
tblCategories ------------- CategoryID ParentCategory CategoryName 1 0 Styresystemer 2 1 Microsoft Windows 3 2 Windows 2000 Professional 4 2 Windows XP Home 5 2 Windows XP Professional 6 0 Scripting 7 6 Server-Side 8 7 ASP 9 6 Client-Side 10 7 JavaScript
tblArticles ---------- ArticleID HeadText Body 1 Bla bla Bla bla 2 Bla bla Bla bla 3 Bla bla Bla bla 4 Bla bla Bla bla
1 Styresystemer 1.1 Microsoft Windows 1.1.1 Windows 2000 Professional 1.1.2 Windows XP Home 1.1.3 Windows XP Professional 2 Scripting 2.1 Server-Side 2.1.1 ASP 2.2 Client-Side 2.2.1 JavaScript
Ideen med parent felt er nok den mest relationelle.
Men jeg er bange for at du dør i mystiske self joins i applikationen.
Jeg kan se flere alternativer som *muligvis* er mere praktiske.
Først tror jeg du skal starte med at vedtage at artikler kun kan hænge på en kategori i nederste niveau.
Hvis du så er villig til at definere at der er et fast antal niveauer af kategorier f.eks. 3, så kan du koble ArticleID med CategoryID_level1 + CategoryID_level2 + CategoryID_level3, droppe parent kolonnen og lade din applikation sørge for korrekt match mellem niveauerne.
Og så er det pludseligt nemt at søge på alle 3 niveauer.
Hvis du ikke bruge fast antal niveauer så synes jeg stadigvæk at du skal droppe parent kolonnen og i.s.f. lave en tabel med kategori struktur og så lade din applikation håndetere det ved at konvertere en SELECT WHERE CategoriName= til en SELECT WHERE CategoriName IN SELECT WHERE.
// find nummer i kategori X# = SELECT CategoryID FROM tblCategories WHERE CategoryName=X
// find underkategorier array xs = SELECT ChildCategory FROM tblCategoryStructure WHERE ParentCategory=X#
// test om der er underkategorier if elements(xs)==0 then
// find artikler array as = SELECT ArticleID FROM tblArticleCategoryLink WHERE CategoryID=X#
else
// find artikler array as = SELECT ArticleID FROM tblArticleCategoryLink WHERE CategoryID IN (SELECT ChildCategory FROM tblCategoryStructure WHERE ParentCatelofy = X#)
arne_v>>> Dit forslag holder altså ikke. Nu har du overbevist somaliomar om at han skal have et net frem for et træ. Ideen med at benytte parent i selve kategoritabellen er klart den mest rationelle. Det andet giver redundans og kan føre til løkker i kategoristrukturen, som man skal tage specielt højde for. Til gengæld er der intet vundet ved den.
Tilbage til det oprindelige forslag - det fungerer helt fint.
Hvis performance bliver et problem, kan man efterfølgende lave nogle tricks med at hælde afslutninger i en separat tabel som netop svarer til arnes. F. eks.:
Med ovenstående tabel kan man få alle forældre til en given kategori ud med en enkelt forespørgsel. Man kan også få alle børn til en given kategori. Ulempen er at man skal vedligeholde den separat, men det er efter min mening klart den bedste løsning.
Jeg har besluttet mig for at anvende erikjacobsen's forslag, som hidtil er det bedste, hvis der ikke er flere løsninger på problemet. Men jeg lader lige spørgsmållet stå åben lidt endnu.
alle artikler om styresystemer? I mit forslag er der også en unik id pr. linie (nemmere end at referere til fx 1.1.2, hvis man en dag laver om i rækkefølgen).
Så skal man bare "select .. from kategorier where type like '1.%'" (man skal nok kalde kategoriernbe 1.0 1.1.0 osv) - og det resultat bruger man til at finde artiklerne i deres database).
Sortering kan ikke umiddelbart foregå i kategorilisten - det kan ske i programmeringssproget, eller hvis man laver det 001.001.003 i stedet for (hvilket dog sætter en øvre grænse for antal kategorier på et givet niveau).
Men det er bare et udkast inspireret af et system jeg engang havde fingrene dybt nede i - der er fordele og ulemper...
arne_v>>> Du spørger til et select-statement som henter alle artikler for en given kategori med underkategorier? Det er et join imellem min afslutningstabel og tabellen med artikler. Hvis du vil have et konkret eksempel, så sig til.
Iøvrigt er eriks idé også fin, men der kommer nogle sjove problemer hvis man gerne vil flytte en underkategori fra et sted i kategoritræet til et andet.
nih>>> Jeg kan ikke helt se hvordan dit forslag kan opfylde f. eks. 4. normalform?
arne_v>>> ved nærmere eftertanke har vi vidst stort set fundet samme løsning. Undskyld hvis jeg hang dig ud. Jeg synes dog at det er vigtigt at man har et parent-felt i kategori-tabellen, som så benyttes som de "primære data". Afslutningstabellen skal blot ses som et hjælpemiddel.
Hvis kun benytter afslutningstabellen (som primær datakilde) kan man komme til at indsætte værdier som laver løkker i strukturen og det vil i mange tilfælde være fatalt.
Nu har jeg fået nogle brand-gode inputs, som jeg kan arbejde videre med. Jeg går i tænkeboksen igen og ser om jeg selv kan føje noget til jeres forslag. Jeg vender tilbage, når jeg har fundet en løsning der passer mig bedst.
Nu tror jeg, at jeg har fundet en løsning på problemet. Hvis min tabel ser sådan ud skulle alle mine problemer være løst:
tblCategories ------------- ID CatTree CategoryName 1 1 Styresystemer 2 1.2.2 Microsoft Windows 3 1.2.3 Windows 2000 Professional 4 1.2.4 Windows XP Home 5 1.2.5 Windows XP Professional 6 6 Scripting 7 6.7 Server-Side 8 6.7.8 ASP 9 6.9 Client-Side 10 6.9.10 JavaScript 11 6.9.11 VBScript 12 1.2.12 Windows 98 Second Edition 13 1.2.13 Windows 95 14 6.7.14 JSP 15 6.7.15 ColdFusion
Hvad siger I? Kan I finde begrænsninger i den måde jeg registere kategorierne på?
Hehehe... Nu kan jeg se hvad du mener. Hvad så hvis jeg laver det sådan her:
ID CatTree CategoryName 1 1. Styresystemer 2 1.2.2. Microsoft Windows 3 1.2.3. Windows 2000 Professional 4 1.2.4. Windows XP Home 5 1.2.5. Windows XP Professional 6 6. Scripting 7 6.7. Server-Side 8 6.7.8. ASP 9 6.9. Client-Side 10 6.9.10. JavaScript 11 6.9.11. VBScript 12 1.2.12. Windows 98 Second Edition 13 1.2.13. Windows 95 14 6.7.14. JSP 15 6.7.15. ColdFusion
_darkstar_ >> Jeg kan ikke lige se, hvordan det skulle være svært at flytte en kategori til et andet sted i hierakiet. Hvis jeg f.eks. skal flytte kategorien "Windows 2000 Professional" til toppen i hierakiet, skal jeg ændre CatTree-værdien til "3."
arne_v + somaliomar>>> Faktisk kan det blive meget værre.
Hvis du opdager at du gerne vil flytte hele kategorien "Client-Side" fra "Scripting" til "Styresystemer", så er der ballade.
ID CatTree CategoryName 9 6.9. -> 1.3 Client-Side 10 6.9.10. -> 1.3.1 JavaScript 11 6.9.11. -> 1.3.2 VBScript 12 1.2.12. -> 1.3.3 Windows 98 Second Edition ...
Nu skriver du jo også at f. eks. JavaScript som er den første underkategori under Client-Side har nummeret 10 (6.9.>10<) - det kan også give dig nogle spændende udfordringer at holde regnskab med hvilke tal, der er optaget.
Jeg synes stadgivæk at løsningen er god nok, men man skal altså være opmærksom på at man kommer til at skrive en del ekstra "bogholderi"-kode efterfølgende.
nih>>> Det ville belyse din idé væsentligt bedre hvis du kunne komme med et udkast til hvad dine tabeller kunne indeholde. Jeg kan stadigvæk ikke se hvordan man kan få noget meningsfuldt ud af en tabel med både et "parent" og et "mother"-felt. Hvis du ser på min afslutningetabel er der jo ikke noget 1-1 forhold til kategorierne. Det ser det ud til at du antager at der er og det er et problem.
Den motherId er egentligt et beregnet felt der skal vedligeholdes og som du siger ikke godt for normaliseringen - men for performance (glem den til du for problemmer med load tider.)
Det kræver også diciplin at 'noder' ikke bliver væk - altså har en parentID der ikke eksistere som nodeid.
Mothernodeid - som jeg bruger det referer til den første node i træet. Så kan man 'nøjes' med at loade 'rootnoderne' først og loade childnoderne når brugeren klikker på træet.
SELECT tblNode.Title, tblNode_1.Title, tblNode_2.Title FROM (tblNode LEFT JOIN tblNode AS tblNode_1 ON tblNode.Nodeid = tblNode_1.parrentId) LEFT JOIN tblNode AS tblNode_2 ON tblNode_1.Nodeid = tblNode_2.parrentId WHERE tblNode.Title Client-Side
nih>>> ...men din løsning virker så ikke med vilkårligt mange niveauer - det gør arne_v's og min. Desuden kan jeg ikke se at du bruger motherID til noget.
_darkstar_ >> Jeg tror ikke, at du helt har forstået, hvad det er jeg registrerer i feltet CatTree. Sådan ser skabelonen ud:
ParentCatID.ParentCatID.ParentCatID...CatID.
Når f.eks. kategorien "JavaScript" har CatTree-værdien "6.9.10." betyder det:
"6." = kategorien hører ind under "Scripting" som har ID-nummeret 6 "9." = kategorien hører ind under "Client-Side" som har ID-nummeret 9 "10." = selve kategorien som har ID-nummeret 10
Hvis jeg nu tager dit eksempel og skal flytte hele kategorien "Client-Side" fra "Scripting" til "Styresystemer" gør sådan her:
Men jeg er helle rikke så bekymret for det. Fordi mit gæt er at antallet af kategorier vil være så lille og antallet af struktur ændringer så sjældne at: - db offline - backup - manuelle SQL UPDATE statements - db online er OK.
Hvis der var hundretusinder af kategorier og det skulle være nemt at ændre struktur, så ville det kræve lidt kode.
somaliomar>>>> Jeg er helt med. Det er stadigvæk noget pilleri, som kunne gøres med en update af blot én række (og efterfølgende opdatering af afslutningstabellen) hvis man benytter den løsning som arne og jeg har foreslået.
jeg synes alligevel at det er en god løsning. Det eneste som jeg skriver er, at du skal være opmærksom på at man skal skrive noget kode som kan udføre den slags operationer.
_darkstar_ >> Der er fordele og ulemper i Erikjacobsens og jeres forslag. Jeg foretrækker nu Erikjacobsens forslag, da jeg synes at metoden er simpel og overskueligt.
nih, darkstar, arne_v, erikjacobsen >> Gider I lige smide jeres svar. Jeg lukker spørgsmålet nu. Jeg takker mange gange for de gode inputs og ikke mindst jeres tid. Jeg er blevet meget klogere :D
Jeg behøves ikke point. Desuden har du åbenbart ikke valgt min løsning.
Jeg har dog en telefonliste, hvor fimaets afdelinger er vist i et treeview på den måde jeg har beskevet - og den kan indeholde uendeligt niveauer og rootnoder. Hvis du f.eks pludselig vil flytte en node skal du ændre alle de underliggende catTree værdier, hvor jeg kan nøjes med at ændre ParentNode.
nih >> Det er rigtigt. Men at give dig nogle points er da det mindste jeg kan gøre som tak for dit bidrag. Siger du nej til det, er det bare helt i orden :)
erikjacobsen >> Jeg går ud fra, at du ikke skal have nogle points!??
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.