Cố gắng tìm lần cuối cùng mà giá trị đã thay đổi


26

Tôi có một bảng có ID, giá trị và ngày. Có nhiều ID, Giá trị và ngày trong bảng này.

Hồ sơ được chèn vào bảng này định kỳ. ID sẽ luôn giữ nguyên nhưng đôi khi giá trị sẽ thay đổi.

Làm cách nào tôi có thể viết một truy vấn sẽ cung cấp cho tôi ID cộng với thời gian gần đây nhất giá trị đã thay đổi? Lưu ý: giá trị sẽ luôn tăng.

Từ dữ liệu mẫu này:

  Create Table Taco
 (  Taco_ID int,
    Taco_value int,
    Taco_date datetime)

Insert INTO Taco 
Values (1, 1, '2012-07-01 00:00:01'),
        (1, 1, '2012-07-01 00:00:02'),
        (1, 1, '2012-07-01 00:00:03'),
        (1, 1, '2012-07-01 00:00:04'),
        (1, 2, '2012-07-01 00:00:05'),
        (1, 2, '2012-07-01 00:00:06'),
        (1, 2, '2012-07-01 00:00:07'),
        (1, 2, '2012-07-01 00:00:08')

Kết quả sẽ là:

Taco_ID      Taco_date
1            2012-07-01 00:00:05

(Bởi vì 00:05 là lần cuối cùng Taco_Valuethay đổi.)


2
Tôi cho rằng tacokhông có gì để làm với thực phẩm?
Kermit

5
Tôi đói và muốn ăn một ít bánh taco. Chỉ cần một tên cho bảng mẫu.
SqlSandwiches

8
Bạn đã chọn tên người dùng của bạn trên cơ sở tương tự?
Martin Smith

1
Khá có thể.
SqlSandwiches

Câu trả lời:


13

Hai truy vấn này dựa trên giả định Taco_valueluôn tăng theo thời gian.

;WITH x AS
(
  SELECT Taco_ID, Taco_date,
    dr = ROW_NUMBER() OVER (PARTITION BY Taco_ID, Taco_Value ORDER BY Taco_date),
    qr = ROW_NUMBER() OVER (PARTITION BY Taco_ID ORDER BY Taco_date)
  FROM dbo.Taco
), y AS
(
  SELECT Taco_ID, Taco_date,
    rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID, dr ORDER BY qr DESC)
  FROM x WHERE dr = 1
)
SELECT Taco_ID, Taco_date
FROM y 
WHERE rn = 1;

Một thay thế với sự điên rồ chức năng cửa sổ ít hơn:

;WITH x AS
(
  SELECT Taco_ID, Taco_value, Taco_date = MIN(Taco_date)
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
), y AS
(
  SELECT Taco_ID, Taco_date, 
    rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID ORDER BY Taco_date DESC)
  FROM x
)
SELECT Taco_ID, Taco_date FROM y WHERE rn = 1;

Ví dụ tại SQLfiddle


Cập nhật

Đối với những người theo dõi, đã có sự tranh cãi về những gì sẽ xảy ra nếu Taco_valuecó thể lặp lại. Nếu nó có thể đi từ 1 đến 2 và sau đó trở về 1 cho bất kỳ Taco_ID, các truy vấn sẽ không hoạt động. Đây là một giải pháp cho trường hợp đó, ngay cả khi đó không phải là kỹ thuật của những khoảng trống và hòn đảo mà một người như Itzik Ben-Gan có thể mơ được, và ngay cả khi nó không phù hợp với kịch bản của OP - nó có thể có liên quan đến một độc giả tương lai. Nó phức tạp hơn một chút và tôi cũng đã thêm một biến bổ sung - một biến Taco_IDchỉ có một Taco_value.

Nếu bạn muốn bao gồm hàng đầu tiên cho bất kỳ ID nào có giá trị không thay đổi trong toàn bộ:

;WITH x AS
(
  SELECT *, rn = ROW_NUMBER() OVER 
    (PARTITION BY Taco_ID ORDER BY Taco_date DESC)
  FROM dbo.Taco
), rest AS (SELECT * FROM x WHERE rn > 1)
SELECT  
  main.Taco_ID, 
  Taco_date = MIN(CASE 
    WHEN main.Taco_value = rest.Taco_value 
    THEN rest.Taco_date ELSE main.Taco_date 
  END)
FROM x AS main LEFT OUTER JOIN rest
ON main.Taco_ID = rest.Taco_ID AND rest.rn > 1
WHERE main.rn = 1
AND NOT EXISTS 
(
  SELECT 1 FROM rest AS rest2
   WHERE Taco_ID = rest.Taco_ID
   AND rn < rest.rn
   AND Taco_value <> rest.Taco_value
) 
GROUP BY main.Taco_ID;

Nếu bạn muốn loại trừ các hàng đó, nó phức tạp hơn một chút, nhưng vẫn có những thay đổi nhỏ:

;WITH x AS
(
  SELECT *, rn = ROW_NUMBER() OVER 
    (PARTITION BY Taco_ID ORDER BY Taco_date DESC)
  FROM dbo.Taco
), rest AS (SELECT * FROM x WHERE rn > 1)
SELECT 
  main.Taco_ID, 
  Taco_date = MIN(
  CASE 
    WHEN main.Taco_value = rest.Taco_value 
    THEN rest.Taco_date ELSE main.Taco_date 
  END)
FROM x AS main INNER JOIN rest -- ***** change this to INNER JOIN *****
ON main.Taco_ID = rest.Taco_ID AND rest.rn > 1
WHERE main.rn = 1
AND NOT EXISTS
(
  SELECT 1 FROM rest AS rest2
   WHERE Taco_ID = rest.Taco_ID
   AND rn < rest.rn
   AND Taco_value <> rest.Taco_value
)
AND EXISTS -- ***** add this EXISTS clause ***** 
(
  SELECT 1 FROM rest AS rest2
   WHERE Taco_ID = rest.Taco_ID
   AND Taco_value <> rest.Taco_value
)
GROUP BY main.Taco_ID;

Các ví dụ SQLfiddle được cập nhật


Tôi đã nhận thấy một số vấn đề hiệu suất đáng kể với HƠN nhưng tôi chỉ sử dụng nó một vài lần và có thể viết nó kém. Bạn có nhận thấy điều gì không?
Kenneth Fisher

1
@KennethFisher không cụ thể với HƠN. Giống như mọi thứ khác, các cấu trúc truy vấn phụ thuộc rất nhiều vào lược đồ / chỉ mục cơ bản để hoạt động chính xác. Một mệnh đề trên rằng các phân vùng sẽ chịu các vấn đề tương tự như NHÓM THEO.
Aaron Bertrand

@KennethFisher hãy cẩn thận để không rút ra kết luận rộng rãi, càn quét từ các quan sát đơn lẻ, riêng lẻ. Tôi thấy các lập luận tương tự chống lại CTE - "Chà, tôi đã có CTE đệ quy này một lần và hiệu suất của nó bị giảm. Vì vậy, tôi không sử dụng CTE nữa."
Aaron Bertrand

Đó là lý do tại sao tôi hỏi. Tôi đã không sử dụng nó đủ để nói theo cách này hay cách khác, nhưng vài lần tôi đã sử dụng nó, tôi có thể có được hiệu suất tốt hơn với CTE. Tôi sẽ tiếp tục chơi với nó mặc dù.
Kenneth Fisher

@AaronBertrand Tôi không nghĩ những thứ này sẽ hoạt động nếu valuexuất hiện lại: Fiddle
ypercubeᵀᴹ

13

Về cơ bản, đây là gợi ý của @ Taryn "cô đọng" thành một CHỌN duy nhất không có bảng dẫn xuất:

SELECT DISTINCT
  Taco_ID,
  Taco_date = MAX(MIN(Taco_date)) OVER (PARTITION BY Taco_ID)
FROM Taco
GROUP BY
  Taco_ID,
  Taco_value
;

Lưu ý: giải pháp này có tính đến quy định Taco_valuechỉ có thể tăng. (Chính xác hơn, nó giả định rằng Taco_valuekhông thể thay đổi trở lại giá trị trước đó - thực tế giống như câu trả lời được liên kết.)

Bản demo SQL Fiddle cho truy vấn: http://sqlfiddle.com/#!3/91368/2


7
Whoa, lồng tối đa / PHÚT. MIND BLOWN +1
Aaron Bertrand

7

Bạn sẽ có thể sử dụng cả hai min()max()các hàm tổng hợp nhận được kết quả:

select t1.Taco_ID, MAX(t1.taco_date) Taco_Date
from taco t1
inner join
(
    select MIN(taco_date) taco_date,
        Taco_ID, Taco_value
    from Taco
    group by Taco_ID, Taco_value
) t2
    on t1.Taco_ID = t2.Taco_ID
    and t1.Taco_date = t2.taco_date
group by t1.Taco_Id

Xem SQL Fiddle với bản demo


5

Thêm một câu trả lời dựa trên giả định rằng các giá trị không xuất hiện lại (về cơ bản đây là truy vấn 2 của Aaron, được cô đọng trong một tổ ít hơn):

;WITH x AS
(
  SELECT 
    Taco_ID, Taco_value, 
    Rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID
                            ORDER BY MIN(Taco_date) DESC),
    Taco_date = MIN(Taco_date) 
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
)
SELECT Taco_ID, Taco_value, Taco_date
FROM x 
WHERE Rn = 1 ;

Kiểm tra tại: SQL-Fiddle


Và một câu trả lời cho vấn đề chung hơn, nơi các giá trị có thể xuất hiện trở lại:

;WITH x AS
(
  SELECT 
    Taco_ID, Taco_value, 
    Rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID
                            ORDER BY MAX(Taco_date) DESC),    
    Taco_date = MAX(Taco_date) 
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
)
SELECT t.Taco_ID, Taco_date = MIN(t.Taco_date)
FROM x
  JOIN dbo.Taco t
    ON  t.Taco_ID = x.Taco_ID
    AND t.Taco_date > x.Taco_date
WHERE x.Rn = 2 
GROUP BY t.Taco_ID ;

(hoặc sử dụng CROSS APPLYtất cả các hàng liên quan, bao gồm cả value, được hiển thị):

;WITH x AS
(
  SELECT 
    Taco_ID, Taco_value, 
    Rn = ROW_NUMBER() OVER (PARTITION BY Taco_ID
                            ORDER BY MAX(Taco_date) DESC),    
    Taco_date = MAX(Taco_date) 
  FROM dbo.Taco
  GROUP BY Taco_ID, Taco_value
)
SELECT t.*
FROM x
  CROSS APPLY 
  ( SELECT TOP (1) *
    FROM dbo.Taco t
    WHERE t.Taco_ID = x.Taco_ID
      AND t.Taco_date > x.Taco_date
    ORDER BY t.Taco_date
  ) t
WHERE x.Rn = 2 ;

Kiểm tra tại: SQL-Fiddle-2


Các đề xuất cho vấn đề chung hơn không hoạt động đối với các ID không có thay đổi. Có thể được sửa bằng cách thêm các mục giả vào bộ gốc (đại loại như dbo.Taco UNION ALL SELECT DISTINCT Taco_ID, NULL AS Taco_value, '19000101' AS Taco_date).
Andriy M

@AndriyM Tôi biết. Tôi giả định rằng "thay đổi" có nghĩa là họ muốn có kết quả khi có ít nhất 2 giá trị, OP đã không làm rõ điều đó (và vì nó dễ viết hơn :)
ypercubeᵀᴹ

2

FYI +1 để cung cấp cấu trúc và dữ liệu mẫu. Điều duy nhất tôi có thể yêu cầu là đầu ra dự kiến ​​cho dữ liệu đó.

EDIT: Cái này sẽ khiến tôi phát điên. Tôi chỉ mới có một cách "đơn giản" để làm điều này. Tôi đã loại bỏ các giải pháp không chính xác và đặt một giải pháp mà tôi tin là đúng. Đây là một giải pháp tương tự như @bluefeets nhưng nó bao gồm các bài kiểm tra mà @AaronBertrand đã đưa ra.

;WITH TacoMin AS (SELECT Taco_ID, Taco_value, MIN(Taco_date) InitialValueDate
                FROM Taco
                GROUP BY Taco_ID, Taco_value)
SELECT Taco_ID, MAX(InitialValueDate)
FROM TacoMin
GROUP BY Taco_ID

2
OP không yêu cầu ngày gần đây hơn, anh ấy hỏi khi nào valuethay đổi.
ypercubeᵀᴹ

Ahhh, tôi thấy lỗi của tôi. Tôi đã tìm ra một câu trả lời, nhưng nó khá giống với @ Aaron vì vậy không có điểm nào trong việc đăng nó.
Kenneth Fisher

1

Tại sao không chỉ nhận được sự khác biệt của giá trị độ trễ và giá trị dẫn? nếu sự khác biệt bằng 0 thì nó không thay đổi, nó khác không, thì nó đã thay đổi. Điều này có thể được thực hiện trong một truy vấn đơn giản:

-- example gives the times the value changed in the last 24 hrs
SELECT
    LastUpdated, [DiffValue]
FROM (
  SELECT
      LastUpdated,
      a.AboveBurdenProbe1TempC - coalesce(lag(a.AboveBurdenProbe1TempC) over (order by ProcessHistoryId), 0) as [DiffValue]
  FROM BFProcessHistory a
  WHERE LastUpdated > getdate() - 1
) b
WHERE [DiffValue] <> 0
ORDER BY LastUpdated ASC

Hàm lag...phân tích chỉ được "giới thiệu" gần đây trong SQL Server 2012. Câu hỏi ban đầu là yêu cầu một giải pháp trên SQL Server 2008 R2. Giải pháp của bạn sẽ không hoạt động cho SQL Server 2008 R2.
John aka hot2use

-1

Điều này có thể đơn giản như sau?

       SELECT taco_id, MAX(
             CASE 
                 WHEN taco_value <> MAX(taco_value) 
                 THEN taco_date 
                 ELSE null 
             END) AS last_change_date

Cho rằng taco_value luôn tăng?

ps Tôi là người mới bắt đầu SQL, tuy nhiên, học chậm nhưng chắc chắn.


1
Trên SQL Server, điều này đưa ra lỗi. Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Martin Smith

2
Thêm một điểm vào nhận xét của Martin: bạn ở bên an toàn nếu bạn chỉ đăng mã được kiểm tra. Một cách dễ dàng có thể được truy cập sqlfiddle.com nếu bạn ở xa sân chơi thông thường của bạn.
dezso
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.