web-dev-qa-db-fra.com

Essayer de trouver la dernière fois qu'une valeur a changé

J'ai une table qui a un ID, une valeur et une date. Il existe de nombreux ID, valeurs et dates dans ce tableau.

Des enregistrements sont insérés périodiquement dans ce tableau. L'ID restera toujours le même mais parfois la valeur changera.

Comment puis-je écrire une requête qui me donnera l'ID plus la dernière fois que la valeur a changé? Remarque: la valeur augmentera toujours.

À partir de ces exemples de données:

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

Le résultat devrait être:

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

(Parce que 00:05 était la dernière fois Taco_Value modifié.)

26
SqlSandwiches

Ces deux requêtes reposent sur l'hypothèse que Taco_value augmente toujours avec le temps.

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

Une alternative avec moins de folie de fonction de fenêtre:

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

Exemples sur SQLfiddle


Mise à jour

Pour ceux qui suivent, il y avait une controverse sur ce qui se passe si Taco_value pourrait jamais se répéter. S'il peut passer de 1 à 2, puis revenir à 1 pour tout Taco_ID, les requêtes ne fonctionneront pas. Voici une solution pour ce cas, même si ce n'est pas tout à fait la technique des lacunes et des îles que quelqu'un comme Itzik Ben-Gan peut imaginer, et même si elle n'est pas pertinente pour le scénario du PO - elle peut être pertinentes pour un futur lecteur. C'est un peu plus complexe, et j'ai également ajouté une variable supplémentaire - un Taco_ID qui n'en a jamais qu'un Taco_value.

Si vous souhaitez inclure la première ligne d'un ID dont la valeur n'a pas changé du tout dans l'ensemble:

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

Si vous souhaitez exclure ces lignes, c'est un peu plus complexe, mais toujours des modifications mineures:

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

Mise à jour exemples SQLfiddle

13
Aaron Bertrand

Fondamentalement, c'est @ suggestion de Taryn "condensé" en un seul SELECT sans tables dérivées:

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

Remarque: cette solution prend en compte la stipulation que Taco_value ne peut qu'augmenter. (Plus exactement, il suppose que Taco_value ne peut pas revenir à une valeur précédente - identique à la réponse liée, en fait.)

Une démonstration SQL Fiddle pour la requête: http://sqlfiddle.com/#!3/91368/2

13
Andriy M

Vous devriez pouvoir utiliser les fonctions d'agrégation min() et max() pour obtenir le résultat:

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

Voir SQL Fiddle with Demo

7
Taryn

Une autre réponse basée sur l'hypothèse que les valeurs ne réapparaissent pas (il s'agit essentiellement de la requête 2 d'Aaron, condensée dans un nid de moins):

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

Testez à: SQL-Fiddle


Et une réponse au problème plus général, où les valeurs peuvent réapparaître:

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

(ou en utilisant CROSS APPLY donc toute la ligne associée, y compris le value, est affichée):

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

Testez à: SQL-Fiddle-2

5
ypercubeᵀᴹ

FYI +1 pour fournir la structure et les données de l'échantillon. La seule chose que j'aurais pu demander est la sortie attendue pour ces données.

EDIT: Celui-ci allait me rendre fou. Je viens de découvrir qu'il y avait une façon "simple" de le faire. Je me suis débarrassé des solutions incorrectes et en ai mis une qui me semble correcte. Voici une solution similaire à @bluefeets mais elle couvre les tests que @AaronBertrand a donnés.

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

Pourquoi ne pas simplement obtenir la différence de la valeur de décalage et de la valeur de plomb? si la différence est nulle, elle n'a pas changé, elle est non nulle, alors elle a changé. Cela peut être fait dans une simple requête:

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

J'ai eu un problème similaire aujourd'hui - et dans Power BI, j'aurais pu le résoudre avec l'utilisation de Tabler.FillDown. Un peu de recherche m'a conduit à une variante SQL de FillDown:

https://www.oraylis.de/blog/fill-down-table-in-t-sql-last-non-empty-value

J'ai donc pris le temps d'adapter cette solution à cet exemple - avec une ligne supplémentaire ajoutée pour montrer la réutilisation de Taco_value.

Insert INTO Taco 
Values (1, 1, '2012-07-01 00:00:09')

Remarque: cette solution prend en compte que Taco_value peut augmenter ET diminuer (ou revenir à la valeur précédente)

WITH help1
 AS (SELECT 
         *
        ,[ChangeIndicator] = CASE
                                 WHEN [Taco_value] = LAG([Taco_value],1) OVER(
                                      ORDER BY 
         [Taco_ID]) THEN 0
                                 ELSE 1
                             END
        ,[Taco_lag] = LAG([Taco_value],1) OVER(
         ORDER BY 
         [Taco_date])
     FROM [Taco]),
 help2
 AS (SELECT 
         *
        ,[RowGroup] = SUM([ChangeIndicator]) OVER(
         ORDER BY 
         [Taco_date])
     FROM [help1])
 SELECT 
     *
    ,[ChangeDate] = FIRST_VALUE([Taco_date]) OVER(PARTITION BY [RowGroup]
     ORDER BY 
     [Taco_date])
    ,[Taco_FillDown] = FIRST_VALUE([Taco_lag]) OVER(PARTITION BY [RowGroup]
     ORDER BY 
     [Taco_date])
 FROM [help2]
 ORDER BY 
     [Taco_date]

Résultat: enter image description here

0
user2846273