Es wurde versucht, den Zeitpunkt zu ermitteln, zu dem sich ein Wert zuletzt geändert hat

26

Ich habe eine Tabelle mit einer ID, einem Wert und einem Datum. In dieser Tabelle befinden sich viele IDs, Werte und Daten.

Datensätze werden regelmäßig in diese Tabelle eingefügt. Die ID bleibt immer gleich, aber gelegentlich ändert sich der Wert.

Wie kann ich eine Abfrage schreiben, die mir die ID und den Zeitpunkt der letzten Änderung des Werts angibt? Hinweis: Der Wert erhöht sich immer.

Aus diesen Beispieldaten:

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

Das Ergebnis sollte sein:

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

(Weil 00:05 das letzte Mal Taco_Valuegeändert wurde.)

SqlSandwiches
quelle
2
Ich nehme an, tacohat nichts mit dem Essen zu tun?
Kermit
5
Ich habe Hunger und möchte Tacos essen. Ich brauchte nur einen Namen für die Beispieltabelle.
SqlSandwiches
8
Haben Sie Ihren Benutzernamen ähnlich gewählt?
Martin Smith
1
Gut möglich.
SqlSandwiches

Antworten:

13

Diese beiden Abfragen beruhen auf der Annahme, dass Taco_valuesie mit der Zeit immer größer werden.

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

Eine Alternative mit weniger Fensterfunktionswahnsinn:

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

Beispiele bei SQLfiddle


Aktualisieren

Für diejenigen, die den Überblick behielten, gab es Streit darüber, was passieren würde, wenn sich Taco_valuedies jemals wiederholen könnte. Wenn es von 1 auf 2 und dann wieder auf 1 gehen könnte, Taco_IDfunktionieren die Abfragen nicht. Hier ist eine Lösung für diesen Fall, auch wenn es nicht ganz die Technik von Gaps & Islands ist, die sich jemand wie Itzik Ben-Gan ausdenken kann, und selbst wenn sie für das OP-Szenario nicht relevant ist - es kann sein relevant für einen zukünftigen Leser. Es ist etwas komplexer, und ich habe auch eine zusätzliche Variable hinzugefügt - eine Taco_ID, die immer nur eine hat Taco_value.

Wenn Sie die erste Zeile für eine ID einfügen möchten, bei der sich der Wert im gesamten Satz überhaupt nicht geändert hat:

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

Wenn Sie diese Zeilen ausschließen möchten, ist dies etwas komplexer, es werden jedoch noch kleinere Änderungen vorgenommen:

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

Aktualisierte SQLfiddle-Beispiele

Aaron Bertrand
quelle
Ich habe einige signifikante Performance-Probleme mit OVER festgestellt, habe es aber nur ein paar Mal verwendet und schreibe es möglicherweise schlecht. Hast du etwas bemerkt?
Kenneth Fisher
1
@KennethFisher nicht speziell mit OVER. Wie alles andere hängen Abfragekonstrukte stark von den zugrunde liegenden Schemas / Indizes ab, um korrekt zu funktionieren. Eine over-Klausel, die besagt, dass Partitionen dieselben Probleme haben wie GROUP BY.
Aaron Bertrand
@KennethFisher Bitte achten Sie darauf, dass Sie keine umfassenden Schlussfolgerungen aus einzelnen, isolierten Beobachtungen ziehen. Ich sehe die gleichen Argumente gegen CTEs - "Nun, ich hatte diesen rekursiven CTE einmal und seine Leistung war schlecht. Also verwende ich keine CTEs mehr."
Aaron Bertrand
Darum habe ich gefragt. Ich habe es nicht genug benutzt, um es auf die eine oder andere Weise auszudrücken, aber die wenigen Male, die ich es benutzt habe, konnte ich mit einem CTE eine bessere Leistung erzielen. Ich werde trotzdem weiter damit spielen.
Kenneth Fisher
@AaronBertrand Ich glaube nicht, dass dies funktioniert, wenn valuewieder ein
angezeigt wird
13

Grundsätzlich ist dies @ Taryns Vorschlag, der sich auf ein einzelnes SELECT ohne abgeleitete Tabellen "konzentriert":

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

Hinweis: Diese Lösung berücksichtigt die Vorgabe, Taco_valuedie sich nur erhöhen kann. (Genauer gesagt, es geht davon aus, dassTaco_value nicht auf einen vorherigen Wert zurückgekehrt werden kann - genau wie bei der verknüpften Antwort.)

Eine SQL Fiddle-Demo für die Abfrage: http://sqlfiddle.com/#!3/91368/2

Andriy M
quelle
7
Whoa, verschachtelt MAX / MIN. GEIST GEBLASEN +1
Aaron Bertrand
7

Sie sollten in der Lage sein, beide Funktionen zu verwenden min()und max()das Ergebnis zu erhalten:

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

Siehe SQL Fiddle with Demo

Taryn
quelle
5

Eine weitere Antwort, die auf der Annahme basiert, dass die Werte nicht wieder angezeigt werden (dies ist im Grunde @ Aarons Abfrage 2, zusammengefasst in einem Nest weniger):

;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 bei: SQL-Fiddle


Und eine Antwort auf das allgemeinere Problem, bei dem Werte wieder auftreten können:

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

(oder mit CROSS APPLYso wird die ganze verwandte Zeile, einschließlich der value, angezeigt):

;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 bei: SQL-Fiddle-2

ypercubeᵀᴹ
quelle
Die Vorschläge für das allgemeinere Problem funktionieren nicht für IDs, die keine Änderungen aufweisen. Könnte durch Hinzufügen von Dummy-Einträgen zum Originalsatz (so etwas wie dbo.Taco UNION ALL SELECT DISTINCT Taco_ID, NULL AS Taco_value, '19000101' AS Taco_date) behoben werden .
Andriy M
@AndriyM Ich weiß. Ich nahm an, dass "change" bedeutet, dass sie Ergebnisse wünschen, wenn es mindestens 2 Werte gibt. Das OP hat dies nicht geklärt (und weil es einfacher zu schreiben war :)
ypercubeᵀᴹ 20.06.13
2

FYI +1 für die Bereitstellung von Musterstruktur und Daten. Das einzige, wonach ich hätte fragen können, ist die erwartete Ausgabe für diese Daten.

EDIT: Dieser würde mich verrückt machen. Ich war gerade neu, es gab einen "einfachen" Weg, dies zu tun. Ich habe die falschen Lösungen losgeworden und eine eingesetzt, die ich für richtig halte. Hier ist eine Lösung ähnlich wie @bluefeets, die jedoch die von @AaronBertrand durchgeführten Tests abdeckt.

;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
Kenneth Fisher
quelle
2
Das OP fragt nicht nach dem aktuellsten Datum, er fragt wann die valueÄnderungen.
Ypercubeᵀᴹ
Ahhh, ich sehe meinen Fehler. Ich habe eine Antwort ausgearbeitet, aber es ist ziemlich dasselbe wie bei @ Aaron, also macht es keinen Sinn, sie zu posten.
Kenneth Fisher
1

Warum nicht einfach die Differenz zwischen dem Lag-Wert und dem Lead-Wert ermitteln? Wenn die Differenz Null ist, hat sie sich nicht geändert, ist sie nicht Null, hat sich geändert. Dies kann in einer einfachen Abfrage erfolgen:

-- 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
JJ_Coder4Hire
quelle
Die lag...Analysefunktion wurde erst "vor kurzem" in SQL Server 2012 eingeführt. Die ursprüngliche Frage betrifft eine Lösung für SQL Server 2008 R2. Ihre Lösung würde für SQL Server 2008 R2 nicht funktionieren.
John aka hot2use
-1

Könnte das so einfach sein wie das Folgende?

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

Angesichts dessen, dass taco_value immer zunimmt?

ps Ich bin selbst ein Anfänger in SQL, lerne aber langsam aber sicher.

pmc086
quelle
1
Auf SQL Server gibt dies den Fehler. Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Martin Smith
2
Hinzufügen eines Punktes zu Martins Kommentar: Sie sind auf der sicheren Seite, wenn Sie jemals nur getesteten Code veröffentlichen. Eine einfache Möglichkeit ist sqlfiddle.com, wenn Sie nicht auf Ihrem gewohnten Spielplatz sind.
Dezso