27. Bases de dades relacionals
Índex
Consultes sobre una base de dades relacionals
Cal començar amb una obvietat: per tal de consultar amb profit una base de dades relacional, cal que aquest estigui ben construïda. En particular, cal que la taula secundària tingui un índex secundari que coincideixi amb un índex primari de la taula principal.
Vegem per exemple l'estructura de la base de dades que farem servir en els exemples següents:

Per a cada Municipi hi ha una clau secundària, IdCom, que ha de coincidir amb el valor de la clau primària Id corresponent a la seva Comarca en la taula Comarques.
Les formes de consultar una base de dades relacional són molt diverses, però cal distingir-hi dos supòsits bàsics diferents:
- Les consultes sobre les diverses taules són relacionades però separades. En aquest cas n'hi ha prou amb els recursos vistos en els temes anteriors, amb l'única diferència que hi entren en joc les coincidències entre la clau primària i la secundària.
- La consulta llegeix conjuntament dues taules. En aquest cas fa falta un nou recurs, INNER JOIN ... ON ... .
Consultes relacionades però separades
Consultes separades significa qu en cada moment presentem dades d'una mateixa taula; relacionades significa que en la taula primària llegim el valor de la clau primària d'un registre i el fem servir com valor desitjat per a la clau secundària en la taula secundària, o al revés.
En l'exemple presentat abans hi ha aquestes dues possibilitats bàsiques:
- En la taula primària (comaques) llegim el valor del camp Id d'una comarca; i en la secundària (municipis) cerquem tots els registres en què IdCom tingui aquest valor.
- En la taula secundària (municipis) llegim el valor del camp IdCom d'un municipi; i en la primària (comarques) cerquem els registres en què Id tingui aquest valor.
Naturalment, en passar per cada taula recollim també els valors de tots els camps necessaris per al nostre propòsit.
- Dues taules MySQL anomenades comarques i municipis (vegeu l'esquema anteror).
- ...
- $dades = mysql_query("SELECT * FROM comarques ORDER BY Comarca") or
- die("Problemes de lectura"); // Obtenció de la llista de totes les comarques
- while($fila = mysql_fetch_array($dades)){
- echo "<a href=27c.php?com=" . $fila['Id'] . ">" . $fila['Comarca'] . "</a><br />"; // Presentació de la llista en forma d'enllaços
- }
- ...
- ...
-
- $c = $_GET['com']; // Rebem la variable tramesa amb l'enllaç
-
- $dades = mysql_query("SELECT * FROM comarques WHERE Id = $c") or
- die("Problemes de lectura"); // Recerca a la taula 'comarques'
- $fila = mysql_fetch_array($dades); // Sense 'while', perquè només n'hi ha un
-
- echo "<blockquote><p style='font-weight: bold; text-decoration: underline'>" . $fila['Comarca'] . "</p></blockquote>"; // Presentació de les dades
- echo "<p><span>Situació: </span>" . $fila['Situacio'] . "</p>";
- echo "<p><span>Relleu: </span>" . $fila['Relleu'] . "</p>";
- echo "<p><span>Hidrografia: </span>" . $fila['Hidrografia'] . "</p>";
-
- ...
-
- $dades_m = mysql_query("SELECT * FROM municipis WHERE IdCom = $c ORDER BY Municipi") or
- die("Problemes de lectura"); // Recerca concordant a la taula 'municipis'
- while($fila = mysql_fetch_array($dades_m)){ // Llista de tots els municipis de la comarca
- echo "<a href=27d.php?mun=" . $fila['Id'] . ">" . $fila['Municipi'] . "</a><br />"; // Presentació en forma d'enllaços
- }
- ...
- ...
-
- $c = $_GET['mun']; // Rebem el codi del municipi desitjat
-
- ...
- $dades = mysql_query("SELECT * FROM municipis WHERE Id = $c") or
- die("Problemes de lectura"); // Fem la recerca en la taula 'municipis'
- $fila = mysql_fetch_array($dades); // Sense 'while' perquè només n'hi ha un
-
- echo "<blockquote><p style='font-weight: bold; text-decoration: underline'>" . $fila['Municipi'] . "</p></blockquote>"; // Presentació de les dades del municipi destjat
- echo "<p><span>Habitants: </span>" . $fila['Habitants'] . "</p>";
- echo "<p><span>Monuments: </span>" . $fila['Monuments'] . "</p>";
- ...
Vegeu-ne el resultat.
Consultes conjuntes
El procediment usat en una consulta conjunta és molt diferent. Comencem establint totes les parelles possibles entre els registres d'una taula i els de l'altra. Aquest aparellament és conegut tècnicament com producte cartesià, i s'aconsegueix amb la clàusula INNER JOIN:
- taula_1 INNER JOIN taula_2
El producte cartesià per si sol pot tenir sentit o no tenir-ne. Suposem una taula amb els noms dels nois presents en una sala de ball, i una altra amb els noms de totes les noies presents en la mateixa sala. El producte cartesià ens indica totes les parelles de ball possibles. Però si dues taules tenen una relació 1:n, com és el cas de les taules de comarques i de municipis, el producte cartesià no té cap sentit; és només una primera fase d'un conjunt que cal acotar mitjançant la coincidència entre la clau primària de la taula 1 (comarques) i la secundària de la taula n (municipis). Per a això es fa servir la clàusula ON:
- ON clau_primària_taula_1=clau_secundària_taula_2
En definitiva tenim:
- SELECT camps FROM taula_1 INNER JOIN taula_2
ON clau_primària_taula_1=clau_secundària_taula_2
Sovint succeeix que els camps de dues taules diferents tenen el mateix nom; en aquests casos cal desfer l'ambigüitat designant-los amb el nom de la taula i el nom del camp units per un punt:
- nom_de_la_taula.nom_del_camp
- Dues taules MySQL anomenades comarques i municipis (vegeu l'esquema anteror).
- $dades = mysql_query("SELECT Municipi, Habitants, Monuments, Comarca FROM municipis INNER JOIN comarques ON comarques.Id = municipis.IdCom") or
- die("Problemes de lectura"); // Aparellament i tria
-
- while($fila = mysql_fetch_array($dades)){
- echo "<tr>";
- echo "<td class='e'>" . $fila['Municipi'] . " (" . $fila['Comarca'] . ")</td>"; // Presentació en el format desitjat
- echo "<td class='e' style='text-align: right'>" . $fila['Habitants'] . "</td>";
- echo "<td class='e'>" . $fila['Monuments'] . "</td>";
- echo "</tr>";
- }
Vegeu-ne el resultat.
La clàusula ON pot ésser completada amb una segona condició; per exemple restringir la consulta als municipis de més de 25.000 habitants. En aquest cas s'inclou la segona condició a cotinuació de la primera, unida a aquesta mitjançant un AND. A efectes pràctics, ON es comporta com WHERE.
Altes en una taula amb valors procedents d'una altra
Què passaria si donéssim d'alta un Municipi en la taula de Municipis i li assignéssim un codi de comarca inexistent? És molt important evitar errades com aquesta, i la millor solució és obligar a assignar les claus secundàries mitjançant desplegables que obtenen les dades de la taula principal, i així restringeixen l'elecció.
En l'exemple reiterat,
- Llegim en la taula Comarques el nom d'aquestes i l'identificador IdCom que cada una té associat.
- Amb aquestes dades construïm un desplegable contingut en un formulari (X)HTML.
- En el mateix formulari establim els espais per a recollir-hi alhora el valor del camp Municipi corresponent al que donem d'alta, i també les altres dades corresponents a aquest.
- Finalment, passem les dades a un document PHP encarregat de fer l'alta en la taula Municipis. Aquest darrer document és exactament igual al que fariem servir si les dades procedissin d'un formulari amb elecció lliure en tots els camps.
En definitiva, l'única cosa nova ara és la creació del desplegable dins el formulari:
- ...
- echo "<option value='" . $identificador . "'>" . $comarca . "</option>";
- ...
- Una taula MySQL anomenada comarques (vegeu l'esquema anteror).
- ...
- while($fila = mysql_fetch_array($dades)){
- $id = $fila['Id']; // Per a més claretat, creem la variable $id
- $com = $fila['Comarca']; // Per a més claretat, creem la variable $com
- echo "<option value='" . $id . "'>" . $com . "</option>"; Inserim $id i $com en un desplegable (X)HTML
- }
- ...
Vegeu-ne el resultat.
Nota: Aquest exemple s'acaba amb la presentació del formulari; en una situació real, el botó d'activació ens portaria a un document d'altes similar als que ja hem vist; en aquest cas, però, ens retorna a aquest document qua ara llegiu, i les dades, òbviament, es perden.