Apakah ada cara untuk mengakses nilai "baris sebelumnya" dalam pernyataan SELECT?

96

Saya perlu menghitung selisih kolom antara dua baris tabel. Apakah ada cara agar saya dapat melakukan ini secara langsung di SQL? Saya menggunakan Microsoft SQL Server 2008.

Saya mencari sesuatu seperti ini:

SELECT value - (previous.value) FROM table

Bayangkan variabel "sebelumnya" merujuk ke baris yang terakhir dipilih. Tentu saja dengan pemilihan seperti itu saya akan berakhir dengan n-1 baris yang dipilih dalam tabel dengan n baris, itu bukan mungkin, sebenarnya itulah yang saya butuhkan.

Apakah itu mungkin dalam beberapa hal?

Edwin Jarvis
sumber
6
Baiklah hanya menambahkan komentar yang berguna untuk pemirsa baru selanjutnya. SQL 2012 memiliki LAG dan LEAD sekarang :) Lihat tautan ini blog.sqlauthority.com/2013/09/22/…
KD

Jawaban:

65

SQL tidak memiliki gagasan urutan bawaan, jadi Anda perlu mengurutkan berdasarkan beberapa kolom agar ini bermakna. Sesuatu seperti ini:

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

Jika Anda tahu cara memesan sesuatu tetapi tidak tahu cara mendapatkan nilai sebelumnya yang diberikan nilai saat ini (EG, Anda ingin mengurutkan menurut abjad) maka saya tidak tahu cara melakukannya dalam SQL standar, tetapi sebagian besar implementasi SQL akan memilikinya ekstensi untuk melakukannya.

Berikut adalah cara untuk SQL server yang berfungsi jika Anda dapat mengurutkan baris sedemikian rupa sehingga masing-masing baris berbeda:

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

Jika Anda perlu memutuskan hubungan, Anda dapat menambahkan kolom sebanyak yang diperlukan ke ORDER BY.

RossFabricant
sumber
Tidak apa-apa, pesanan bukanlah masalah, saya hanya menghapusnya dari contoh untuk membuatnya lebih sederhana, saya akan mencobanya.
Edwin Jarvis
7
yang mengasumsikan, bahwa kunci primer dihasilkan secara berurutan dan baris tidak pernah dihapus dan pilihan tidak memiliki klausa urutan lain dan dan dan ...
MartinStettner
Martin benar. Meskipun ini mungkin berhasil dalam beberapa kasus, Anda benar-benar perlu mendefinisikan dengan tepat apa yang Anda maksud dengan "sebelumnya" dalam pengertian bisnis, lebih disukai tanpa bergantung pada ID yang dihasilkan.
Tom H
Anda benar, saya menambahkan peningkatan menggunakan ekstensi SQL Server.
RossFabricant
2
Menanggapi "Tidak apa-apa, pesanan bukanlah masalah" ... Lalu mengapa Anda tidak mengurangi nilai arbitrase dalam kueri Anda karena itulah yang Anda lakukan jika Anda tidak mempertimbangkan pesanan?
JohnFx
85

Gunakan fungsi lag :

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

Urutan yang digunakan untuk Id dapat melewati nilai, jadi Id-1 tidak selalu berfungsi.

Hans Ginzel
sumber
1
Ini adalah solusi PostgreSQL. Pertanyaannya adalah tentang MSSQL. MSSQL memiliki fungsi seperti itu di versi 2012+ ( msdn.microsoft.com/en-us/en-en/library/hh231256(v=sql.120).aspx )
Kromster
10
@Krom Tidak hanya solusi PostQL. Fungsi SQL Window diperkenalkan dalam standar SQL: 2003 .
Hans Ginzel
Fungsi LAG dapat mengambil tiga parameter: LAG(ExpressionToSelect, NumberOfRowsToLag, DefaultValue). Jumlah baris default yang akan ditunda adalah 1, tetapi Anda dapat menentukannya dan nilai default untuk dipilih jika tidak memungkinkan untuk tertinggal karena Anda berada di awal set.
sia
30

Oracle, PostgreSQL, SQL Server, dan banyak lagi mesin RDBMS memiliki fungsi analitik yang dipanggil LAGdan LEADmelakukan hal ini.

Di SQL Server sebelum 2012, Anda perlu melakukan hal berikut:

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

, di mana COL1kolom tempat Anda memesan.

Memiliki indeks (COL1, PK)akan sangat meningkatkan kueri ini.

Quassnoi
sumber
14
SQL Server 2012 sekarang memiliki LAG dan LEAD juga.
ErikE
Skrip Hana SQL juga mendukung LAG dan LEAD.
mik
Hanya untuk menambahkan komentar lain untuk pemirsa yang datang ke sini mencari melakukan itu di Hive. Ia juga memiliki fungsi LAG dan LEAD. Dokumentasi di sini: 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
sumber
Ini berfungsi dengan benar jika tidak ada pengelompokan dalam kueri, tetapi bagaimana jika kita ingin mengurangi nilai dari nilai sebelumnya hanya dalam satu grup, katakanlah EmployeeID yang sama, lalu bagaimana kita bisa melakukannya? Karena menjalankan ini hanya berfungsi untuk 2 baris teratas dari setiap grup dan tidak untuk sisa baris dalam grup itu. Untuk ini, saya menggunakan menjalankan kode ini di while loop, tetapi itu tampaknya sangat lambat. Adakah pendekatan lain yang dapat kami lakukan dalam skenario ini? Dan itu juga hanya di SQL Server 2008?
Hemant Sisodia
10

LEFT JOIN tabel itu sendiri, dengan kondisi join berhasil sehingga baris yang cocok dalam versi tabel yang digabungkan adalah satu baris sebelumnya, untuk definisi khusus Anda tentang "sebelumnya".

Pembaruan: Awalnya saya berpikir Anda ingin menyimpan semua baris, dengan NULL untuk kondisi di mana tidak ada baris sebelumnya. Membacanya lagi Anda hanya ingin baris itu disingkirkan, jadi Anda harus gabungan dalam daripada gabungan kiri.


Memperbarui:

Versi Sql Server yang lebih baru juga memiliki fungsi LAG dan LEAD Windowing yang dapat digunakan untuk ini juga.

Joel Coehoorn
sumber
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
pengguna1920851
sumber
2

Jawaban yang dipilih hanya akan berfungsi jika tidak ada celah dalam urutan. Namun jika Anda menggunakan id yang dibuat secara otomatis, kemungkinan ada celah dalam urutan karena penyisipan yang dibatalkan.

Metode ini akan berfungsi jika Anda memiliki celah

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
sumber