MySQL-Abfrage über 2 Tabellen, wo 2 Bedingungen gleichzeitig erfüllt sein müssen.

klausschreiber

Well-known member
ID: 162475
L
6 Mai 2006
247
8
Hallo,

ich habe ein Problem mit MySQL und bin dort, bzw. generell mit Datenbankabfragen eher ein Neuling. Ich schreibe ein PHP-Script, wo man Dateien hochladen kann und verschiedenen Kategorien zuordnen kann. Eine Datei kann dabei auch mehreren Kategorien angehören.

Hier mal ein Bild des Teils, der für die Kategorisierung zuständig ist:
datenbank.jpg


Die erste Tabelle lautet "dirs". "dirs" steht für Verzeichnis, da bei jedem Upload ein neues Verzeichnis erstellt wird mit fortlaufender Nummer. Die "DirID" ist einerseits der Primärschlüssel der Tabelle und andererseits die Nummer des Verzeichnisses. Der genaue Dateiname wird in einer extra Tabelle gespeichert, da ein Verzeichnis mehrere Dateien enthalten kann, wenn sie zum gleichen Thema sind (z.B. die gleiche Datei als .pdf und als .jpg). Alle Dateien in einem Verzeichnis sind der/den gleichen Kategorie(n) zugeordnet. Daher reicht es, wenn ich das Verzeichnis der/den gewünschten Kategorie(n) zuordne.

Die dritte Tabelle lautet "categories". Dort ist "CategorieID" der Primärschlüssel und "CategorieName" enthält den Namen der Kategorie (z.B. Mathematik, Deutsch oder sonst irgendetwas)

Die zweite Tabelle "dirs_categories" verbindet beide Tabellen miteinander. Die Beziehungen zueinander sind "dirs.DirID=dirs_categories.DirNR" und "categories.CategorieID=dirs_categories.CategorieNR".

Nun nehmen wir folgenden Inhalt für die Tabelle "categories" an:
Code:
[B]CategorieID[/B]     [B]CategorieName[/B]
1                     Deutsch
2                     Mathematik
3                     leicht
4                     schwer
Wenn ich nun alle Verzeichnisse aufgelistet haben will, die in der Kategorie "Mathematik" sind, also die KategorieID=2 haben, kann ich das ja relativ einfach über die Tabellen "dirs" und "dirs_categories" abfragen, da die "CategorieID" ja als Fremdschlüssel in der "dirs_categories" enthalten ist. Eine einfache Abfrage würde dann also folgendermaßen lauten:
PHP:
SELECT * 
FROM dirs, dirs_categories 
WHERE dirs.DirID=dirs_categories.DirNR AND dirs_categories.CategorieNR=2
Aber wie muss die Abfrage lauten, wenn ich nur die Verzeichnisse aufgelistet haben will, die z.B. sowohl in der Kategorie "Mathematik", als auch in der Kategorie "schwer" enthalten sind?

Mir ist keine Lösung eingefallen, aber das muss doch möglich sein, oder ist das nur mit PHP zu lösen?


Danke für eure Antworten,
Gruß,
Klaus
datenbank.jpg
 
so weit ich weiß müsstest du das mit LEFT JOIN hinbekommen.

Der Spuckt so weit ich weiß immer nur nen Datensatz aus, wenn der auch in der 2. Tabelle enthalten ist. google einfach mal nach "Left+join+mysql"
 
Code:
[FONT=Courier New][B][COLOR=#9932cc]WHERE[/COLOR][/B] dirs.DirID=dirs_categories.DirNR
  [B][COLOR=#9932cc]AND[/COLOR][/B] dirs_categories.CategorieNR=2[/FONT]
selektiert das gewünschte.

Jetzt fällt dir ein, dass du nicht nur Category 2, sondern auch zusätzlich Category 4 haben willst, also
Code:
[FONT=Courier New][B][COLOR=#9932cc]WHERE[/COLOR][/B] dirs.DirID=dirs_categories.DirNR
  [B][COLOR=#9932cc]AND[/COLOR][/B] [COLOR=Red]dirs_categories.CategorieNR=2[/COLOR][/FONT]
wird der rote Part zu
Code:
[FONT=Courier New]dirs_categories.CategorieNR=2
[B][COLOR=#9932cc]OR[/COLOR][/B] dirs_categories.CategorieNR=4[/FONT]
.

Ordentlich Klammern nicht vergessen:
Code:
[FONT=Courier New][B][COLOR=#9932cc]WHERE[/COLOR][/B] dirs.DirID=dirs_categories.DirNR
  [B][COLOR=#9932cc]AND[/COLOR][/B] (dirs_categories.CategorieNR=2
       [B][COLOR=#9932cc]OR [/COLOR][/B]dirs_categories.CategorieNR=4)[/FONT]

edit:
@QuArK007:
JOIN is nicht notwendig, da er ja die Tabellen von Hand zusammenjoint.
 
Vielen Dank für eure Antworten. Leider ist es nicht genau das, was ich wollte. Ich versuche es nochmal besser zu beschreiben.

@QuArK007:
Normalerweise ist der Datensatz immer in beiden Tabellen enthalten, das ist nicht das Problem.

@theHacker:
Dein Code gibt alle Verzeichnisse aus, die der Kategorie 2 und alle Verzeichnisse, die der Kategorie 4 zugeordnet sind. Ich möchte aber nur die Verzeichnisse, die sowohl der Kategorie 2, als auch gleichzeitig der Kategorei 4 zugeordnet sind.

Folgendes Beispiel:
Code:
[B]Verzeichnis[/B]          [B]zugeordnete Kategorien[/B]
a                          1 und 4
b                          2 und 4
Deine Abfrage würde mir ja Verzeichnis a und Verzeichnis b anzeigen. Ich möchte aber nur Verzeichnis b.



edit:
Wenn ein anderes Datenbankdesign dafür besser geeignet ist, kann ich das natürlich auch ändern. Aber es muss halt so sein, dass es beliebig viele Kategorien geben kann und man ein Verzeichnis theoretisch beliebig vielen Kategorien zuordnen kann.
 
Zuletzt bearbeitet:
ich glaube, jetzt habe ich eine Lösung gefunden. Ich brauche dann zwar pro Kategorie eine Datenbankabfrage, aber mittels einer Datenbankabfrage wird es wohl nicht gehen, oder?
Meine Idee ist folgendermaßen: (Auf das Beispiel aus meinem letzten Post bezogen)
Zuerst frage ich ab, welche Verzeichnisse in der ersten gesuchten Kategorie sind. In diesem Beispiel nehme ich an, diese Kategorie hat die ID 4. Also:
Code:
[COLOR=Magenta]SELECT[/COLOR] dirs.DirID
[COLOR=Magenta]FROM[/COLOR] dirs, dirs_categories
[COLOR=Magenta]WHERE[/COLOR] dirs.DirID=dirs_categories.DirNR 
[COLOR=Magenta]    AND[/COLOR] dirs_categories.CategorieNR=4
Nun habe ich alle Verzeichnisse, die in Kategorie 4 sind. In meinem Beispiel wäre das Verzeichnis "a" und "b" Nun frage ich ab, welche von diesen Verzeichnissen in der Kategorie 2 sind. Also:
Code:
[COLOR=Magenta]SELECT[/COLOR] *
[COLOR=Magenta]FROM[/COLOR] dirs, dirs_categories
[COLOR=Magenta]WHERE[/COLOR] dirs.DirID=dirs_categories.DirNR 
    [COLOR=Magenta]AND[/COLOR] dirs_categories.CategorieNR=2 
    [COLOR=Magenta]AND[/COLOR] (dirs.DirID="a" 
            [COLOR=Magenta]OR[/COLOR] dirs.DirID="b")
Klar, dirs.DirID ist eigentlich eine Zahl, aber zum besseren Verständnis habe ich jetzt a und b aus meinem Beispiel genommen.

Besser wird es wohl nicht gehen, oder?
 
Naja, ein Join ist hier nicht unbedingt die beste Wahl.

Versuch mal eine Subquery.

SELECT daten
FROM dirs
WHERE dirs.dirID IN
(
SELECT dirs_categories.DirNR
FROM dirs_categories
WHERE dirs_categories.CategorieNR IN ( 2,4 )
GROUP BY dirs_categories.DirNR
HAVING COUNT(*) = 2 )


kurz zur Erklärung:

SELECT dirs_categories.DirNR
FROM dirs_categories
WHERE dirs_categories.CategorieNR IN ( 2,4 )


damit wählst Du alle DirNR aus, die entweder in der ersten oder zweiten Kategorie sind.


GROUP BY dirs_categories.DirNR
HAVING COUNT(*) = 2


jetzt schaust Du, welche DirNR Du zweimal hast ( das sind nämlich die, die in beiden Kategorien sind )


SELECT daten
FROM dirs
WHERE dirs.dirID IN
(...)


Damit holst Du Dir jetzt die zugehörigen Daten aus der Tabelle dirs
 
Zuletzt bearbeitet:
wow, vielen Dank. Das ist genau das, was ich gesucht habe und es funktioniert einwandfrei (und lässt sich auch mit php besser dynamisch mit beliebig vielen Kategorien generieren).

Mit so einer Subquery ein merhdimensionales Array zu erstellen, ist aber nicht möglich, oder? Zur Verdeutlichung:
Ich habe die einzelnen Kategorien zu Gruppen gegliedert, z.B.

Fach:
-> Deutsch
-> Matehmatik

Schwierigkeitsgrad:
-> leicht
-> schwer

Dies wird in html mittels Select-Liste ausgegeben, also:
HTML:
Fach:<select>
<option>Deutsch</option>
<option>Mathematik</option>
</select>

Schwierigkeitsgrad:<select>
<option>leicht</option>
<option>schwer</option>
</select>

Ich habe das so gelöst, dass ich die Gruppen und Kategorien mittels Join verbinde und dann die Daten nach der Spalte "Gruppen" sortiert ausgeben lasse, sodass alle Kategorien der gleichen Gruppe direkt nacheinander kommen. Mittels PHP prüfe ich dann bei jeder Zeile, ob sich der Gruppenname geändert hat, und falls ja, erstelle ich ein neues <select> Element. Also praktisch:
PHP:
if ($row['Gruppe']!=$bisherigeGruppe) {
echo '<select>';
$bisherigeGruppe=$row['Gruppe'];

Ist es möglich, sich das Ganze in MySQL gleich passend als mehrdimensionales Array ausgeben zu lassen? Also dass die einzelnen Zeilen dann praktisch so aussehen:
PHP:
$row[Fach][Mathematik]
$row[Fach][Deutsch]
$row[Schwierigkeitsgrad][leicht]
$row[Schwierigkeitsgrad][schwer]
Oder ist die von mir gepostete Methode die übliche/beste Methode um ein <select> dynamisch aus einer Datenbank zu erstellen?
 
Ist es möglich, sich das Ganze in MySQL gleich passend als mehrdimensionales Array ausgeben zu lassen?

Eine native Möglichkeit gibt es nicht. Aber du kannst dir das Result-Set ja in ein Array lesen und dieses dann durchlaufen.
PHP:
$dirs = $mysql->get_results("SELECT..."); // Pseudo-Code -> lese Daten aus DB
foreach($dirs AS $dir)
    $final_array[$dir['Gruppe']][] = $dir;

und schon kannst du dir dein <select> bauen. Musst du eben an deinen Fall anpassen :)

Greetz

paddya
 
danke für deine Antwort.
Dies wäre natürlich möglich. Allerdings ist es auf diese Weise dann ja zusätzlicher Aufwand für php, das Ganze erstmal in ein mehrdimensionales Array zu bringen und bedeutet nochmal eine Schleife extra.
Wie wird die Sache mit den <select>'s denn normalerweise gelöst, bzw. wa ist die bessere Lösung? Meine vorhin genannte Lösung oder die Lösung mit der Umwandlung in mehrdimensional Arrays?
 
Wenn du nicht gerade mit riesigen Datenmengen hantierst, dürften sich das Umbauen des Arrays nicht sonderlich auswirken.

Und ich würde auch deine genannte Möglichkeit nehmen ;)

Greetz

paddya
 
ok, danke für die schnelle Antwort. Ne, riesige Datenmengen werden es bei den Kategorien wohl eher nicht, sonst wäre das Ganze ja sehr unübersichtlich, wenn es da tausende Kategorien gäbe. Ich wollte mich nur versichern, dass meine Variante eine übliche/gute Variante ist, da ich mich im Bezug auf Datenbankabfragen noch nicht sonderlich gut auskenne und gleich von Anfang an guten Code schreiben/lernen will.
 
Du könntest übrigens auch gleich zwei verschachtelte Schleifen für die Ausgabe nehmen, anstatt die aktuelle Gruppe mit der letzten Gruppe gegenzuchecken.

PHP:
foreach($final_array AS $category => $dirs) {
    echo '<p>' . $category . ': <select>';
    foreach($dirs AS $dir)
        echo '<option value="' . $dir['DirID'] . '">' . $dir['DirShort_description'] . '</option>';
    echo '</select></p>';
}

Und ein Tipp noch: Gewöhne dir gleich an, deinen Code übersichtlich zu gestalten, sprich genügend Leerzeichen zu setzen.

Aus
PHP:
if ($row['Gruppe']!=$bisherigeGruppe) {
echo '<select>';
$bisherigeGruppe=$row['Gruppe'];

wird
PHP:
if ($row['Gruppe'] != $bisherigeGruppe) {
echo '<select>';
$bisherigeGruppe = $row['Gruppe'];

und ist gleich viel besser lesbar :)

Greetz

paddya
 
danke für deine Antwort. Jo, ob ich vor und nach dem "=" ein Leerzeichen setze oder nicht, ist unterschiedlich. Aber hast recht, wäre vielleicht keine schlechte Idee, wenn ich die etwas häufiger einsetze.

Aber bezüglich deines Codes:
Wenn mir MySQL das Ergebnis als mehrdimensionales Array ausgeben würde, hätte ich das auch so gemacht, aber wenn ich die Ausgabe nicht zuerst in ein Array umwandle, brauche ich doch das "if ..." Zeugs? Oder stehe ich gerade auf dem Schlauch?
 
Mein geposteter Code basiert darauf, dass du das Array bereits in ein mehrdimensionales Array umgebaut hast ;) D.h. du brauchst eigentlich nur meine beiden Codeschnippsel aneinander zu ketten und entsprechend anzupassen.

Greetz

paddya
 
danke für deine Antwort. Ok, dann hatte ich das richtig verstanden. Werde es aber wohl so lassen, wie es bisher ist, da du ja auch der Meinung warst, dass ein Umbau keinen Vorteil verschafft oder in irgendeiner Weise besser ist.
 
Im Prinzip sollte mein Code eigentlich der schnellere sein. Aber wie gesagt: das wirkt sich erst bei größeren Datenmengen aus.

Greetz

paddya
 
danke für deine Antwort. Dann hatte ich das wohl missverstanden.
Ich werde es jetzt wahrscheinlich sowieso, unabhängig von der Geschwindigkeit, deinen Vorschlag nehmen, da ich die select-Liste 2x brauche, jedoch in jeweils leicht modifizierter Form. Da bietet es sich ja an, eine Funktion zu schreiben, die mir die Liste als Array ausgibt, damit ich daraus dann jeweils mit den gewünschten Anpassungen die select-Felder erstellen kann, ohne den gesamten Code doppelt zu schreiben.