Mencoba menemukan kapan terakhir kali suatu nilai berubah


26

Saya memiliki tabel yang memiliki ID, nilai, dan tanggal. Ada banyak ID, Nilai, dan tanggal dalam tabel ini.

Catatan dimasukkan ke dalam tabel ini secara berkala. ID akan selalu tetap sama tetapi kadang-kadang nilainya akan berubah.

Bagaimana saya bisa menulis kueri yang akan memberi saya ID plus waktu terakhir nilai telah berubah? Catatan: nilainya akan selalu meningkat.

Dari data sampel ini:

  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')

Hasilnya harus:

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

(Karena 00:05 adalah waktu terakhir Taco_Valueberubah.)


2
Saya berasumsi tacotidak ada hubungannya dengan makanan?
Kermit

5
Saya lapar dan ingin makan taco. Hanya perlu nama untuk tabel sampel.
SqlSandwiches

8
Apakah Anda memilih nama pengguna Anda dengan dasar yang sama?
Martin Smith

1
Cukup mungkin.
SqlSandwich

Jawaban:


13

Dua pertanyaan ini bergantung pada asumsi yang Taco_valueselalu meningkat dari waktu ke waktu.

;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;

Alternatif dengan kegilaan fungsi jendela yang lebih sedikit:

;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;

Contoh di SQLfiddle


Memperbarui

Bagi mereka yang melacak, ada pertentangan tentang apa yang terjadi jika Taco_value bisa diulang. Jika bisa dari 1 ke 2 dan kemudian kembali ke 1 untuk yang diberikan Taco_ID, kueri tidak akan berfungsi. Berikut adalah solusi untuk kasus itu, bahkan jika itu bukan teknik kesenjangan & kepulauan yang orang seperti Itzik Ben-Gan mungkin bisa impikan, dan bahkan jika itu tidak relevan untuk skenario OP - mungkin saja relevan dengan pembaca masa depan. Ini sedikit lebih kompleks, dan saya juga menambahkan variabel tambahan - Taco_IDyang hanya pernah memiliki satu Taco_value.

Jika Anda ingin memasukkan baris pertama untuk ID mana pun yang nilainya tidak berubah sama sekali di seluruh rangkaian:

;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;

Jika Anda ingin mengecualikan baris itu, itu sedikit lebih kompleks, tetapi masih sedikit perubahan:

;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;

Diperbarui contoh SQLfiddle


Saya telah memperhatikan beberapa masalah kinerja yang signifikan dengan OVER tetapi saya hanya menggunakannya beberapa kali dan mungkin menulisnya dengan buruk. Pernahkah Anda memperhatikan sesuatu?
Kenneth Fisher

1
@KennethFisher tidak secara khusus dengan OVER. Seperti yang lainnya, konstruksi kueri sangat bergantung pada skema / indeks yang mendasarinya agar berfungsi dengan benar. Klausa berlebih bahwa partisi akan mengalami masalah yang sama dengan GROUP BY.
Aaron Bertrand

@KennethFisher harap berhati-hati untuk tidak menarik kesimpulan yang luas dan luas dari pengamatan tunggal yang terisolasi. Saya melihat argumen yang sama terhadap CTE - "Yah, saya pernah melakukan CTE rekursif ini sekali, dan kinerjanya payah. Jadi saya tidak menggunakan CTE lagi."
Aaron Bertrand

Itulah mengapa saya bertanya. Saya belum menggunakannya cukup untuk mengatakan satu atau lain cara, tetapi beberapa kali saya menggunakannya saya bisa mendapatkan kinerja yang lebih baik dengan CTE. Saya akan terus bermain dengannya.
Kenneth Fisher

@AaronBertrand Saya tidak berpikir ini akan bekerja jika valuemuncul kembali: Fiddle
ypercubeᵀᴹ

13

Pada dasarnya, ini adalah saran @ Taryn "terkondensasi" untuk SELECT tunggal tanpa tabel turunan:

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

Catatan: solusi ini memperhitungkan ketentuan itu Taco_value hanya bisa bertambah. (Lebih tepatnya, ini mengasumsikan bahwa Taco_valuetidak dapat mengubah kembali ke nilai sebelumnya - sama dengan jawaban yang terhubung, pada kenyataannya.)

Demo SQL Fiddle untuk kueri: http://sqlfiddle.com/#!3/91368/2


7
Wah, bersarang MAX / MIN. MIND BLOWN +1
Aaron Bertrand

7

Anda harus dapat menggunakan keduanya min()dan max()fungsi agregat mendapatkan hasilnya:

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

Lihat SQL Fiddle dengan Demo


5

Satu lagi jawaban yang didasarkan pada asumsi bahwa nilai-nilai tidak muncul kembali (ini pada dasarnya adalah permintaan @ Aaron 2, diringkas dalam satu sarang kurang):

;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 ;

Tes di: SQL-Fiddle


Dan jawaban untuk masalah yang lebih umum, di mana nilai dapat muncul kembali:

;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 ;

(atau menggunakan CROSS APPLYsemua baris terkait, termasuk value, ditampilkan):

;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 ;

Tes di: SQL-Fiddle-2


Saran untuk masalah yang lebih umum tidak berfungsi untuk ID yang tidak memiliki perubahan. Bisa diperbaiki dengan menambahkan entri boneka ke set asli (seperti dbo.Taco UNION ALL SELECT DISTINCT Taco_ID, NULL AS Taco_value, '19000101' AS Taco_date).
Andriy M

@ Andrew saya tahu. Saya berasumsi bahwa "perubahan" berarti mereka menginginkan hasil ketika setidaknya ada 2 nilai, OP belum mengklarifikasi itu (dan karena lebih mudah untuk menulis :)
ypercubeᵀᴹ

2

FYI +1 untuk menyediakan struktur sampel dan data. Satu-satunya hal yang bisa saya minta adalah output yang diharapkan untuk data itu.

EDIT: Yang ini akan membuatku gila. Saya baru tahu ada cara "sederhana" untuk melakukan ini. Saya menyingkirkan solusi yang salah dan menempatkan yang saya yakini benar. Berikut adalah solusi yang mirip dengan @bluefeets tetapi mencakup pengujian yang diberikan @AaronBertrand.

;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 tidak meminta tanggal yang lebih baru, ia bertanya kapan valueperubahannya.
ypercubeᵀᴹ

Ahhh, aku melihat kesalahanku. Saya menemukan jawaban, tetapi hampir sama dengan @ Harun sehingga tidak ada gunanya mempostingnya.
Kenneth Fisher

1

Mengapa tidak mendapatkan perbedaan nilai lag dan nilai memimpin? jika perbedaannya nol, itu tidak berubah, itu bukan nol, maka itu berubah. Ini dapat dilakukan dalam permintaan sederhana:

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

Fungsi lag...analitis hanya "baru" yang diperkenalkan di SQL Server 2012. Pertanyaan aslinya adalah meminta solusi pada SQL Server 2008 R2. Solusi Anda tidak akan berfungsi untuk SQL Server 2008 R2.
John aka hot2use

-1

Mungkinkah ini sesederhana yang berikut ini?

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

Mengingat taco_value selalu meningkat?

id Saya pemula SQL sendiri, namun, belajar dengan lambat tapi pasti.


1
Pada SQL Server ini memberikan kesalahan. Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Martin Smith

2
Menambahkan poin ke komentar Martin: Anda berada di sisi yang aman jika Anda pernah memposting kode yang diuji saja. Cara mudah bisa menuju ke sqlfiddle.com jika Anda jauh dari taman bermain yang biasa.
dezso
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.