Felix Riesterer: GROUP BY - höchsten Wert in anderer Spalte nicht verlieren

Liebe Datenbankerfahrene,

ich habe (stark vereinfacht) folgende Situation:

|id|update|login| |---| |1|1|Alex |2|1|Bob |3|1|Chris |4|1|Sue |5|2|Alex |6|2|Chris |7|2|Sue

Es wird immer wieder einmal ein Update mit einer laufenden Nummer an User ausgegeben und in obiger Tabelle festgehalten, wenn ein User ein bestimmtes Update erhalten hat. Man beachte, dass Bob das Update 2 nicht erhalten hat, daher fehlt zwischen ID(5) und ID(6) ein passender Eintrag. Statt der Klartextnamen hier ist login selbstverständlich ein anderer String-Wert - eben der Benutzername.

Ja, den ID-Wert könnte man sich sparen, wenn man den Primärschlüssel über die beiden Spalten update und user definieren würde. In meinem Fall aber benötige ich die ID an anderer Stelle wieder, daher habe ich einen Primärschlüssel mit AUTO_INCREMENT-Wert.

Nun zu meinem Problem.

Ich möchte ausgeben, wer zuletzt welches Update erhalten hat. Dazu erwarte ich für dieses Beispiel folgendes Ergebnis:

|id|update|login| |---| |2|1|Bob |5|2|Alex |6|2|Chris |7|2|Sue

Mit welcher Abfrage kann ich das erreichen? Mein bisheriges Verständnis von SQL, insbesondere mit GROUP BY, setzt hier leider mit "ist unmöglich" aus. So sieht mein bisheriger Ansatz aus, der jeweils die ersten Einträge aus der Tabelle selektiert und damit immer das älteste anstatt des neuesten Updates listet:

SELECT `id`, `update`, `login`
FROM `user_updated_log`
WHERE 1
GROUP BY `login`
ORDER BY `update`

Offensichtlich wird die Information zu update beim Grouping verworfen, sodass die anschließende Sortierung nach update keinen Sinn mehr hat. Wie kann ich das verhindern und vor dem Grouping eine Sortierung erreichen, die den jeweils höchsten update-Wert zuerst listet?

Liebe Grüße,

Felix Riesterer.

akzeptierte Antworten

  1. Tach!

    Ich möchte ausgeben, wer zuletzt welches Update erhalten hat. Dazu erwarte ich für dieses Beispiel folgendes Ergebnis:

    Wenn die update-Werte Zahlen oder andere aufsteigende Werte sind, dann MAX().

    SELECT `id`, `update`, `login`
    FROM `user_updated_log`
    WHERE 1
    GROUP BY `login`
    ORDER BY `update`
    

    Offensichtlich wird die Information zu update beim Grouping verworfen, sodass die anschließende Sortierung nach update keinen Sinn mehr hat.

    Nein, da wird nichts verworfen, das ist (dokumentiertes) MySQL-Verhalten. In anderen SQL-Dialekten darf man keine Felder selektieren, über die nicht gruppiert wurde. MySQL hingegen nimmt sich einfach irgendeinen Wert aus der Gruppe. Das kann man nur in Situationen verwenden, in denen man Redundanzen derart in der Datenmenge hat, dass da überall dieselben Daten drinstehen. Zum Beispiel in nicht normalisierten Daten.

    dedlfix.

    1. Tach!

      Nachtrag:

      Offensichtlich wird die Information zu update beim Grouping verworfen, sodass die anschließende Sortierung nach update keinen Sinn mehr hat.

      Die SELECT-Klausel wird zwischen GROUP BY und ORDER BY ausgewertet. Das Select nimmt sich also irgendeinen Wert und erst dann findet de Sortierung statt. Der Zug ist da also bereits abgefahren. Das Verhalten ist insofern auch ungünstig, als dass da im Test zufällig die gewünschten Werte entstehen können und man sich da sicher wiegt.

      Außerdem ist noch anzumerken, dass du mit dem MAX()-Ansatz nicht ermitteln kannst, ob zwischendurch noch weitere Updates fehlen. Mit COUNT() kann man zumindest feststellen, ob die Menge abweicht.

      dedlfix.

      1. Lieber dedlfix,

        Ich möchte ausgeben, wer zuletzt welches Update erhalten hat. Dazu benötige ich wenigstens die id aus der Tabelle. Der Rest lässt sich ja dann in einer WHERE ... IN()-Abfrage ermitteln, die man "drüberstülpt".

        Liebe Grüße,

        Felix Riesterer.

    2. Lieber dedlfix,

      Du meinst, ich dürfte update in anderen SQL-Dialekten nicht selektieren, da ich darüber gruppiere?

      Und wo setze ich MAX() genau ein? Etwa so?

      SELECT `id`, `update`, `login`
      FROM `user_updated_log`
      WHERE (
          SELECT MAX(`update`)
          FROM `user_updated_log`
          WHERE `login`=`login`
      )
      GROUP BY `login`
      ORDER BY `update`
      

      Oder ist mein Ansatz völlig falsch und ich sollte grundsätzlich anders die Informationen herauslesen?

      Liebe Grüße,

      Felix Riesterer.

      1. Tach!

        Du meinst, ich dürfte update in anderen SQL-Dialekten nicht selektieren, da ich darüber gruppiere?

        + nicht. Und auch id kannst du so nicht ermitteln, weil auch das ein Zufallswert aus der Gruppe ist. Mann kann auch nicht davon ausgehen, dass sich das am MAX() orientiert, denn in Abfragen, die MAX, MIN und AVG ermitteln, welcher Wert soll dann in id stehen?

        Und wo setze ich MAX() genau ein? Etwa so?

        SELECT `id`, `update`, `login`
        FROM `user_updated_log`
        WHERE (
            SELECT MAX(`update`)
            FROM `user_updated_log`
            WHERE `login`=`login`
        )
        GROUP BY `login`
        ORDER BY `update`
        

        Den MAX-Wert der Gruppe ermittelt man so:

        SELECT `login`, MAX(`update`) FROM `user_updated_log` GROUP BY `login`
        

        Aber eigentlich brauchst du die Gruppierung nicht, denn es sollte so gehen (ungetestet):

        SELECT * FROM user_updated_log u WHERE `update` = (SELECT MAX(m.`update`) FROM `user_updated_log` m WHERE m.`login` = u.`login`)
        

        dedlfix.

        1. Lieber dedlfix,

          DAS war der entscheidende Hinweis:

          SELECT * FROM user_updated_log u WHERE `update` = (SELECT MAX(m.`update`) FROM `user_updated_log` m WHERE m.`login` = u.`login`)
          

          So erhalte ich genau das, was ich will. Super!

          Herzlichsten Dank und liebe Grüße,

          Felix Riesterer.

  2. Hallo Felix Riesterer,

    Ja, den ID-Wert könnte man sich sparen,

    imho ist es guter Programmierstil, jeder Tabelle eine ID zu geben.

    Mit welcher Abfrage kann ich das erreichen? Mein bisheriges Verständnis von SQL, insbesondere mit GROUP BY, setzt hier leider mit "ist unmöglich" aus. So sieht mein bisheriger Ansatz aus, der jeweils die ersten Einträge aus der Tabelle selektiert und damit immer das älteste anstatt des neuesten Updates listet:

    SELECT `id`, max(`update`), `login`
    FROM `user_updated_log`
    GROUP BY `login`
    

    Bis demnächst
    Matthias

    --
    Dieses Forum nutzt Markdown. Im Wiki erhalten Sie Hilfe bei der Formatierung Ihrer Beiträge.
    1. Tach!

      SELECT `id`, max(`update`), `login`
      FROM `user_updated_log`
      GROUP BY `login`
      

      Nö, id ist ein zufälliger Wert, nicht zwangsläufig der zu MAX() passende. Erweitere mal die Query um MIN(), dann solltest du das Problem daran sehen.

      dedlfix.

      1. Hallo dedlfix,

        SELECT `id`, max(`update`), `login`
        FROM `user_updated_log`
        GROUP BY `login`
        

        Nö, id ist ein zufälliger Wert, nicht zwangsläufig der zu MAX() passende. Erweitere mal die Query um MIN(), dann solltest du das Problem daran sehen.

        Ja, das habe ich (nach meiner Antwort) in deinem Beitrag gelesen. Allerdings wird mMn. die ID doch gar nicht benötigt, auch wenn Felix das schrieb, da es ja eigentlich um die Loginnamen geht. Oder passt dann auch das Maximum nicht zum Login?

        Bis demnächst
        Matthias

        --
        Dieses Forum nutzt Markdown. Im Wiki erhalten Sie Hilfe bei der Formatierung Ihrer Beiträge.
        1. Tach!

          SELECT `id`, max(`update`), `login`
          FROM `user_updated_log`
          GROUP BY `login`
          

          Nö, id ist ein zufälliger Wert, nicht zwangsläufig der zu MAX() passende. Erweitere mal die Query um MIN(), dann solltest du das Problem daran sehen.

          Ja, das habe ich (nach meiner Antwort) in deinem Beitrag gelesen. Allerdings wird mMn. die ID doch gar nicht benötigt, auch wenn Felix das schrieb, da es ja eigentlich um die Loginnamen geht. Oder passt dann auch das Maximum nicht zum Login?

          Doch, das passt, denn das ist ja die Gruppierbedingung. Wenn du alle Äpfel auf den einen Haufen und alle Birnen auf einen anderen legst, dann sind das zwei eindeutige Mengen und die jeweils größte Frucht kann nur ein Apfel und eine Birne sein.

          dedlfix.

          1. Hallo dedlfix,

            Doch, das passt, denn das ist ja die Gruppierbedingung.

            Also:

            SELECT max(`update`), `login`
            FROM `user_updated_log`
            GROUP BY `login`
            

            wenn die Datenbank nicht zu stark vereinfacht ist.

            Bis demnächst
            Matthias

            --
            Dieses Forum nutzt Markdown. Im Wiki erhalten Sie Hilfe bei der Formatierung Ihrer Beiträge.