Ik heb twee tabellen, printtoplate_ophangsystemen en printtoplate_artikelen
Graag had ik de volledige output van printtoplate_ophangsystemen in checkboxen en waar de id gekoppeld is aan printtoplate_artikelen in checkboxen checked..
printtoplate_ophangsystemen
ID | NAAM
1 Velcro
2 H-profiel
printtoplate_artikelen
ID | ARTIKEL | ophangsysteem_id
1 Dibond 1,2
$query_ophangsystemen = "SELECT printtoplate_ophangsystemen.naam AS ophang,printtoplate_ophangsystemen.id AS id FROM printtoplate_ophangsystemen INNER JOIN printtoplate_artikelen ON FIND_IN_SET(printtoplate_ophangsystemen.id,printtoplate_artikelen.ophangsysteem_id) > 0 WHERE printtoplate_artikelen.id='".$id."'";
// query vaste ophangsystemen
$result_ophang = mysql_query($query_ophangsystemen) or die(mysql_error());
while($row_ophang = $query_ophangsystemen = mysql_fetch_array($result_ophang)) // Alle vaste ophangsystemen uit database halen
{
echo '<tr><td><input type="checkbox" checked name="ophangsysteem[]" value="'.$row_ophang['id'].'"></td><td>'.$row_ophang['ophang'].'</td></tr>';
}
$query_ophangsystemen="SELECT printtoplate_ophangsystemen.naam AS ophang,printtoplate_ophangsystemen.id AS id FROM printtoplate_ophangsystemen INNER JOIN printtoplate_artikelen ON FIND_IN_SET(printtoplate_ophangsystemen.id,printtoplate_artikelen.ophangsysteem_id) > 0 WHERE printtoplate_artikelen.id='".$id."'";
Ten eerste zou ik een kleine aanpassing in je database structuur doen. Blijkbaar kan 1 artikel aan meerdere ophangsystemen behoren. Is het ook zo dat 1 ophangsysteem meerdere artikelen kan hebben? Dan is het namelijk een meer op meer relatie en heb je dus een koppeltabel nodig.
Een koppeltabel is niets meer dan een id, artikel_id en een ophangsysteem_id. Zo hoef je het dus niet komma gescheiden op te slaan. En kan je veel makkelijker een query maken voor de checkboxes.
In beide gevallen (een artikel kan tot meerdere ophangsystemen behoren en/of een ophangsysteem bestaat uit/bevat meerdere artikelen) is, zoals marten al zei, een koppeltabel gerechtvaardigd zo niet noodzakelijk om e.e.a. gestructureerd op te slaan.
Af en toe kan kommagescheiden data handig zijn, maar in dit geval waarschijnlijk niet ;).
Bedankt voor het snelle antwoord.
Het klopt inderdaad dat een artikel meerdere ophangsystemen kan hebben,gescheiden met een komma.
Kan je me even een voorbeeld geven hoe je dit precies zou doen?
Want ik gebruik namelijk een koppeltabel door INNER JOIN?
Bedankt!
Aha, een ophangsysteem is een eigenschap van een artikel (klopt dit?). Het codefragment in je oorspronkelijke bericht lijkt onderdeel van een beheerpagina van een specifiek artikel, waarin je (onder andere?) kunt aangeven welke ophangsystemen dit artikel allemaal ondersteunt (klopt dit?).
Wat ik in dat geval zou doen, is een lijst van alle ophangsystemen afdrukken, en door middel van een LEFT JOIN op de koppeltabel bepalen of een artikel een zeker ophangsysteem heeft. Dit kun je in één query doen.
Ik zal hier later nog een voorbeeldje van fabriceren als edit op dit bericht (database structuur en bijbehorende code).
Weet niet of je MyISAM of InnoDB gebruikt, maar heb even snel iets in elkaar gezet in InnoDB (iets andere namen, maar de strekking lijkt mij wel duidelijk). Indien je MyISAM gebruikt zijn foreign keys niet mogelijk dacht ik.
<?php
function escape_output($in) {
return htmlspecialchars($in, ENT_QUOTES);
}
$geselecteerd_artikel_id = 1;
$query = 'SELECT o.id AS ohs_id, o.naam AS ohs_naam, ao.ophangsysteem_id
FROM ophangsystemen o
LEFT JOIN artikel_ophangsystemen ao ON (ao.ophangsysteem_id = o.id AND ao.artikel_id='.mysql_real_escape_string($geselecteerd_artikel_id).')';
$res = mysql_query($query) or die(mysql_error());
if (mysql_num_rows($res)) {
?><table><?php
while ($row = mysql_fetch_assoc($res)) {
// als ao.ophangsysteem_id de waarde NULL heeft, betekent dit dat dit ophangsysteem niet bij dit artikel voorkwam in de koppeltabel
// in dat geval checken we de checkbox niet
if ($row['ophangsysteem_id'] == NULL) {
$checked = '';
} else {
$checked = ' checked="checked"';
}
?><tr>
<td>
<input type="checkbox" name="ophangsystemen[]" id="ophangsysteem_<?php echo escape_output($row['ohs_id']) ?>" value="<?php echo escape_output($row['ohs_id']) ?>"<?php echo $checked ?> />
<label for="ophangsysteem_<?php echo escape_output($row['ohs_id']) ?>"><?php echo escape_output($row['ohs_naam']) ?></label>
</td>
</tr><?php
}
?></table><?php
}
mysql_free_result($res);
?>
Dank je FangorN, die ON DELETE CASCADE is echt super handig, ik heb dat nog niet eerder gezien (wist wel dat het mogelijk was, maar nooit opgezocht). Weer wat geleerd!
Heel erg bedankt, dit werkt inderdaad perfect.
Alleen krijg ik een error wanneer ik de FK toevoeg aan de tabel artikel_ophangsystemen.
Ik gebruik nochtans innoDB engine voor beide tabellen...
Iemand een idee?
@sanderrebry: dit hangt vaak samen met de reeds aanwezige waarden die in de tabel zitten waar je de foreign key op wilt aanbrengen. Bij het aanmaken van de foreign key wordt namelijk gecontroleerd of de data "klopt". Als je dus bijvoorbeeld op de artikel_ophangsystemen-tabel een foreign key probeert aan te maken waarin artikel 1, 2 en 3 voorkomt, maar in de artikelen-tabel (het tabel met de "originelen" zeg maar, waar gedefinieerd staat welke artikelen er allemaal zijn) waaraan je refereert komt alleen artikel 1 en 2 voor, dan zal MySQL hier over struikelen.
Een andere reden waarom het aanmaken van een foreign key kan mislukken is een verschil in typedefinitie tussen het foreign-key veld en het veld waar je de foreign key naar wilt laten verwijzen. Voorbeeld: zowel artikelen.id als artikel_ophangsystemen.artikel_id moeten van hetzelfde type zijn als je een foreign key op artikel_ophangsystemen.artikel_id wilt laten verwijzen naar artikelen.id.
Dit is gelukt, gegevens geexporteerd daarna de structuur aangepast en dan alle gegevens terug geimporteerd. Nu nog de juiste query zoeken voor die koppeling. Dus wanneer de checkbox unchecked mag die uit en wanneer checked is die gekoppeld...
Thomas - 26/10/2013 00:11 (laatste wijziging 26/10/2013 00:19)
Moderator
Bedoel je de extra queries die uitgevoerd moeten worden om de koppeltabelgegevens aan te passen als je de informatie van een artikel (waaronder dus ophangsystemen) bijwerkt?
In mijn eerdere voorbeeld staat al hoe de ophangsystemen voor een bepaald artikel kunnen worden uitgelezen, hieronder staat in het kort beschreven hoe je deze bij kunt werken:
Gemakshalve heb ik maar even de hele code toegevoegd, je ziet zelf de nieuwe toevoegingen en ik ga er vanuit dat je dit zelf in je eigen code kunt opnemen.
Dit is een (voor mijzelf) werkend voorbeeld. Je zult dit zelf nog moeten aanpassen!
<?php
function escape_output($in) {
return htmlspecialchars($in, ENT_QUOTES);
}
// hier ga ik er gemakshalve van uit dat het artikel-id in de URL wordt doorgegeven via "artikel_id"
// dit kan in jouw code anders zijn!
$geselecteerd_artikel_id = isset($_GET['artikel_id']) && is_numeric($_GET['artikel_id']) ? $_GET['artikel_id'] : 1;
$submitting = $_SERVER['REQUEST_METHOD'] == 'POST';
if ($submitting) {
// LET OP: Hier doe je zelf waarschijnlijk ook je EIGEN aanpassingen aan het artikel, die staan hier niet bij!
// ...
// dit stuk gaat over het bijwerken van de koppeltabel
mysql_query('START TRANSACTION'); // InnoDB specifiek
// verwijderen
mysql_query('DELETE FROM artikel_ophangsystemen WHERE artikel_id = '.$geselecteerd_artikel_id);
// en alles weer toevoegen... als er iets toe te voegen is!
if (isset($_POST['ophangsystemen'])) {
foreach ($_POST['ophangsystemen'] as $ophangsysteem_id) {
mysql_query('INSERT INTO artikel_ophangsystemen (
artikel_id,
ophangsysteem_id
) VALUES (
'.mysql_real_escape_string($geselecteerd_artikel_id).',
'.mysql_real_escape_string($ophangsysteem_id).'
)') or die(mysql_error());
}
}
mysql_query('COMMIT'); // InnoDB specifiek
// doorsturen naar artikelpagina, dit kan in jouw code natuurtlijk een andere actie zijn...
header('Location: '.$_SERVER['PHP_SELF'].'?artikel_id='.$geselecteerd_artikel_id);
exit;
} else {
?><form action="<?php echo escape_output($_SERVER['PHP_SELF']) ?>?artikel_id=<?php echo escape_output($geselecteerd_artikel_id) ?>" method="post"><?php
$query = 'SELECT o.id AS ohs_id, o.naam AS ohs_naam, ao.ophangsysteem_id
FROM ophangsystemen o
LEFT JOIN artikel_ophangsystemen ao ON (ao.ophangsysteem_id = o.id AND ao.artikel_id='.mysql_real_escape_string($geselecteerd_artikel_id).')';
$res = mysql_query($query) or die(mysql_error());
if (mysql_num_rows($res)) {
?><table><?php
while ($row = mysql_fetch_assoc($res)) {
// als ao.ophangsysteem_id de waarde NULL heeft, betekent dit dat dit ophangsysteem niet bij dit artikel voorkwam in de koppeltabel
// in dat geval checken we de checkbox niet
if ($row['ophangsysteem_id'] == NULL) {
$checked = '';
} else {
$checked = ' checked="checked"';
}
?><tr>
<td>
<input type="checkbox" name="ophangsystemen[]" id="ophangsysteem_<?php echo escape_output($row['ohs_id']) ?>" value="<?php echo escape_output($row['ohs_id']) ?>"<?php echo $checked ?> />
<label for="ophangsysteem_<?php echo escape_output($row['ohs_id']) ?>"><?php echo escape_output($row['ohs_naam']) ?></label>
</td>
</tr><?php
}
?></table><?php
}
mysql_free_result($res);
?><button type="submit">submit</button>
</form><?php
}
?>
Wat je in feite doet is het verwijderen van de bestaande koppelsystemen voor het bewuste artikel, en daarna voeg je ze weer toe. Je hoeft dan namelijk niet te controleren of een record al aanwezig is of niet. Als je eenzelfde artikel-ophangsysteem-paar opnieuw probeert in te voegen gaat dit mis vanwege de PRIMARY KEY constraint: elke artikel-ophangsysteem-combinatie in de koppeltabel MOET uniek zijn. Daarom maak je het jezelf makkelijk door ze eerst weg te gooien, vervolgens voeg je ze opnieuw toe.
Nu wil je natuurlijk niet dat dit mis gaat (je verwijdert wel de koppelingen, maar bij het toevoegen gaat iets mis, dan ben je data kwijt). InnoDB heeft hier een voorziening voor: de transactie. Kortweg gezegd kun je een reeks van queries "als geheel" uitvoeren. Indien er iets misgaat wordt de hele transactie teruggedraaid, een transactie (reeks van queries) wordt dus altijd helemaal, of helemaal niet uitgevoerd. Dit helpt je verder met het kloppend houden van je data als er fouten in je code zitten. De kans dat ook je database-data hierdoor gecorrumpeerd raakt neemt hierdoor af. Maar je kunt het natuurlijk ook zonder transacties stellen, je hebt het niet per se nodig. Je start een transactie door middel van START TRANSACTION en als je met je reeks queries klaar bent COMMIT je alles. Als je niet van InnoDB gebruik maakt moet je deze twee regels weglaten.
Nog een opmerking: Bij het aanmaken van mijn foreign keys ben ik er van uitgegaan dat:
- een ophangsysteem verwijderd mag worden uit de database, ook als er nog artikelen bestaan die dit ophangsysteem gebruiken, de koppeltabel wordt dan opgeschoond (dit is in feite wat ON DELETE CASCADE inhoudt); het verwijderen van ophangsystemen wil je mogelijk niet toestaan zolang er artikelen zijn die dit ophangsysteem gebruiken; dit kun je afdwingen met de foreign key, maak de ophangsysteem-key in dat geval ON DELETE RESTRICT (of laat dit simpelweg achterwege want ON DELETE RESTRICT is default)
- een artikel verwijderd mag worden uit de database, ook al is deze gekoppeld aan een of meer ophangsystemen; dit is logisch, je wilt bij het verwijderen van een artikel ALLE informatie van een artikel weggooien, dus ook de koppelsysteem-gerelateerde informatie
Dit illustreert goed de kracht van InnoDB boven MyISAM: het kost je heel weinig moeite om de gegevens kloppend (referentieel integer) te houden en je kunt extra condities opleggen (o.a.) op het moment dat je gegevens probeert te verwijderen.
Hier geldt echter wel "with great power comes great responsibility". Als je InnoDB op volle toeren wilt gebruiken (foreign keys, transacties en de hele mikmak) moet je wel een gezond databaseontwerp hebben en enige kaas gegeten hebben van wat je aan het doen bent!