2009-04-02 35 views
55

Tôi cần phải tính toán sự khác biệt của một cột giữa hai dòng của một bảng, có cách nào tôi có thể làm điều này trực tiếp trong SQL? Tôi đang sử dụng Microsoft SQL Server 2008.Có cách nào để truy cập giá trị "hàng trước" trong câu lệnh SELECT không?

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

SELECT value - (previous.value) FROM table 

Tưởng tượng rằng các tham chiếu biến "trước" mới nhất hàng đã chọn. Tất nhiên với một lựa chọn như thế này tôi sẽ kết thúc với n-1 hàng được lựa chọn trong một bảng với n hàng, đó không phải là một có 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 đó không?

+5

Cũng chỉ cần thêm 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 http://blog.sqlauthority.com/2013/09/22/sql-server-how-to-access-the-previous-row-and-next-row- value-in-select-statement/ –

Trả lời

39

SQL không có khái niệm về trật tự, vì vậy bạn cần đặt hàng 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 làm thế nào để đặt hàng thứ nhưng không làm thế nào để có được giá trị trước đó đưa ra một hiện tại (EG, bạn muốn đặt hàng theo thứ tự abc) thì tôi không biết một cách để làm trong SQL chuẩn, nhưng hầu hết các triển khai SQL sẽ có các phần mở rộng để thực hiện nó.

Dưới đây là một cách để SQL server mà hoạt động nếu bạn có thể đặt hàng như vậy mà mỗi một 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 bao nhiêu cột nếu cần thiết để ORDER BY.

+0

Đó là tốt, trật tự không phải là một vấn đề, tôi chỉ cần loại bỏ nó từ ví dụ để làm cho nó đơn giản hơn, tôi sẽ cố gắng đó. –

+4

giả định rằng các khóa chính được tạo tuần tự và các hàng không bao giờ bị xóa và chọn không có bất kỳ mệnh đề thứ tự nào khác và và ... – MartinStettner

+0

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

8

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

Cập nhật: Lúc đầu, tôi đã nghĩ bạn 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 nó một lần nữa bạn chỉ muốn các hàng được chọn lọc, vì vậy bạn nên tham gia bên trong chứ không phải là một phép nối trái.


Cập nhật:

Mới hơn phiên bản của SQL Server cũng có LAG và chức năng Windowing LEAD có thể được sử dụng cho điều này, quá.

24

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

Trong SQL Server trước đến năm 2012 bạn sẽ cần phải làm như 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 

, nơi COL1 là cột bạn đang ra lệnh bằng.

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

+13

SQL Server 2012 giờ đây cũng có LAG và LEAD. – ErikE

+0

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

+0

Chỉ cần thêm một nhận xét khác cho người xem đã đến đây để tìm kiếm điều đó ở Hive. Nó cũng có các hàm LAG và LEAD. Tài liệu ở đây: https://cwiki.apache.org/confluence/display/Hive/LanguageManual+WindowingAndAnalytics –

2

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

Phương pháp này sẽ làm việc nếu bạn có những 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 
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

Nó hoạt động chính xác nếu không có nhóm trong truy vấn, nhưng điều gì sẽ xảy ra nếu chúng ta muốn trừ các giá trị từ giá trị trước đó chỉ trong một nhóm , cho phép nói cùng một EmployeeID, sau đó làm thế nào chúng ta có thể làm điều đó? Coz chạy tác phẩm này chỉ dành cho 2 hàng trên cùng của mỗi nhóm chứ không phải phần còn lại của các hàng trong nhóm đó. Đối với điều này, tôi đã sử dụng chạy mã này trong khi vòng lặp, nhưng điều đó có vẻ rất chậm. Bất kỳ cách tiếp cận nào khác chúng ta có thể trong kịch bản này? Và điều đó cũng chỉ trong 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

Sử dụng lag chức năng:

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

Trình tự sử dụng cho Id có thể bỏ qua các giá trị, vì vậy Id-1 không không phải lúc nào cũng hiệu quả.

+1

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

+2

@KromStern Không chỉ giải pháp PostgreSQL. [Các chức năng của cửa sổ SQL] (https://www.simple-talk.com/sql/t-sql-programming/window-functions-in-sql/) được giới thiệu trong [SQL: 2003] (https: // vi. wikipedia.org/wiki/SQL:2003) tiêu chuẩn. –

+1

Nói cách khác, điều này sẽ làm việc trên trường hợp OP? – Kromster

Các vấn đề liên quan