Bir değerin en son ne zaman değiştiğini bulmaya çalışmak


26

Kimliği, değeri ve tarihi olan bir tablom var. Bu tabloda birçok kimlik, Değer ve tarih vardır.

Kayıtlar periyodik olarak bu tabloya eklenir. Kimlik daima aynı kalacak, ancak zaman zaman değer değişecektir.

Bana kimliği artı değerin en son değiştiği zamanı verecek bir sorgu nasıl yazabilirim? Not: değer her zaman artacaktır.

Bu örnek verilerden:

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

Sonuç şöyle olmalı:

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

(Çünkü 00:05 en son Taco_Valuedeğiştirildi.)


2
Sanırım tacoyemekle ilgisi yok mu?
Kermit

5
Acıktım ve biraz taco yemek istiyorum. Sadece örnek tablo için bir isim gerekli.
SqlSandwiches

8
Kullanıcı adınızı da benzer şekilde seçtiniz mi?
Martin Smith,

1
Oldukça Mümkün.
SqlSandwiches

Yanıtlar:


13

Bu iki sorgu Taco_valueher zaman zamanla artan varsayımlara dayanmaktadır .

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

Daha az pencere işlevi deliliğine sahip bir alternatif:

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

SQLfiddle'da Örnekler


Güncelleştirme

Takipte olanlar için, Taco_valuetekrar edebilirlerse ne olacağı konusunda çekişmeler vardı . 1'den 2'ye ve sonra verilenler için 1'e geri dönebilirse Taco_ID, sorgular çalışmayacaktır. İşte bu durum için bir çözüm, Itzik Ben-Gan gibi birinin hayal edebileceği boşluklar ve adalar tekniği olmasa bile ve OP'nin senaryosuyla alakalı olmasa bile - bu olabilir gelecekteki bir okuyucu ile ilgili. Bu biraz daha karmaşık, ve ben de ek bir değişken eklendi - bir Taco_IDsadece hiç birine sahip olduğunu Taco_value.

Tüm kümede değerin hiç değişmediği herhangi bir kimliğin ilk satırını eklemek istiyorsanız:

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

Bu satırları hariç tutmak istiyorsanız, biraz daha karmaşık, ancak yine de küçük değişiklikler yapılabilir:

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

SQLfiddle örnekleri güncellendi


OVER ile ilgili bazı önemli performans sorunlarını fark ettim, ancak yalnızca birkaç kez kullandım ve bu durumu kötü bir şekilde yazıyor olabilirim. Bir şey farkettin mi?
Kenneth Fisher

1
@KennethFisher özellikle OVER ile değil. Başka bir şey gibi, sorgu yapıları büyük ölçüde doğru çalışması için temel şemaya / dizinlere bağlıdır. Bölümlerin bir GROUP BY ile aynı sorunları yaşayacağına dair aşırı bir madde.
Aaron Bertrand

@KennethFisher, tekil ve izole edilmiş gözlemlerden geniş ve kapsamlı sonuçlar çıkarmamaya dikkat edin. CTE'lere karşı aynı argümanları görüyorum - "Eh, bu özyinelemeli CTE'yi bir kez yaptım ve performansı berbattı. Artık CTE kullanmıyorum."
Aaron Bertrand

Bu yüzden sordum. Öyle ya da böyle söyleyecek kadar kullanmamıştım, ancak birkaç kez kullandığımda CTE ile daha iyi performans elde edebildim. Bununla birlikte oynamaya devam edeceğim.
Kenneth Fisher

@AaronBertrand Yeniden göründüğünde bunların işe yarayacağını sanmıyorum value: Fiddle
ypercubeᵀᴹ

13

Temel olarak, bu @ Taryn'in türetilmiş tabloları olmayan tek bir SELECT için "yoğunlaştırılmış" önerisidir :

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

Not: Bu çözüm Taco_valuesadece artabilecek şartı dikkate almaktadır . (Daha doğrusu, aslında Taco_valuebir önceki değere dönüşemeyeceğini varsaymaktadır - aslında bağlantılı cevapla aynıdır.)

Sorgu için bir SQL Fiddle demosu: http://sqlfiddle.com/#!3/91368/2


7
Whoa, iç içe MAX / MIN. MIND BLOWN +1
Aaron Bertrand

7

Her ikisini de kullanabilmeniz gerekir min()ve max()toplama işlevleri sonucu alır:

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

Demo ile SQL Fiddle'ı görün


5

Değerlerin tekrar ortaya çıkmadığı varsayımına dayanan bir cevap daha (bu temelde @ Aaron'un sorgusu 2, daha az bir yuvada yoğunlaştırılmış):

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

Test at: SQL-Fiddle


Değerlerin yeniden ortaya çıkabileceği daha genel soruna bir cevap:

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

(veya bunları kullanarak CROSS APPLYda ilgili tüm satırlar valuegösterilir):

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

Test at: SQL-Fiddle-2


Daha genel bir soruna ilişkin önerilerde değişiklik olmayan kimlikler için çalışmaz. Orijinal sete sahte girişler eklenerek düzeltilebilir (benzeri bir şey dbo.Taco UNION ALL SELECT DISTINCT Taco_ID, NULL AS Taco_value, '19000101' AS Taco_date).
Andriy M,

@AndriyM biliyorum. "Değişimin" en az 2 değer olduğunda sonuç istedikleri anlamına geldiğini varsaydım, OP bunu açıklığa kavuşturmadı (ve yazmak daha kolay olduğu için :)
ypercubeᵀᴹ

2

Örnek yapı ve veri sağlamak için FYI +1. İsteyebileceğim tek şey, bu veriler için beklenen çıktı.

EDIT: Bu beni çıldırtacaktı. Yeni yaptım, bunu yapmanın "basit" bir yolu vardı. Yanlış çözümlerden kurtuldum ve doğru olduğuna inanıyorum. İşte bluefeets'e benzer bir çözüm ama @AaronBertrand'ın verdiği testleri kapsıyor.

;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 daha yakın bir tarih istemiyor, ne zaman valuedeğişiklik yapılacağını soruyor .
ypercubeᵀᴹ

Ahhh, hatamı görüyorum. Bir cevabı buldum ama @ Aaron'un yazdığı şeyle hemen hemen aynı.
Kenneth Fisher

1

Neden sadece gecikme değeri ile kurşun değer arasındaki farkı elde etmiyorsunuz? fark sıfır ise değişmedi, sıfır olmadı ve sonra değişti. Bu basit bir sorguda yapılabilir:

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

lag...Analitik işlevi yalnızca "son zamanlarda" Orijinal soru SQL Server 2008 R2 üzerinde çözüm istiyor SQL Server 2012 yılında tanıtıldı. Çözümünüz SQL Server 2008 R2 için işe yaramaz.
John aka hot2use

-1

Bu aşağıdaki kadar basit olabilir mi?

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

Taco_value her zaman artar mı?

ps Ben oldukça acemi SQL kendim, ancak, yavaş ama emin bir şekilde öğreniyorum.


1
SQL Server'da bu hata veriyor. Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Martin Smith

2
Martin'in yorumuna bir nokta ekleyerek: Sadece test edilmiş kodları gönderirseniz güvenli taraftasınız. Her zamanki oyun alanınızdan uzaksanız , sqlfiddle.com adresine kolayca gidebilirsiniz .
dezso
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.