Намагаючись знайти останній раз, коли значення змінилося


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змінено.)


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 не спеціально із НАДОБОЮ. Як і все інше, конструкції запитів сильно залежать від основної схеми / індексів для правильної роботи. Застережне положення про те, що розділи зазнаватимуть тих же проблем, що і GROUP BY.
Аарон Бертран

@KennethFisher будь ласка, будьте обережні, щоб не робити широких, глибоких висновків із поодиноких, ізольованих спостережень. Я бачу ті ж аргументи проти CTE - "Ну, у мене був цей рекурсивний CTE один раз, і його ефективність була відсмоктувана. Тому я більше не використовую CTE".
Аарон Бертран

Ось чому я запитав. Я не використовував це досить, щоб сказати так чи інакше, але кілька разів, коли я його використав, мені вдалося досягти кращої продуктивності за допомогою CTE. Я продовжую грати з цим, хоча.
Кеннет Фішер

@AaronBertrand Я не думаю , що це буде працювати , якщо valueзнову з'являється: скрипка
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
Whoa, вкладений MAX / MIN. MIND BLOWN +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 with Demo


5

Ще одна відповідь, яка ґрунтується на припущенні, що значення не з’являються знову (це в основному запит @ Аарона 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


Пропозиції щодо більш загальної проблеми не працюють для ідентифікаторів, які не мають змін. Не вдалося виправити додавання фіктивних записів до оригінального набору (щось подібне dbo.Taco UNION ALL SELECT DISTINCT Taco_ID, NULL AS Taco_value, '19000101' AS Taco_date).
Андрій М

@AndriyM Я знаю. Я припускав, що "зміни" означає, що вони хочуть результатів, коли є принаймні 2 значення, ОП не уточнив це (і тому, що було легше писати :)
ypercubeᵀᴹ

2

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

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, проте навчаюся повільно, але впевнено.


1
На SQL Server це дає помилку. Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Мартін Сміт

2
Додаючи крапку до коментаря Мартіна: ви в безпеці, якщо ви коли-небудь публікуєте лише перевірений код Простий спосіб може бути на сайті sqlfiddle.com, якщо ви знаходитесь далеко від звичної ігрової площадки.
dezso
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.