Verstädnissfrage zu GROUP BY
-
Dafür musst Du ja im Prinzip jeden Eintrag mit einen anderen Vergleichen. Dafür ist HAVING in Verbindung mit GROUP BY da:
SELECT a.userID, a.ip FROM userips a JOIN userips b ON a.userID = b.userID GROUP BY a.userID, a.ip HAVING a.ID = max(b.ID)
-
cl90 schrieb:
Dafür wollte ich Group By nutzen.
SELECT userID, ip FROM userips GROUP BY userID ORDER BY ID DESC
Also MSSQL verweigert eine Abfrage dieser Art, und ich bin mir auch ziemlich sicher dass Standard-SQL das auch verbietet.
Du kannst nix selektieren was nicht entweder in der GROUP BY Liste vorkommt oder eine Aggregatfunktion (min/max/avg/count
) verwendet.@tntnet:
Dein (für mich sehr schwer zu entzifferndes) Konstrukt entspricht logisch genau der von Duplikateneliminator vorgeschlagenen Lösung mit "where ID in".
Und genau so (die "where ID in" Variante) würde ich es auch selbst schreiben.
-
Achso, in T-SQL kann man auch schön Ranking-Functions und Common-Table-Expressions kombinieren um das selbe zu machen:
; WITH latest_userips AS ( SELECT *, ROW_NUMBER() OVER(PARTITION BY userID ORDER BY ID DESC) AS age FROM userips ) SELECT * FROM latest_userips WHERE age = 1
-
hustbaer schrieb:
@tntnet:
Dein (für mich sehr schwer zu entzifferndes) Konstrukt entspricht logisch genau der von Duplikateneliminator vorgeschlagenen Lösung mit "where ID in".
Und genau so (die "where ID in" Variante) würde ich es auch selbst schreiben.Mag sein, dass das schwer zu entziffern ist aber es ist erst mal Standard SQL und außerdem wahrscheinlich eher das, was erwartet wird.
Ich gehe erst mal davon aus, dass die IDs pro user vergeben werden, so dass die IDs doppelt vor kommen können. Der Primärschlüssel wäre (userid, id) Also so was wie:
userid id ip ------ -- ------- hugo 1 1.2.3.4 willi 1 3.4.5.6 hugo 2 5.5.6.3 uwe 1 6.3.5.6
Damit fehlt bei der GROUP-BY-Lösung von Duplikateneliminator im Subselect die Referenz auf die äußere Tabelle. Ich bekomme in diesem Fall also alle Datensätze, da das Subselect (1, 2) liefert und das äussere alle Datensätze, wo diese id gesetzt ist und damit auch den hugo mit der id 1.
Ob das dann noch wirklich leichter zu entziffern ist, wage ich zu bezweifeln.
Das Selfjoin mit HAVING ist in der Regel schneller. Optimierer können besser damit um gehen. Zumindest ist das meine Erfahrung und sicher stark von den jeweiligen Umständen abhängig, also Datenmengen und natürlich die verwendete Datenbank.
-
Da das ein interessantes Problem ist und mich der Ehrgeiz gepackt hat, habe ich das nochmal ausprobiert. Ich habe hier eine Datenbank mit einer Tabelle mit 18239 Sätzen in einer Postgresql-9.3.5-Datenbank unter Fedora 20. Also nicht wirklich groß aber mehr als ein paar Sätze. Der Aufbau der Tabelle ist ein wenig komplexer aber nah genug, dass ich die 2 Lösungen (ich habe die 2. fehlerhafte von Duplikateneliminator weg gelassen) miteinander vergleichen kann.
Der Zugriff mit dem Subquery dauert so im Schnitt 50ms. Mit meiner HAVING-Lösung ca. 39ms. Das bestätigt also meine Vermutung, dass die HAVING-Lösung schneller ist. Ich könnte jetzt natürlich noch die Zugriffspfade (explain) vergleichen, aber das erspare ich mir jetzt. Übrigens habe ich die Zeiten direkt mit psql gemessen. Da kann man das mit "\timing on" einschalten. Ach, ich liebe postgresql .
Also so ein Self-Join ist schon nützlich. Und wenn man das Prinzip des Self-Joins verstanden hat, dann ist das nicht wirklich schwerer zu entziffern als so ein correlated Subquery.
-
[quote="tntnet"]
hustbaer schrieb:
Ich gehe erst mal davon aus, dass die IDs pro user vergeben werden, so dass die IDs doppelt vor kommen können. Der Primärschlüssel wäre (userid, id)
Deine Annahme ist nicht korrekt -> siehe SQL Fiddle. Wobei da natürlich auch noch genug Interpretationsspielraum bleibt... ID scheint pk zu sein. Deshalb ist die zweite Lösung nicht falsch.
-
@tntnet
Ich sag' nicht dass ein self-join grundsätzlich schwer zu verstehen ist.
Es ist nur für mich schwer zu lesen wenn man es alles in eine Abfrage schreibt.
Genau so wie ich z.B. auch Abfragen schwer lesen kann die bei Joins kein "ON" verwenden, sondern alles im "WHERE" machen.Deine Abfrage lässt sich z.B. auch so schreiben, und so finde ich sie viel einfacher zu verstehen:
SELECT a.userID, a.ip FROM userips a INNER JOIN (SELECT x.userID, max(x.ID) max_ID FROM userips x GROUP BY x.userID) AS b ON a.userID = b.userID AND a.ID = b.max_ID
Bzw. mit CTEs auch so
; WITH max_ids AS ( SELECT userID, max(ID) max_ID FROM userips GROUP BY userID ) SELECT a.userID, a.ip FROM userips a INNER JOIN max_ids b ON a.userID = b.userID AND a.ID = b.max_ID
tntnet schrieb:
Das Selfjoin mit HAVING ist in der Regel schneller. Optimierer können besser damit um gehen.
Schneller als was?
Schneller als diewhere not exists
von Duplikateneliminator? Ziemlich sicher ja.
Schneller als diewhere ID in
Variante (falls sie verwendbar ist, also falls ID für sich genommen unique ist): ziemlich sicher nein.
Zumindest ist es meine Erfahrung dassWHERE ... IN
vom MSSQL Optimizer genau gleich behandelt wird wie ein JOIN. (Mit anderen DBMS fehlt mir die Erfahrung.)
-
hustbaer schrieb:
Schneller als was?
Schneller als diewhere not exists
von Duplikateneliminator? Ziemlich sicher ja.
Schneller als diewhere ID in
Variante (falls sie verwendbar ist, also falls ID für sich genommen unique ist): ziemlich sicher nein.
Zumindest ist es meine Erfahrung dassWHERE ... IN
vom MSSQL Optimizer genau gleich behandelt wird wie ein JOIN. (Mit anderen DBMS fehlt mir die Erfahrung.)Ich habe doch meine Annahme erläutert. Und das
WHERE ... IN
ist unter diesen Annahmen (die offensichtlich nicht korrekt waren) nicht anwendbar. Also ist mein self-join unter den Annahmen, die ich erläutert habe schneller als dieEXISTS
-Lösung, wie Du mir ja bestätigst. Kommen ich mit einer nicht correlated Subquery aus, dann ist das natürlich schneller.Und ja - Deine erste Lösung finde ich auch prima. Danke für den Vorschlag. Deine zweite Lösung ist kein Standard-SQL. Und wenn ich Standard-SQL verwenden kann, dann tue ich das.
-
OK, war mir nicht sicher welche Variante du als langsamer eingestuft hast.
tntnet schrieb:
Und ja - Deine erste Lösung finde ich auch prima. Danke für den Vorschlag.
Gerne
tntnet schrieb:
Deine zweite Lösung ist kein Standard-SQL. Und wenn ich Standard-SQL verwenden kann, dann tue ich das.
Huch? Wieso nicht? Was genau ist daran non-Standard?
Common table expression sind soweit ich weiss schon Standard SQL.
Siehe
http://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL#Common_table_expression
und
http://en.wikipedia.org/wiki/SQL:1999#Common_table_expressions_and_recursive_queries
(Bloss der olle MySQL kann sie halt (noch?) nicht.)Und mMn. eines der collsten SQL Features die ich lange lange Zeit übersehen habe.
Abgesehen von rekursiven CTEs kann man damit zwar nicht mehr machen als mit normalen Subqueries, aber man kann es damit viel, viel aufgeräumter/übersichtlicher machen.Bei diesem einfachen Beispiel zeigt sich das noch nicht so, aber wenn man mehrere, aufeinander aufbauende CTEs hat ... wenn man dann versucht das ganze ohne CTEs hinzuschreiben, sieht man schnell, wie furchtbar das würde
-
Ich sehe schon - bin bei SQL92 stehen geblieben. SQL99 bringt ja schon ein paar interessante Neuerungen mit .