値が最後に変更された時間を見つけようとする


26

ID、値、および日付を持つテーブルがあります。この表には多くのID、値、および日付があります。

レコードはこのテーブルに定期的に挿入されます。IDは常に同じままですが、ときどき値が変更されます。

IDと値が変更された最新の時間を提供するクエリを作成するにはどうすればよいですか?注:値は常に増加します。

このサンプルデータから:

  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

これら2つのクエリは、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場合、クエリは機能しません。Itzik Ben-Ganのような人が夢見ることができるかもしれないギャップと島のテクニックではなくても、OPのシナリオに関係ない場合でも、その場合の解決策は次のとおりです-将来の読者に関連します。それはもう少し複雑で、追加の変数を追加しましたTaco_ID-aは1つしかありませんTaco_value

セット全体で値がまったく変更されなかった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 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を1回使ったのですが、パフォーマンスが低下したので、CTEはもう使用しません」。
アーロンバートランド

それが私が尋ねた理由です。私はそれを何らかの方法で言うほど十分に使用していませんが、それを使用した数回、CTEでより良いパフォーマンスを得ることができました。私はそれで遊んでいきます。
ケネスフィッシャー

@AaronBertrand私はこれらがあれば動作するとは思わないvalueが再び表示されます:フィドル
ypercubeᵀᴹ

13

基本的に、これは派生テーブルのない単一のSELECTに「凝縮」された@Tarynの提案です。

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 with Demoをご覧ください


5

値が再表示されないという前提に基づくもう1つの答え(これは基本的に@Aaronのクエリ2であり、ネストが1つ少なくなっています)。

;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


より一般的な問題に対する提案は、変更のないIDでは機能しません。元のセットにダミーエントリを追加することで修正できます(のようなものdbo.Taco UNION ALL SELECT DISTINCT Taco_ID, NULL AS Taco_value, '19000101' AS Taco_date)。
アンドリーM

@AndriyM知っています。私は、彼らは、少なくとも2つの値がある場合の結果は、OPが(それは:)書き込みに簡単だったのでことを明らかにしていないたい「変更」の手段と仮定
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
OPは最新の日付を要求せず、いつvalue変更するかを尋ねます。
ypercubeᵀᴹ

ああ、私は間違いを見ました。答えを出しましたが、@ Aaronの投稿とほとんど同じなので、投稿する意味はありません。
ケネスフィッシャー

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 2008 R2の上の解決策を求めているSQL Serverの2012年に導入されました。ソリューションは、SQL Server 2008 R2では機能しません。
ジョン別名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が常に増加するとしますか?

追伸:私はかなり初心者のSQLですが、学習はゆっくりですが確実です。


1
SQL Serverでは、これによりエラーが発生します。Cannot perform an aggregate function on an expression containing an aggregate or a subquery
マーティンスミス

2
Martinのコメントにポイントを追加します。テスト済みのコードのみを投稿した場合は安全です。通常の遊び場から離れている場合は、sqlfiddle.comに簡単にアクセスできます。
-dezso
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.