Gibt es eine Möglichkeit, in einer SELECT-Anweisung auf den Wert der vorherigen Zeile zuzugreifen?

93

Ich muss die Differenz einer Spalte zwischen zwei Zeilen einer Tabelle berechnen. Gibt es eine Möglichkeit, dies direkt in SQL zu tun? Ich verwende Microsoft SQL Server 2008.

Ich suche so etwas:

SELECT value - (previous.value) FROM table

Stellen Sie sich vor, die "vorherige" Variable verweist auf die zuletzt ausgewählte Zeile. Natürlich werde ich mit einer solchen Auswahl am Ende n-1 Zeilen haben, die in einer Tabelle mit n Zeilen ausgewählt sind. Das ist wahrscheinlich nicht genau das, was ich brauche.

Ist das irgendwie möglich?

Edwin Jarvis
quelle
6
Nun, nur für einen Kommentar hinzufügen, der für neue Zuschauer weiter nützlich ist. SQL 2012 hat LAG und LEAD jetzt :) Siehe diesen Link blog.sqlauthority.com/2013/09/22/…
KD

Antworten:

61

In SQL ist kein Ordnungsbegriff integriert. Sie müssen daher nach einer Spalte sortieren, damit dies sinnvoll ist. Etwas wie das:

select t1.value - t2.value from table t1, table t2 
where t1.primaryKey = t2.primaryKey - 1

Wenn Sie wissen, wie man Dinge ordnet, aber nicht, wie man den vorherigen Wert unter Berücksichtigung des aktuellen Werts erhält (z. B. möchten Sie alphabetisch ordnen), dann kenne ich keine Möglichkeit, dies in Standard-SQL zu tun, aber die meisten SQL-Implementierungen haben Erweiterungen, um es zu tun.

Hier ist eine Möglichkeit für SQL Server, die funktioniert, wenn Sie Zeilen so anordnen können, dass sie jeweils unterschiedlich sind:

select  rank() OVER (ORDER BY id) as 'Rank', value into temp1 from t

select t1.value - t2.value from temp1 t1, temp1 t2 
where t1.Rank = t2.Rank - 1

drop table temp1

Wenn Sie Verbindungen trennen müssen, können Sie dem ORDER BY so viele Spalten wie nötig hinzufügen.

RossFabricant
quelle
Das ist in Ordnung, Ordnung ist kein Problem, ich habe es einfach aus dem Beispiel entfernt, um es einfacher zu machen. Ich werde es versuchen.
Edwin Jarvis
7
Dies setzt voraus, dass Primärschlüssel nacheinander generiert werden und Zeilen niemals gelöscht werden und die Auswahl keine andere Ordnungsklausel hat und und und ...
MartinStettner
Martin ist richtig. Obwohl dies in einigen Fällen funktionieren kann, müssen Sie wirklich genau definieren, was Sie unter "vorher" im geschäftlichen Sinne verstehen, vorzugsweise ohne sich auf eine generierte ID zu verlassen.
Tom H
Sie haben Recht, ich habe eine Verbesserung mit einer SQL Server-Erweiterung hinzugefügt.
RossFabricant
2
Als Antwort auf "Das ist in Ordnung, Ordnung ist kein Problem" ... Warum subtrahieren Sie dann nicht einfach einen Schiedswert in Ihrer Abfrage, da Sie dies tun, wenn Sie die Ordnung nicht berücksichtigen?
JohnFx
78

Verwenden Sie die Verzögerungsfunktion :

SELECT value - lag(value) OVER (ORDER BY Id) FROM table

Für IDs verwendete Sequenzen können Werte überspringen, sodass Id-1 nicht immer funktioniert.

Hans Ginzel
quelle
1
Dies ist eine PostgreSQL-Lösung. Die Frage betrifft MSSQL. MSSQL hat eine solche Funktion in Versionen 2012+ ( msdn.microsoft.com/en-us/en-en/library/hh231256(v=sql.120).aspx )
Kromster
10
@KromStern Nicht nur PostgreSQL-Lösung. SQL Window-Funktionen wurden im SQL: 2003- Standard eingeführt.
Hans Ginzel
Die LAG-Funktion kann drei Parameter annehmen : LAG(ExpressionToSelect, NumberOfRowsToLag, DefaultValue). Die Standardanzahl der zu verzögerenden Zeilen ist 1, aber Sie können dies und den Standardwert angeben, der ausgewählt werden soll, wenn eine Verzögerung nicht möglich ist, da Sie sich am Anfang des Satzes befinden.
Vaindil
29

Oracle, PostgreSQL, SQL Server und viele mehr RDBMS - Motoren haben analytische Funktionen aufgerufen LAGund LEADdas tun , um diese sehr Sache.

In SQL Server vor 2012 müssen Sie Folgendes tun:

SELECT  value - (
        SELECT  TOP 1 value
        FROM    mytable m2
        WHERE   m2.col1 < m1.col1 OR (m2.col1 = m1.col1 AND m2.pk < m1.pk)
        ORDER BY 
                col1, pk
        )
FROM mytable m1
ORDER BY
      col1, pk

, wo COL1ist die Spalte, nach der Sie bestellen.

Wenn Sie einen Index haben, (COL1, PK)wird diese Abfrage erheblich verbessert.

Quassnoi
quelle
14
SQL Server 2012 verfügt jetzt auch über LAG und LEAD.
ErikE
Das Hana SQL-Skript unterstützt auch LAG und LEAD.
Mik
Nur um den Zuschauern, die hier angekommen sind, einen weiteren Kommentar hinzuzufügen, um dies in Hive zu tun. Es hat auch LAG- und LEAD-Funktionen. Dokumentation hier: cwiki.apache.org/confluence/display/Hive/…
Jaime Caffarel
27
WITH CTE AS (
  SELECT
    rownum = ROW_NUMBER() OVER (ORDER BY columns_to_order_by),
    value
  FROM table
)
SELECT
  curr.value - prev.value
FROM CTE cur
INNER JOIN CTE prev on prev.rownum = cur.rownum - 1
Jeremy Stein
quelle
Es funktioniert korrekt, wenn die Abfrage keine Gruppierung enthält. Was ist jedoch, wenn wir Werte nur innerhalb einer Gruppe vom vorherigen Wert abziehen möchten, sagen wir dieselbe EmployeeID. Wie können wir das tun? Coz, das dies ausführt, funktioniert nur für die obersten 2 Zeilen jeder Gruppe und nicht für die restlichen Zeilen in dieser Gruppe. Dafür habe ich diesen Code in der while-Schleife ausgeführt, aber das scheint sehr langsam zu sein. Gibt es einen anderen Ansatz, den wir in diesem Szenario könnten? Und das auch nur in SQL Server 2008?
Hemant Sisodia
10

LINKS VERBINDEN Sie die Tabelle für sich selbst, wobei die Verknüpfungsbedingung so ausgearbeitet ist, dass die in der verknüpften Version der Tabelle übereinstimmende Zeile für Ihre spezielle Definition von "Vorherige" eine Zeile früher ist.

Update: Zuerst dachte ich, Sie möchten alle Zeilen mit NULL-Werten für die Bedingung beibehalten, in der es keine vorherige Zeile gab. Wenn Sie es noch einmal lesen, möchten Sie nur, dass die Zeilen ausgesondert werden. Daher sollten Sie eine innere Verknüpfung anstelle einer linken Verknüpfung verwenden.


Aktualisieren:

Neuere Versionen von SQL Server verfügen auch über die Funktionen LAG und LEAD Windowing, die auch hierfür verwendet werden können.

Joel Coehoorn
quelle
3
select t2.col from (
select col,MAX(ID) id from 
(
select ROW_NUMBER() over(PARTITION by col order by col) id ,col from testtab t1) as t1
group by col) as t2
user1920851
quelle
2

Die ausgewählte Antwort funktioniert nur, wenn die Sequenz keine Lücken aufweist. Wenn Sie jedoch eine automatisch generierte ID verwenden, kann es aufgrund von zurückgesetzten Einfügungen zu Lücken in der Sequenz kommen.

Diese Methode sollte funktionieren, wenn Sie Lücken haben

declare @temp (value int, primaryKey int, tempid int identity)
insert value, primarykey from mytable order by  primarykey

select t1.value - t2.value from @temp  t1
join @temp  t2 
on t1.tempid = t2.tempid - 1
HLGEM
quelle