Avatar billede eikhorsholm Nybegynder
14. oktober 2008 - 18:36 Der er 13 kommentarer

MySQL træ-struktur med foreign keys og tilhørende PHP function.

Jeg har en MySQL-tabel:

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";

CREATE TABLE `pages` (
  `id` int(6) NOT NULL auto_increment,
  `parent` int(6) default NULL,
  `title` varchar(255) default NULL,
  `content` longtext,
  PRIMARY KEY  (`id`),
  KEY `parent` (`parent`),
  FOREIGN KEY (`parent`) REFERENCES `pages` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=9 ;

INSERT INTO `pages` VALUES(1, NULL, 'Velkommen', NULL);
INSERT INTO `pages` VALUES(2, NULL, 'Lidt om mig selv', NULL);
INSERT INTO `pages` VALUES(3, NULL, 'CV', NULL);
INSERT INTO `pages` VALUES(4, NULL, 'Kontakt', NULL);
INSERT INTO `pages` VALUES(5, 1, 'En underside til Velkommen', NULL);
INSERT INTO `pages` VALUES(6, 3, '2001', NULL);
INSERT INTO `pages` VALUES(7, 3, '2005', NULL);
INSERT INTO `pages` VALUES(8, 3, '2008', NULL);

Og derefter en PHP som skriver siderne ud i træstruktur:

function print_pages($parent = NULL) {
    echo '<ul>';
    if($parent == NULL) {
        $pages_query = mysql_query("SELECT * FROM pages WHERE parent IS NULL");
    } else {
        $pages_query = mysql_query("SELECT * FROM pages WHERE parent = '$parent'");
    }
    while($page = mysql_fetch_array($pages_query)) {
        echo '<li>'. $page['title'] .' | <a href="admin.php?delete='. $page['id'] .'">Delete</a></li>';
        echo '<li>'. print_pages($page['id']) .'</li>';
    }
    echo '</ul>';
}

print_pages();

Men den laver nogle gange nogen tomme <li></li> osv. Er der nogen som kan se om alt min kode osv. er optimalt, da jeg meget gerne vil have et solidt "fundament" inden jeg fortsætter.
Avatar billede majbom Novice
14. oktober 2008 - 19:52 #1
du kalder funktionen "print_pages" inde i sig selv..?
Avatar billede eikhorsholm Nybegynder
14. oktober 2008 - 20:10 #2
Yup, det er en rekursive funktion.
Avatar billede majbom Novice
14. oktober 2008 - 21:01 #3
nååh ja, det kan jeg da godt se nu... :)

men du kan ikke se dig ud af hvorfor den laver tomme?

har du et link til outputtet?
Avatar billede pidgeot Nybegynder
15. oktober 2008 - 10:13 #4
Problemet er vel at du kalder print_pages uanset om der er nogle underpunkter eller ej, men du skriver alligevel en <ul> ud (og sådan en må ikke være tom - der skal være en <li> inde i den). Det er sandsynligvis derfor du oplever problemet

Lav din funktion om så du checker antallet af børn inden du skriver <ul></ul> ud (eller check inden du kalder dig selv igen - dit valg).

(En bedre databasestruktur til hierarkiske data ville i øvrigt være nested sets, men det er så lidt en sidebemærkning: http://dev.mysql.com/tech-resources/articles/hierarchical-data.html)
Avatar billede eikhorsholm Nybegynder
15. oktober 2008 - 17:14 #5
pidgeot -> Det vil generelt være tabeller med omkring 20-50 rækker i. Så tænker at nested sets er lidt "overkill"?)

Jeg har prøvet, at ændre koden til:

function print_pages($parent = NULL) {
    echo '<ul>';
    if($parent == NULL) {
        $pages_query = mysql_query("SELECT * FROM pages WHERE parent IS NULL");
    } else {
        $pages_query = mysql_query("SELECT * FROM pages WHERE parent = '$parent'");
    }
    while($page = mysql_fetch_array($pages_query)) {
        echo '<li>'. $page['title'] .' | <a href="admin.php?delete='. $page['id'] .'">Delete</a></li>';
        $subpages_query = mysql_query("SELECT id FROM pages WHERE parent = '$page[id]'");
        if (0 < mysql_num_rows($subpages_query)) {
            echo "<li>";
            print_pages($page['id']);
            echo "</li>";
        }
    }
    echo '</ul>';
}

Men det virker stadig ikke helt.

Og ændret tabellen til:

CREATE TABLE `pages` (
  `id` int(6) NOT NULL auto_increment,
  `parent` int(6) default NULL,
  `title` varchar(255) default NULL,
  `content` longtext,
  PRIMARY KEY  (`id`),
  KEY `parent` (`parent`),
  FOREIGN KEY (`parent`) REFERENCES `pages` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=9 ;
Avatar billede eikhorsholm Nybegynder
15. oktober 2008 - 17:18 #6
Hvad er egentlig korrekt her?

<ul>
  <li>side1</li>
  <li>
    <ul>
      <li>underside1</li>
    </ul>
  </li>
</ul>

eller


<ul>
  <li>side1</li>
  <ul>
    <li>underside1</li>
  </ul>
</ul>

for hvis det sidste er korrekt så virker den vistnok.
Avatar billede majbom Novice
15. oktober 2008 - 19:23 #7
dit sidste eksempel er korrekt
Avatar billede eikhorsholm Nybegynder
16. oktober 2008 - 11:06 #8
ok, super. Så burde det virke :-) TAK!
LAver I ikke begge et svar, så fordeler jeg point?
Avatar billede majbom Novice
16. oktober 2008 - 11:16 #9
de går til pidgeot denne gang :)

og velbekomme
Avatar billede pidgeot Nybegynder
16. oktober 2008 - 17:41 #10
Værsgo.

Om nested sets er overkill - det kommer jo an på hvordan man ser på det. Din nuværende struktur er sikkert hurtig nok et godt stykke ud i fremtiden - selvom menuer jo normalt er noget der skal genereres ret tit - men til gengæld kunne nested sets måske gøre din PHP-kode mere læsevenlig.

Dermed ikke sagt at nested sets altid er det jeg ville vælge, da det trods alt er en smule mere komplekst at forholde sig til. Tidligere på året skulle jeg lave en ASP.NET-baseret menu hvor det var fastlagt at vi kun havde overskrifter og menupunkter (dermed kun 2 niveauer - hovedniveauet og underniveauet, og det var kun underniveauet der var egentlige links). Samtidigt skulle vi ikke have nogle "fold ud/ind"-funktionalitet.

I den situation lavede jeg en tabelstruktur der lød id, text, link, parent, sortvalue, og bestemte at sortvalue ville køre på et globalt niveau, så en enkelt query med ORDER BY sortvalue ville hente hele menuen ud i den ønskede rækkefølge. I min kode kunne jeg så bestemme om det skulle formateres som en overskrift eller et menupunkt, ud fra om parent var NULL. Dertil kom et administrationsinterface, samt et par stored procedures til at foretage oprettelse, ændring af rækkefølge, samt sletning, så der ikke var huller i sortvalue.

Det var måske en lidt fusket måde at gøre det på, men det resulterede i meget læsevenlig kode. Tricket mister i høj grad sin værdi hvis man har flere niveauer end 2 (det vil enten kræve redundans i tabellen, eller queries til at beregne niveauet), men da det ikke var et problem, virkede det som det bedste valg i min situation :)
Avatar billede majbom Novice
16. oktober 2008 - 19:27 #11
-> pidgeot - det er faktisk meget sjovt; jeg tænkte på at dét (nested sets) ville være en smart løsning, men har aldrig hørt om det før, det var bare måden jeg ville gøre det på. jeg havde så ikke lige tænkt på alle de lettere avancerede ting der kommer senere i artiklen du linker til, men selve tabel-strukturen var lige efter mit hovede :)

jeg havde bare lidt svært ved at forklare det, hvorfor ved jeg ikke, da det ser rimelig simpelt ud når jeg læser denne artikel :)
Avatar billede pidgeot Nybegynder
16. oktober 2008 - 19:37 #12
Hehe :)

Husk at mysql_query kun kan klare et statement af gangen. Du bliver nødt til enten at lave en stored procedure eller dele op med flere kald til mysql_query - eller gå over til mysqli, hvilket også giver dig mulighed for parameteriserede queries.
Avatar billede majbom Novice
04. november 2010 - 10:46 #13
kan vi lukke hér?
Avatar billede Ny bruger Nybegynder

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.

Loading billede Opret Preview
Kategori
Vi tilbyder markedets bedste kurser inden for webudvikling

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester