C'è un modo per accedere al valore "riga precedente" in un'istruzione SELECT?


93

Devo calcolare la differenza di una colonna tra due righe di una tabella. C'è un modo per farlo direttamente in SQL? Sto usando Microsoft SQL Server 2008.

Sto cercando qualcosa di simile:

SELECT value - (previous.value) FROM table

Immaginando che la variabile "precedente" faccia riferimento all'ultima riga selezionata. Ovviamente con una selezione del genere finirò con n-1 righe selezionate in una tabella con n righe, probabilmente non è una, in realtà è esattamente ciò di cui ho bisogno.

È possibile in qualche modo?


6
Bene, aggiungo solo un commento utile per i nuovi spettatori. SQL 2012 ha LAG e LEAD ora :) Fare riferimento a questo collegamento blog.sqlauthority.com/2013/09/22/…
KD

Risposte:


61

SQL non ha una nozione di ordine incorporata, quindi è necessario ordinare per alcune colonne affinché questo sia significativo. Qualcosa come questo:

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

Se sai come ordinare le cose ma non come ottenere il valore precedente dato quello corrente (ad esempio, vuoi ordinare alfabeticamente), allora non conosco un modo per farlo in SQL standard, ma la maggior parte delle implementazioni SQL avrà estensioni per farlo.

Ecco un modo per SQL Server che funziona se puoi ordinare le righe in modo che ognuna sia distinta:

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

Se hai bisogno di rompere i legami, puoi aggiungere tutte le colonne necessarie a ORDER BY.


Va bene, l'ordine non è un problema, l'ho semplicemente rimosso dall'esempio per renderlo più semplice, lo proverò.
Edwin Jarvis

7
che presuppone che le chiavi primarie vengano generate in sequenza e le righe non vengano mai eliminate e la selezione non abbia altre clausole di ordine e and e ...
MartinStettner

Martin ha ragione. Sebbene questo possa funzionare in alcuni casi, è davvero necessario definire esattamente cosa intendi per "precedente" in senso commerciale, preferibilmente senza fare affidamento su un ID generato.
Tom H

Hai ragione, ho aggiunto un miglioramento utilizzando un'estensione di SQL Server.
RossFabricant

2
In risposta a "Va bene, l'ordine non è un problema" ... Allora perché non sottrarre semplicemente un valore arbitrario nella query poiché è quello che stai facendo se non consideri l'ordine?
JohnFx

79

Usa la funzione lag :

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

Le sequenze utilizzate per gli ID possono saltare i valori, quindi Id-1 non funziona sempre.


1
Questa è la soluzione PostgreSQL. La domanda riguarda MSSQL. MSSQL ha tale funzione nelle versioni 2012+ ( msdn.microsoft.com/en-us/en-en/library/hh231256(v=sql.120).aspx )
Kromster

10
@KromStern Non solo soluzione PostgreSQL. Le funzioni della finestra SQL sono state introdotte nello standard SQL: 2003 .
Hans Ginzel

La funzione GAL può prendere tre parametri: LAG(ExpressionToSelect, NumberOfRowsToLag, DefaultValue). Il numero predefinito di righe da ritardare è 1, ma puoi specificarlo e il valore predefinito da selezionare quando non è possibile ritardare poiché sei all'inizio del set.
vaindil

29

Oracle, PostgreSQL, SQL Server e molti altri motori RDBMS hanno funzioni analitiche chiamate LAGe LEADche fanno proprio questo.

In SQL Server prima del 2012 è necessario eseguire le seguenti operazioni:

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

, dov'è COL1la colonna in base alla quale stai ordinando.

Avere un indice (COL1, PK)attivo migliorerà notevolmente questa query.


14
SQL Server 2012 ora ha anche LAG e LEAD.
ErikE

Lo script Hana SQL supporta anche LAG e LEAD.
mik

Solo per aggiungere un altro commento agli spettatori che sono arrivati ​​qui cercando di farlo in Hive. Ha anche funzioni LAG e LEAD. Documentazione qui: 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

Funziona correttamente se non vi è alcun raggruppamento nella query, ma cosa succede se vogliamo sottrarre valori dal valore precedente solo all'interno di un gruppo, diciamo lo stesso EmployeeID, allora come possiamo farlo? Perché l'esecuzione di questo funziona solo per le prime 2 righe di ogni gruppo e non per il resto delle righe in quel gruppo. Per questo, ho usato l'esecuzione di questo codice nel ciclo while, ma sembra essere molto lento. Qualche altro approccio che potremmo in questo scenario? E anche questo solo in SQL Server 2008?
Hemant Sisodia

10

LEFT JOIN la tabella a se stessa, con la condizione di join elaborata in modo che la riga abbinata nella versione unita della tabella sia una riga precedente, per la tua particolare definizione di "precedente".

Aggiornamento: all'inizio stavo pensando che avresti voluto mantenere tutte le righe, con NULL per la condizione in cui non c'erano righe precedenti. Leggendolo di nuovo vuoi solo che le righe vengano eliminate, quindi dovresti un join interno piuttosto che un join sinistro.


Aggiornare:

Le versioni più recenti di Sql Server hanno anche le funzioni LAG e LEAD Windowing che possono essere utilizzate anche per questo.


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

2

La risposta selezionata funzionerà solo se non ci sono spazi vuoti nella sequenza. Tuttavia, se si utilizza un ID generato automaticamente, è probabile che ci siano delle lacune nella sequenza a causa di inserimenti di cui è stato eseguito il rollback.

Questo metodo dovrebbe funzionare se hai delle lacune

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.