653 Stimmen

Abrufen der Zeile, die den Max-Wert für eine Spalte enthält

Tabelle:

UserId, Value, Date.

Ich möchte die UserId, Wert für die max (Datum) für jede UserId zu erhalten. Das heißt, der Wert für jede UserId, die das späteste Datum hat. Gibt es eine Möglichkeit, dies einfach in SQL zu tun? (Vorzugsweise Oracle)

更新しました。 Entschuldigung für etwaige Unklarheiten: Ich muss ALLE UserIds abrufen. Aber für jede UserId, nur die Zeile, wo dieser Benutzer das neueste Datum hat.

15voto

mancaus Punkte 2923

Ich weiß, dass Sie nach Oracle gefragt haben, aber in SQL 2005 verwenden wir dies jetzt:

-- Single Value
;WITH ByDate
AS (
SELECT UserId, Value, ROW_NUMBER() OVER (PARTITION BY UserId ORDER BY Date DESC) RowNum
FROM UserDates
)
SELECT UserId, Value
FROM ByDate
WHERE RowNum = 1

-- Multiple values where dates match
;WITH ByDate
AS (
SELECT UserId, Value, RANK() OVER (PARTITION BY UserId ORDER BY Date DESC) Rnk
FROM UserDates
)
SELECT UserId, Value
FROM ByDate
WHERE Rnk = 1

8voto

user11318 Punkte 9115

Ich habe kein Oracle, um es zu testen, aber die effizienteste Lösung ist die Verwendung analytischer Abfragen. Sie sollte etwa so aussehen:

SELECT DISTINCT
    UserId
  , MaxValue
FROM (
    SELECT UserId
      , FIRST (Value) Over (
          PARTITION BY UserId
          ORDER BY Date DESC
        ) MaxValue
    FROM SomeTable
  )

Ich vermute, dass Sie die äußere Abfrage loswerden und die innere Abfrage unterscheiden können, aber ich bin mir nicht sicher. In der Zwischenzeit weiß ich, dass dies funktioniert.

Wenn Sie mehr über analytische Abfragen erfahren möchten, empfehle ich Ihnen die Lektüre http://www.orafaq.com/node/55 y http://www.akadia.com/services/ora_analytic_functions.html . Hier ist die kurze Zusammenfassung.

Unter der Haube sortieren analytische Abfragen den gesamten Datensatz und verarbeiten ihn dann sequentiell. Bei der Verarbeitung partitionieren Sie den Datensatz nach bestimmten Kriterien und betrachten dann für jede Zeile ein Fenster (standardmäßig den ersten Wert in der Partition der aktuellen Zeile - dieser Standardwert ist auch der effizienteste) und können Werte mithilfe einer Reihe von analytischen Funktionen berechnen (die Liste der Funktionen ist den Aggregatfunktionen sehr ähnlich).

In diesem Fall wird die innere Abfrage folgendermaßen durchgeführt. Der gesamte Datensatz wird nach UserId und dann nach Datum DESC sortiert. Dann wird er in einem Durchgang verarbeitet. Für jede Zeile geben Sie die UserId und das erste Datum zurück, das für diese UserId gesehen wurde (da die Daten DESC sortiert sind, ist das das maximale Datum). So erhalten Sie Ihre Antwort mit doppelten Zeilen. Die äußere DISTINCT zerdrückt dann die Duplikate.

Dies ist kein besonders spektakuläres Beispiel für analytische Abfragen. Ein weitaus größerer Gewinn wäre es, wenn Sie eine Tabelle mit Finanzeinnahmen nehmen und für jeden Benutzer und jede Einnahme eine laufende Gesamtsumme dessen berechnen, was er bezahlt hat. Analytische Abfragen lösen das effizient. Andere Lösungen sind weniger effizient. Deshalb sind sie auch Teil des SQL-Standards 2003. (Leider hat Postgres sie noch nicht. Grrr...)

7voto

Cito Punkte 4934

Mit PostgreSQL 8.4 oder höher können Sie dies verwenden:

select user_id, user_value_1, user_value_2
  from (select user_id, user_value_1, user_value_2, row_number()
          over (partition by user_id order by user_date desc) 
        from users) as r
  where r.row_number=1

7voto

wcw Punkte 71

Wäre eine QUALIFY-Klausel nicht das Einfachste und Beste zugleich?

select userid, my_date, ...
from users
qualify rank() over (partition by userid order by my_date desc) = 1

Zum Vergleich: Auf Teradata läuft ein Test dieser Art in 17 Sekunden mit dieser QUALIFY-Version und in 23 Sekunden mit der 'Inline-Ansicht'/Aldridge-Lösung #1.

7voto

Gurwinder Singh Punkte 37047

Oracle 12c+ können Sie verwenden Oben n Abfragen zusammen mit der analytischen Funktion rank um dies in aller Kürze zu erreichen ohne Unterabfragen:

select *
from your_table
order by rank() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;

Der obige Befehl gibt alle Zeilen mit max my_date pro Benutzer zurück.

Wenn Sie nur eine Zeile mit maximalem Datum wünschen, ersetzen Sie die rank con row_number :

select *
from your_table
order by row_number() over (partition by user_id order by my_date desc)
fetch first 1 row with ties;

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X