MySQL: Erweiterte Abfrage - Sollte/darf man das so tun?
Nico R.
- sql
Hallo zusammen,
ich habe festgestellt, dass die SQL-Abfrage aus meinem letzten Beitrag noch zu unpräzise ist. Ich erhalte ja aktuell die Gesamtstatistik eines Spielers aus einer Saison.
Nun kann es allerdings in einer Saison fünf verschiedene Wettbewerbstypen (Liga, Landespokal, DFB-Pokal, Testspiel, Relegation) geben. Ich möchte gerne pro Saison die jeweiligen Gesamtsummen für diese Wettbewerbe erhalten.
Ich könnte nun meine bestehende SQL-Abfrage entsprechend erweitern, die liefert im Test auch schon die korrekten Daten. Dabei entsteht allerdings ein ziemliches Monstrum an Abfrage. Ich habe meine Zweifel, ob man das so tun sollte. Hier die erweiterte Abfrage mit zwei verschiedenen Wettbewerbstypen (eigentlich sind es wie gesagt fünf):
SELECT
saison_spieler.saison_id,
saison_spieler.nr,
saison_spieler.pos,
saison.name,
saison.jahr,
liga.einsaetze, liga.eingew, liga.ausgew, liga.spielzeit, liga.tore, liga.vorlagen, liga.gelb, liga.gelb_rot, liga.rot,
pokal.einsaetze, pokal.eingew, pokal.ausgew, pokal.spielzeit, pokal.tore, pokal.vorlagen, pokal.gelb, pokal.gelb_rot, pokal.rot
FROM
`saison_spieler`
JOIN
`saison`
ON saison.id = saison_spieler.saison_id
JOIN
(SELECT
spieltag.saison_id,
COUNT(spielerstatistik.spieler_id) AS einsaetze,
COUNT(spielerstatistik.eingew) AS eingew,
COUNT(spielerstatistik.ausgew) AS ausgew,
SUM(spielerstatistik.spielzeit) AS spielzeit,
SUM(spielerstatistik.tore) AS tore,
SUM(spielerstatistik.vorlagen) AS vorlagen,
COUNT(spielerstatistik.gelb) AS gelb,
COUNT(spielerstatistik.gelb_rot) AS gelb_rot,
COUNT(spielerstatistik.rot) AS rot
FROM
spielerstatistik
JOIN
spieltag
ON spieltag.id = spielerstatistik.spieltag_id
WHERE
spielerstatistik.spieler_id = 12 AND spieltag.wettbewerb = 1
GROUP BY
saison_id
) AS liga
ON liga.saison_id = saison_spieler.saison_id
JOIN
(SELECT
spieltag.saison_id,
COUNT(spielerstatistik.spieler_id) AS einsaetze,
COUNT(spielerstatistik.eingew) AS eingew,
COUNT(spielerstatistik.ausgew) AS ausgew,
SUM(spielerstatistik.spielzeit) AS spielzeit,
SUM(spielerstatistik.tore) AS tore,
SUM(spielerstatistik.vorlagen) AS vorlagen,
COUNT(spielerstatistik.gelb) AS gelb,
COUNT(spielerstatistik.gelb_rot) AS gelb_rot,
COUNT(spielerstatistik.rot) AS rot
FROM
spielerstatistik
JOIN
spieltag
ON spieltag.id = spielerstatistik.spieltag_id
WHERE
spielerstatistik.spieler_id = 12 AND spieltag.wettbewerb = 2
GROUP BY
saison_id
) AS pokal
ON pokal.saison_id = saison_spieler.saison_id
WHERE
saison_spieler.spieler_id = 12
Offenbar ist das prinzipiell für MySQL keine große Herausforderung. Ich bekomme in phpmyadmin die Auskunft "Die Abfrage dauerte 0,0000 Sekunden". Aber dennoch frage ich ja fünfmal die gleiche Art von Daten ab. In PHP kann ich die Erstellung des Querys in eine Funktion packen und somit Redundanzen vermeiden. Sollte/könnte ich das auch in SQL tun? Bewegt sich der Query noch einem "normalen" Rahmen? Ich hatte so etwas bisher noch nicht.
Schöne Grüße
Nico
Hi,
Nun kann es allerdings in einer Saison fünf verschiedene Wettbewerbstypen (Liga, Landespokal, DFB-Pokal, Testspiel, Relegation) geben. Ich möchte gerne pro Saison die jeweiligen Gesamtsummen für diese Wettbewerbe erhalten.
klingt für mich spontan nach einem GROUP BY Wettbewerbstyp.
cu,
Andreas a/k/a MudGuard
Hallo Andreas,
das geht wohl nicht, da ich schon nach saison_id gruppiere. Hier mal die Ergebnismenge zu der obigen Abfrage:
Hier sind natürlich die Feldnamen noch nicht per Alias präzisiert (liga.spielzeit usw.), aber grundsätzlich ist das schon genau das Ergebnis, das ich für die Darstellung der Daten brauche.
Ich glaube meine Frage, war daher auch überflüssig, denn im Grunde funktionierts ja. Und egal wie ichs drehe oder wende, z.B. nicht in eine einzige Abfrage packe, sondern in separate und dann dort meinetwegen per GROUP BY spieltag.wettbewerb strukturiere, muss ich ja doch irgendwie genau diese Daten abfragen. Glaube ich…
Für Anregungen und Hilfe bin ich natürlich trotzdem dankbar.
Schöne Grüße
Nico
Hallo Nico,
Die Tabellenstruktur der Abfrage muss nicht identisch mit der Tabellenstruktur der Ausgabe sein. Ein paar Umbauten sind schon erlaubt und ggf auch einfacher zu handhaben
Rolf
Hi,
das geht wohl nicht, da ich schon nach saison_id gruppiere.
Das ist kein Argument: man kann nach mehreren Spalten gruppieren.
Ich hab mir Deine Konstruktion nicht näher angeschaut (wie auch, gibt ja keine Tabellenbeschreibung), ggf. muß auch ein Group By am äußeren Select stattfinden.
cu,
Andreas a/k/a MudGuard
Hallo Nico,
WILLST du oder musst du alle Daten zu einer Saison in einer Zeile haben?
Es wäre einfacher, je Saison und Wettbewerb eine Zeile zu erzeugen.
Beim JOIN hat du auch das Problem, dass du nur Spieler bekommst, die an allen Wettbewerbstypen teilgenommen haben.
Bevor ich hier Berge von SQL raushaue, warte ich lieber auf deine Antwort 😉
Rolf
Hallo Rolf,
WILLST du oder musst du alle Daten zu einer Saison in einer Zeile haben?
Das hat sich aufgrund der Anzeige der Daten so ergeben. Zwingend ist es wohl nicht. So sieht der enstsprechende Anzeigeteil derzeit aus:
Es wäre einfacher, je Saison und Wettbewerb eine Zeile zu erzeugen.
Ich verstehs nicht ganz, fürchte ich 🤔
Beim JOIN hat du auch das Problem, dass du nur Spieler bekommst, die an allen Wettbewerbstypen teilgenommen haben.
Ja, stimmt. Das hätte ich gar nicht auf dem Schirm, lies sich aber im Test mit jeweils LEFT JOIN beheben.
Bevor ich hier Berge von SQL raushaue, warte ich lieber auf deine Antwort 😉
Das würde ich auch gar nicht erwarten. Der eine oder andere Anstoß reicht ja. Hoffentlich 😀
Schöne Grüße
Nico
Hallo Nico R.,
Nun kann es allerdings in einer Saison fünf verschiedene Wettbewerbstypen (Liga, Landespokal, DFB-Pokal, Testspiel, Relegation) geben. Ich möchte gerne pro Saison die jeweiligen Gesamtsummen für diese Wettbewerbe erhalten.
Das sieht doch schonmal so aus, als ob deine Query das falsche Format hätte. Du hast gar nicht so viele Spalten im Display wie die Query liefert, d.h. du musst bei deiner Query pro Saison erstmal rauskriegen, welche Spalten überhaupt gezeigt werden müssen. Ich reverse-engineere für Dich das folgende Datenmodell (sicherlich hast Du noch mehr Spalten in den Tabellen):
Ich hätte noch fachliche Fragen:
Ich habe Nr und Pos mal dringelassen… Eigentlich muss man nur die Abfrage auf den Wettbewerb entfernen und dafür die Spalte Wettbewerb in die Select-Liste und in die GROUP BY Liste übernehmen. Daraufhin bekommt man eine nach Saison und Wettbewerb gruppierte Liste der Einsätze eines Spielers.
Saisons, an denen er nicht teilgenommen hat, kommen aus diesem GROUP BY Select nicht heraus. Es ist deshalb sinnvoll, mit diesem Teil zu starten und für die gefundenen Saisons die Saison- und Spielerdaten hinzuzumischen.
SELECT Saison.Name, Saison.Jahr,
Saison_Spieler.Nr, Saison_Spieler_Pos,
Stat.Wettbewerb, Stat.Einsaetze, Stat.Eingewechselt, ... Stat.Rot
FROM
(SELECT Saison_ID as saison_id,
Wettbewerb as wettbewerb,
Spielerstatistik.Spieler_ID as spieler_id,
COUNT(Spieler_ID) as einsaetze,
COUNT(eingew) as eingewechselt,
...
COUNT(rot) as rot
FROM
Spieltag
JOIN
Spielerstatistik
ON spieltag.ID = spielerstatistik.spieltag_id
WHERE
Spielerstatistik.Spieler_ID = $spieler_id
GROUP BY
Saison_ID, Wettbewerb
) Stat
ON Stat.Saison_ID = Saison.ID
JOIN
Saison ON Saison.ID = Stat.Saison_ID
JOIN
Saison_Spieler ON Saison_Spieler.Saison_ID = Stat.Saison_ID
AND Saison_Spieler.Spieler_ID = Stat.Spieler_ID
ORDER BY Saison.Jahr, Wettbewerb
Für das Beispiel in deinem Screenshot bekämest Du nun 4 Zeilen heraus, sortiert nach Saisonjahr und Wettbewerb.
Über diese Liste machst Du nun mit PHP eine Gruppenverarbeitung. Es gibt dazu einen Artikel in unserem Wiki, der ist für das, was hier nötig ist, aber zu komplex. Für eine einstufige Gruppenverarbeitung funktioniert es in PHP so:
/* Hier die Query ausführen und Erfolg prüfen */
/* Hier den Seitenkopf schreiben */
$row = $db->fetch(); // Einmal vorauslesen
if ($row == null) {
/* Hier die Entschuldigung schreiben, dass keine Daten da sind */
}
WHILE ($row) {
/* Hier <table>,<thead> etc für neue Saison-Tabelle schreiben */
$saison = $row['saison_id']
WHILE ($row && $row['saison_id'] == $saison) {
/* Hier die Zeile für einen Wettbewerb schreiben */
$row = $db->fetch(); // Nachlesen
}
/* Hier </table> etc für letzte Saison-Tabelle schreiben */
}
/* Hier den Seitenfuß schreiben */
Dieser Mustercode verwendet NICHT das bekannte PHP Pattern, im WHILE auch zu lesen (also WHILE ($row = $db->fetch())
), statt dessen wird einmal vor der Schleife vorausgelesen, es werden zwei Schleifen geschachtelt (die äußere für die Saisons, die innere für die Saisonzeilen) und es wird am Ende der inneren Schleife nachgelesen. Auf diese Weise erhält man eine Programmstruktur, die der Ausgabedatenstruktur entspricht.
Rolf