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é.)
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
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
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
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
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
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
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
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]