Komplexe PHP / MySQL Abfrage optimieren

Manimal

Angesehenes Mitglied
Hallo,
nach dem ich nun immer fleißig mitgelesen habe und auch in den Antworten ab und an vertreten bin muss ich nun uach mal nach Hilfe schreien.

Ich brauche eine Abfrage die in 2 verschiedenen Tabellen suchen und filtern muss.
Eine Lösung hat sich schnell gefunden, allerdings habe ich so das Gefühl das diese nicht ganz sauer ist.
wink.gif

Ausserdem entspricht die Abfrage noch nicht ganz meinen Vorstellungen.

Hier erstmal die momentane Version:
CODE
$sqlt = db_select_query("tag","top10_tags","cid = '".$CHART_id."'","GROUP BY tag");
while ($dsm = mysql_fetch_array($sqlt))
{
$sqlt1 = db_select_query("cid","top10_tags","tag = '".$dsm[tag]."' && cid != '".$CHART_id."'","GROUP BY cid");
while ($dsm1 = mysql_fetch_array($sqlt1))
{
$sqlt2 = db_select_query("chart_url,chart_description","top10_charts","id = '".$dsm1[cid]."' && active='1'","LIMIT 1");
while ($dsm2 = mysql_fetch_array($sqlt2))
{
$morechartsdesc .= '<li class="mcharts"><a href="'.$dsm2[chart_url].'" class="mchartsdesc">'.$dsm2[chart_description].'</a></li>';
}
}
}


Zum besseren Verständnis:
db_select_query ist eine selbst geschrieben Funktion.
("SPALTE","TABELLE","WHERE-ANWEISUNG","EXTRAS")

Die Frage:
A: Wie kann ich diese Abfrage optimieren?
B: Wie schaffe ich es das max. 7 Zeilen (ORDER BY views) ausgegeben werden?

PS: Falls noch nicht alles (z.B. Tabellenstruktur) klar ist, versuche ich das gerne nochmal zu erklären.
 
Schreib mal nur den MYSQL Teil, das gewurschtel mit PHP ist unleserlich.

Wenn Du nur sieben Ergebnisse willst: LIMIT 7
 
Ich blick da auf Anhieb auch nicht ganz durch, nenne doch mal die Tabellenstruktur und die Ergebnisse, die du erzielen willst. Zu deiner jetzigen Abfrage kann ich nur sagen: Vermeide auf jeden Fall irgendwelche Datenbankabfragen in einer Schleife durchzuführen!
Und bei dir sind es gleich drei
unsure.gif
 
OK, hier nochmal die reinen SQL Abfragen mit Kommentar.

CODE SELECT tag FROM tabelle1 WHERE cid = '$id' GROUP BY tag

Das Script soll herausfinden welche Tags es zu diesem Eintrag gibt.


CODE SELECT cid FROM tabelle1 WHERE tag = '$tags' GROUP BY tag

Anschließend soll es alle Einträge ausgeben die dieselben Tags haben.

Schon diese von mir getrennten Abfragen kann man vielleicht zusammenführen!?!?



CODE SELECT feld1,feld2 FROM tabelle2 WHERE id = '$cid' LIMIT 1

Anschließend sollen alle Einträge aus einer anderen DB mit der sleben ID ausgegeben werden.
(cid von top10_tags = id von top10_charts)

LIMIT 7 kann nicht angewendet werden da die letzte SQL Abfrage in einer while-Schleife ist.

Ist es jetzt verständlicher?
 
Da ich nicht mit MySQL arbeite, kenn ich dessen Spezialitäten nicht. Aber grundsätzlich zum SQL:

CODE SELECT tag FROM tabelle1 WHERE cid = '$id' GROUP BY tag

Da ist der Group überflüssig, es reicht ein distinct: select distinct tag from tabelle1 where cid='$id'.


CODE SELECT cid FROM tabelle1 WHERE tag = '$tags' GROUP BY tag

Da kommts jetzt auf die Tabellenstruktur an, und was im $tags drinsteht. Ich vermute, dass in $tags mehrere Tags kommasepariert drin sind, und dass im Feld tag in tabelle1 jeweils genau ein tag drinsteht. Dann dürfte diese Abfrage nicht die gewünschten Resulate bringen. Es ginge aber mit einem IN: select cid from tabelle1 where tag in '($tags)'.

Zusammenführen könnte mans so: select cid from tabelle1 where tag in (select tag from tabelle1 where cid='$id')


CODE SELECT feld1,feld2 FROM tabelle2 WHERE id = '$cid' LIMIT 1

Bin nicht sicher ob ich den Zusammenhang zwischen den beiden Tabellen richtig verstehe. Wenn die ID-Werte in den beiden Tabellen gleich sind:

select feld1, feld2 from tabelle2 where id in (select cid from tabelle1 where tag in (select tag from tabelle1 where cid='$id'))

So sind alle drei Abfragen in einer zusammengefasst. Es ist aber nicht gesagt, dass die Performance dann besser ist.
Jedenfalls dürfte die Abfrage dann nicht mehr in einer Schleife ausgeführt werden, die Schleife müsste dann lediglich zum Verarbeiten der Resulate herhalten.

Im Prinzip könnte man das auch mit Joins statt mit der IN-Klausel machen, je nachdem wär das schneller:
select feld1, feld2
from tabelle2 as c inner join
(select b.cid from tabelle1 a inner join tabelle1 b on a.tag=b.tag where a.cid='$id') as ab
on c.id=ab.cid

Ich weiss aber nicht, wieweit MySQL Self Joins unterstützt.

Griessli
Irene
 
Hallo Irene,

erst einmal vielen Dank für die ausführliche Antowrt.
An sowas in der Richtung hatte ich gedacht.

Werde damit heute auf jeden fall mal testen.

Hier nochmal für alle die grobe Tabellestrukur der ersten Tabelle die in den ersten beiden Abfragen von mir genutzt wird:

tabelle1
cid|tag
1|amazon
1|musik
2|musik
2|itunes
2|dance

@Irene: Es ist immer nur ein Tag enthalten!
 
QUOTE (Manimal @ Fr 22.06.2007, 12:19)tabelle1
cid|tag
1|amazon
1|musik
2|musik
2|itunes
2|dance

Das hatte ich nicht bedacht, dass mehrere gleiche cid's vorkommen. Kann sein, dass Du dann in die Unterabfragen noch ein Distinct reinsetzen musst, damit nicht mehrere gleiche Werte vorkommen. Aber die Datenbank würde Dir das gegebenenfalls mitteilen, mit "too many values" oder so ähnlich ;-)

Griessli
Irene
 
Irene, vielen vielen Dank.
Mit dem Tips aus deinem Post habe ich jetzt glaube ich genau das was ich wollte.

Bisher habe ich es nur direkt in der DB probiert, aber die Ergebnisse sind schonmal grandios.
Jetzt läßt nur noch die Umsetzung in PHP aus sich warten.

Für alle die interessiert was draus geworden ist:
CODE
SELECT chart_url
FROM top10_charts
WHERE id
IN (
SELECT DISTINCT cid
FROM top10_tags
WHERE tag
IN (
SELECT DISTINCT tag
FROM top10_tags
WHERE cid = '23'
)
)
LIMIT 7


 
Ich würde die Tabellen zuallererst normalisieren, bevor ich mir Gedanken über die Ausgaben mache.

Das

QUOTE (Manimal @ Fr 22.06.2007, 11:19)tabelle1
cid|tag
1|amazon
1|musik
2|musik
2|itunes
2|dance


ist eine n:m - Verknüpfung, also sind zwei Randtabellen und eine Verknüpfungstabelle notwendig, die nur IDs enthält.

Die oben notierte Lösung scheint mir nicht deterministisch zu sein: Ein Limit macht eigentlich nur Sinn in Kombination mit einer Sortierung, da ansonsten die alphabetisch vorne stehenden oder die mit kleinerer ID bevorzugt werden (oder auch genau umgekehrt, das ist ja das Problem).
 
Vielen Dank für deine sehr fachliche Antwort.

Leider kann ich nicht alles ganz nachvollziehen da ich das Programmieren nicht "gelernt" habe sondern es er als "learning-by-doing" betreibe.

Ich bin aber ziemlich sicher das folgende Abfrage trotzdem Sinn macht, da es eine Sortierung gibt.

CODE
SELECT chart_url
FROM top10_charts
WHERE id
IN (
SELECT DISTINCT cid
FROM top10_tags
WHERE tag
IN (
SELECT DISTINCT tag
FROM top10_tags
WHERE cid = '23'
)
)
ORDER BY views
LIMIT 7



Ich hatte in den vorherigen Post nicht alle rein geschrieben.
Somit werden die Ergebnisse aus der ersten Tabelle "top10_charts" erst sortiert und dann limitiert ausgegeben. Womit nicht nach alphabet oder id sortiert wird.

Liege ich damit richtig order trifft deine Aussage immer noch zu?

PS: Es sei Anzumerken das die ersten 10 Ergebnisse zum testen der SQL Abfrage sehr gute Ergebnisse hervor brachten.
 
QUOTE (Manimal @ Fr 22.06.2007, 18:58)Liege ich damit richtig order trifft deine Aussage immer noch zu?

Das weiß ich nicht, weil ich nicht weiß, was Du rauskriegen willst.

Im Augenblick holst Du dir alle cids, die mit der Ursprungs-cid 23 über irgendeinen tag-Eintrag verbunden sind. Die werden nach top10_charts.views sortiert, davon die Top-7 ausgegeben. Sprich: Die mit maximalem top10_charts.view - Wert kommen 'fast immer' raus.

Ebenso wäre es aber denkbar, eine der Unterabfragen (oder beide) bsp. nach der Zahl der Fundstellen absteigend zu sortieren und von diesen nur 10 an die aufrufende Where-Klausel zurückzugeben.

Dann sähe der Output - je nach Datenlage - womöglich ganz anders aus.

Skizziert:


CODE SELECT chart_url
FROM top10_charts
WHERE id
IN (
SELECT Top 10 cid, Count(*) As Anzahl
FROM top10_tags
WHERE tag
IN (
SELECT Top 10 tag, Count(*) As iAnzahl
FROM top10_tags
WHERE cid = '23'
Group By tag
Order By iAnzahl Desc
)
Group By cid
Order By Anzahl Desc
)
ORDER BY views
LIMIT 7


Man muß die Spalten Anzahl/iAnzahl weglassen, da sonst IN nicht mehr funktioniert (IN erwartet eine Spalte), dann Count(*) direkt in die OrderBy-Klausel verschieben. Aber es dürfte deutlich werden, daß so womöglich ganz andere Ergebnisse rauskommen. Außerdem könnte das, was jetzt als IN notiert ist, auch als Join mit dem Rest verknüpft werden - dann gibt es nochmals andere Ergebnisse.

Sprich: Was willst Du?
 
Zurück
Oben