Có cách nào để truy cập giá trị "hàng trước" trong câu lệnh SELECT không?


93

Tôi cần tính toán sự khác biệt của một cột giữa hai dòng của bảng. Có cách nào tôi có thể thực hiện việc này trực tiếp trong SQL không? Tôi đang sử dụng Microsoft SQL Server 2008.

Tôi đang tìm kiếm một cái gì đó như thế này:

SELECT value - (previous.value) FROM table

Tưởng tượng rằng biến "trước đó" tham chiếu đến hàng được chọn mới nhất. Tất nhiên với một lựa chọn như vậy, tôi sẽ kết thúc với n-1 hàng được chọn trong một bảng có n hàng, đó không phải là một lẽ, thực sự là chính xác những gì tôi cần.

Điều đó có thể theo một cách nào đó?


6
Cũng chỉ thêm cho một nhận xét hữu ích cho người xem mới hơn nữa. SQL 2012 có LAG và LEAD ngay bây giờ :) Tham khảo liên kết này blog.sqlauthority.com/2013/09/22/…
KD

Câu trả lời:


61

SQL không có khái niệm về thứ tự được xây dựng, vì vậy bạn cần phải sắp xếp theo một số cột để điều này có ý nghĩa. Một cái gì đó như thế này:

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

Nếu bạn biết cách sắp xếp mọi thứ nhưng không biết cách lấy giá trị trước đó cho giá trị hiện tại (EG, bạn muốn sắp xếp theo thứ tự bảng chữ cái) thì tôi không biết cách nào để làm điều đó trong SQL chuẩn, nhưng hầu hết các triển khai SQL sẽ có phần mở rộng để thực hiện nó.

Đây là một cách để máy chủ SQL hoạt động nếu bạn có thể sắp xếp các hàng sao cho mỗi hàng là riêng biệt:

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

Nếu bạn cần phá vỡ mối quan hệ, bạn có thể thêm nhiều cột nếu cần thiết vào ORDER BY.


Tốt thôi, thứ tự không phải là vấn đề, tôi chỉ xóa nó khỏi ví dụ để làm cho nó đơn giản hơn, tôi sẽ thử điều đó.
Edwin Jarvis

7
trong đó giả định, rằng khóa chính được tạo ra liên tục và hàng không bao giờ bị xóa và chọn không có bất kỳ khoản trật tự khác và và và ...
MartinStettner

Martin đúng. Mặc dù điều này có thể hiệu quả trong một số trường hợp, nhưng bạn thực sự cần phải xác định chính xác ý của bạn là "trước đó" theo nghĩa kinh doanh, tốt nhất là không dựa vào ID đã tạo.
Tom H

Bạn nói đúng, tôi đã thêm một cải tiến bằng cách sử dụng tiện ích mở rộng SQL Server.
RossFnaiant

2
Để trả lời "Tốt thôi, đơn đặt hàng không phải là vấn đề" ... Vậy tại sao bạn không trừ một giá trị tùy tiện trong truy vấn của mình vì đó là điều bạn đang làm nếu bạn không xem xét đơn đặt hàng?
JohnFx

79

Sử dụng chức năng trễ :

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

Các chuỗi được sử dụng cho Id có thể bỏ qua các giá trị, vì vậy Id-1 không phải lúc nào cũng hoạt động.


1
Đây là giải pháp PostgreSQL. Câu hỏi là về MSSQL. MSSQL có chức năng như vậy trong các phiên bản 2012+ ( msdn.microsoft.com/en-us/en-en/library/hh231256(v=sql.120).aspx )
Kromster

10
@KromStern Không chỉ có giải pháp PostgreSQL. Các hàm SQL Window đã được giới thiệu trong tiêu chuẩn SQL: 2003 .
Hans Ginzel

Chức năng LAG có thể mất ba thông số: LAG(ExpressionToSelect, NumberOfRowsToLag, DefaultValue). Số hàng mặc định bị trễ là 1, nhưng bạn có thể chỉ định giá trị đó và giá trị mặc định để chọn khi không thể bị trễ vì bạn đang ở đầu tập hợp.
vaindil

29

Oracle, PostgreSQL, SQL Server và nhiều công cụ RDBMS khác có các hàm phân tích được gọi LAGLEADthực hiện điều này.

Trong SQL Server trước năm 2012, bạn cần thực hiện những việc sau:

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

, COL1cột bạn đang đặt hàng ở đâu.

Có một chỉ mục trên (COL1, PK)sẽ cải thiện đáng kể truy vấn này.


14
SQL Server 2012 hiện cũng có LAG và LEAD.
ErikE

Tập lệnh Hana SQL cũng hỗ trợ LAG và LEAD.
mik

Chỉ để thêm một nhận xét khác cho những người xem đã đến đây tìm cách làm điều đó trong Hive. Nó cũng có các chức năng LAG và LEAD. Tài liệu ở đây: 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

Nó hoạt động chính xác nếu không có nhóm trong truy vấn, nhưng nếu chúng ta muốn trừ các giá trị khỏi giá trị trước đó chỉ trong một nhóm, giả sử cùng một EmployeeID, thì làm thế nào chúng ta có thể làm điều đó? Coz chạy điều này chỉ hoạt động cho 2 hàng trên cùng của mỗi nhóm chứ không phải cho các hàng còn lại trong nhóm đó. Đối với điều này, tôi đã sử dụng chạy mã này trong vòng lặp while, nhưng điều đó dường như rất chậm. Bất kỳ cách tiếp cận nào khác mà chúng ta có thể thực hiện trong trường hợp này? Và điều đó cũng chỉ trong SQL Server 2008?
Hemant Sisodia

10

THAM GIA TRÁI bảng với chính nó, với điều kiện kết hợp được thực hiện để hàng được khớp trong phiên bản đã kết hợp của bảng là hàng trước đó một hàng, đối với định nghĩa cụ thể của bạn về "trước đó".

Cập nhật: Lúc đầu, tôi nghĩ bạn sẽ muốn giữ tất cả các hàng, với NULL cho điều kiện không có hàng trước đó. Đọc lại nó, bạn chỉ muốn các hàng được chọn, vì vậy bạn nên tham gia bên trong hơn là liên kết bên trái.


Cập nhật:

Các phiên bản mới hơn của Sql Server cũng có chức năng LAG và LEAD Windowing cũng có thể được sử dụng cho việc này.


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

Câu trả lời đã chọn sẽ chỉ hoạt động nếu không có khoảng trống trong trình tự. Tuy nhiên, nếu bạn đang sử dụng id được tạo tự động, có khả năng có khoảng trống trong trình tự do các phần chèn đã được cuộn lại.

Phương pháp này sẽ hoạt động nếu bạn có khoảng trống

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.