Попытка найти последний раз, когда значение изменилось

26

У меня есть таблица с идентификатором, значением и датой. В этой таблице много идентификаторов, значений и дат.

Записи вставляются в эту таблицу периодически. Идентификатор всегда будет оставаться неизменным, но иногда значение будет меняться.

Как я могу написать запрос, который даст мне идентификатор плюс самое последнее время, когда значение изменилось? Примечание: значение всегда будет увеличиваться.

Из этого примера данных:

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

Результат должен быть:

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

(Потому что 00:05 последний раз Taco_Valueменяли.)

SqlSandwiches
источник
2
Я полагаю taco, не имеет ничего общего с едой?
Кермит
5
Я голоден и хотел бы съесть немного тако. Просто нужно имя для образца таблицы.
SqlSandwiches
8
Вы выбрали свое имя пользователя на аналогичной основе?
Мартин Смит
1
Вполне возможно.
SqlSandwiches

Ответы:

13

Эти два запроса основаны на предположении, которое Taco_valueвсегда увеличивается со временем.

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

Альтернатива с меньшим количеством безумных оконных функций:

;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


Обновить

Для тех, кто следил, был спор о том, что произойдет, если Taco_valueкогда-либо может повториться Если он может перейти от 1 к 2, а затем обратно к 1 для любого заданного Taco_ID, запросы не будут работать. Вот решение для этого случая, даже если это не совсем техника пробелов и островков, о которой может мечтать кто-то вроде Ицик Бен-Гана, и даже если это не имеет отношения к сценарию ОП - это может быть отношение к будущему читателю. Это немного сложнее, и я также добавил дополнительную переменную - Taco_IDкоторая есть только в одной Taco_value.

Если вы хотите включить первую строку для любого идентификатора, значение которого не изменилось вообще во всем наборе:

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

Если вы хотите исключить эти строки, это немного сложнее, но все же незначительные изменения:

;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

Аарон Бертран
источник
Я заметил некоторые существенные проблемы с производительностью OVER, но я использовал его всего несколько раз и, возможно, плохо пишу. Вы заметили что-нибудь?
Кеннет Фишер
1
@KennethFisher не специально с OVER. Как и все остальное, конструкции запросов в значительной степени зависят от базовой схемы / индексов для правильной работы. Предложение over, что разделы будут испытывать те же проблемы, что и GROUP BY.
Аарон Бертран
@KennethFisher, пожалуйста, будьте осторожны, чтобы не делать широкие, широкие выводы из единичных, изолированных наблюдений. Я вижу те же аргументы против CTE - «Ну, у меня был этот рекурсивный CTE один раз, и его производительность отстой. Поэтому я больше не использую CTE».
Аарон Бертран
Вот почему я спросил. Я не использовал это достаточно, чтобы сказать так или иначе, но несколько раз, когда я использовал это, я смог добиться лучшей производительности с помощью CTE. Я продолжу играть с этим все же.
Кеннет Фишер
@AaronBertrand Я не думаю, что они будут работать, если появится valueснова: Fiddle
ypercubeᵀᴹ
13

По сути, это предложение @ Taryn, «сжатое» до одного SELECT без производных таблиц:

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

Примечание: это решение учитывает условие, которое Taco_valueможет только увеличиться. (Точнее, он предполагает, что Taco_valueне может вернуться к предыдущему значению - так же, как и связанный ответ, на самом деле.)

Демонстрация SQL Fiddle для запроса: http://sqlfiddle.com/#!3/91368/2

Андрей М
источник
7
Вау, вложенный MAX / MIN. УМНЫЙ УДАР +1
Аарон Бертран
7

Вы должны быть в состоянии использовать обе min()и max()агрегатные функции, чтобы получить результат:

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

Смотрите SQL Fiddle с демо

Тарын
источник
5

Еще один ответ, основанный на предположении, что значения не появляются снова (это в основном запрос @ Aaron 2, сжатый в одном меньшем гнезде):

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

Тест в: SQL-Fiddle


И ответ на более общую проблему, где значения могут появиться снова:

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

(или используя CROSS APPLYтак, чтобы отобразилась вся связанная строка, включая value,):

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

Тест в: SQL-Fiddle-2

ypercubeᵀᴹ
источник
Предложения по более общей проблеме не работают для идентификаторов без изменений. Может быть исправлено добавлением фиктивных записей в исходный набор (что-то вроде dbo.Taco UNION ALL SELECT DISTINCT Taco_ID, NULL AS Taco_value, '19000101' AS Taco_date).
Андрей М,
@AndriyM Я знаю. Я предположил, что «изменение» означает, что они хотят результатов, когда есть хотя бы 2 значения, ОП не уточнил это (и потому что это было легче написать :)
ypercubeᵀᴹ
2

К вашему сведению +1 за предоставление структуры выборки и данных. Единственное, что я мог бы попросить, это ожидаемый результат для этих данных.

РЕДАКТИРОВАТЬ: Этот собирался сводить меня с ума. У меня только что появился "простой" способ сделать это. Я избавился от неправильных решений и поставил одно, которое считаю верным. Вот решение, похожее на @bluefeets, но оно охватывает тесты, которые дал @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
ОП не просит более позднюю дату, он спрашивает, когда valueизменения.
ypercubeᵀᴹ
Аааа, я вижу свою ошибку. Я разработал ответ, но он почти такой же, как у @ Аарона, поэтому нет смысла публиковать его.
Кеннет Фишер
1

Почему бы просто не получить разницу между значением задержки и значением опережения? если разница равна нулю, она не изменилась, если она не равна нулю, то изменилась. Это можно сделать простым запросом:

-- 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
источник
lag...Аналитическая функция только была «недавно» введена в SQL Server 2012. Оригинальный вопрос просит решения на SQL Server 2008 R2. Ваше решение не будет работать для SQL Server 2008 R2.
Джон aka hot2use
-1

Может ли это быть так просто, как показано ниже?

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

Учитывая, что taco_value всегда увеличивается?

PS Я сам начинающий SQL, но учусь медленно, но верно.

pmc086
источник
1
На SQL Server это дает ошибку. Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Мартин Смит
2
Добавление комментария к комментарию Мартина: вы в безопасности, если будете публиковать только проверенный код. На sqlfiddle.com можно легко попасть, если вы находитесь вдали от обычной игровой площадки.
Дезсо