2009-04-02 21 views
55

Ho bisogno di calcolare la differenza di una colonna tra due righe di una tabella, esiste un modo per farlo direttamente in SQL? Sto utilizzando Microsoft SQL Server 2008.Esiste un modo per accedere al valore "riga precedente" in un'istruzione SELECT?

Sto cercando qualcosa di simile:

SELECT value - (previous.value) FROM table 

Immaginando che il "precedente" di riferimento variabile l'ultima riga selezionata. Ovviamente con una selezione come questa finirò con le righe n-1 selezionate in una tabella con n righe, non è un probabilmente, in realtà è esattamente ciò di cui ho bisogno.

È possibile in qualche modo?

+5

Beh, aggiungendo semplicemente un commento utile per i nuovi spettatori. SQL 2012 ha ora LAG e LEAD :) Fai riferimento a questo link http://blog.sqlauthority.com/2013/09/22/sql-server-how-to-access-the-previous-row-and-next-row- value-in-select-statement/ –

risposta

39

SQL non ha alcun concetto incorporato di ordine, quindi è necessario ordinare per alcune colonne perché ciò sia significativo. Qualcosa di simile a 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 data quella attuale (EG, volete ordinare in ordine alfabetico) quindi non so di un modo di fare quello in SQL standard, ma la maggior parte delle implementazioni SQL avrà estensioni per farlo.

Ecco un modo per il server SQL che funziona, se è possibile ordinare le righe in modo tale che ognuno è diverso:

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 avete bisogno di rompere i legami, è possibile aggiungere come anni necessari alla ORDINE DI.

+0

Va bene, l'ordine non è un problema, l'ho appena rimosso dall'esempio per renderlo più semplice, ci proverò. –

+4

che presuppone che le chiavi primarie siano generate in modo sequenziale e che le righe non vengano mai eliminate e che la selezione non abbia altre clausole di ordine e e ... – MartinStettner

+0

Martin sia corretto. Sebbene ciò possa funzionare in alcuni casi, è necessario definire esattamente cosa intendi per "precedente" in senso lato, preferibilmente senza fare affidamento su un ID generato. –

8

LEFT UNISCONO la tabella a se stessa, con la condizione di join elaborata in modo che la riga corrispondente nella versione unita del tavolo sia una riga precedente, per la particolare definizione di "precedente".

Aggiornamento: inizialmente pensavo che avresti voluto mantenere tutte le righe, con NULL per la condizione in cui non c'era nessuna riga precedente. Leggendolo di nuovo vuoi solo che le righe vengano abbattute, quindi dovresti un join interno piuttosto che un join sinistro.


Aggiornamento:

recenti versioni di SQL Server hanno anche il GAL e funzioni Windowing piombo che può essere utilizzato per questo, anche.

24

Oracle, PostgreSQL, SQL Server e molti altri motori RDBMS dispongono di funzioni di analisi chiamate LAG e LEAD che fanno proprio questo.

In SQL Server prima del 2012 avresti bisogno di fare quanto segue:

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 

, dove COL1 è la colonna che si sta ordinando da.

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

+13

SQL Server 2012 ora dispone anche di LAG e LEAD. – ErikE

+0

Lo script Hana SQL supporta anche LAG e LEAD. – mik

+0

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: https://cwiki.apache.org/confluence/display/Hive/LanguageManual+WindowingAndAnalytics –

2

La risposta selezionata funzionerà solo se non ci sono spazi vuoti nella sequenza. Tuttavia, se si utilizza un ID generato automaticamente, è probabile che vi siano degli spazi vuoti nella sequenza dovuti agli inserti che sono stati ripristinati.

Questo metodo dovrebbe funzionare se si dispone di spazi

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 
23
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 
+0

Funziona correttamente se non c'è 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? Coz che esegue 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. Qualunque altro approccio potremmo in questo scenario? E anche quello solo in SQL Server 2008? –

2
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 
26

Utilizzare la funzione lag:

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

sequenze utilizzate per Ids può ignorare i valori, in modo da Id-1 non non sempre funziona.

+1

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

+2

@KromStern Soluzione PostgreSQL non solo. [Funzioni finestra SQL] (https://www.simple-talk.com/sql/t-sql-programming/window-functions-in-sql/) sono state introdotte in [SQL: 2003] (https: // en. wikipedia.org/wiki/SQL:2003) standard. –

+1

In altre parole, funzionerà sul caso OP? – Kromster

Problemi correlati