datetime
の価格変更の時系列と、N個のイベントの別の異なる時系列がある場合、N個のクエリを実行せずに、各イベントの時点で最後の既知の価格をクエリするにはどうすればよいですか?基本的に、最新の既知の価格に一致するある種のCROSS-JOIN BETWEEN
クエリを作成する必要があります。
たとえば、価格変更履歴:
Changed At (time) | Price (money)
1 | 10
5 | 20
10 | 30
20 | 40
イベント:
Event Time | Nearest Matched Price (from above)
0 | n/a
3 | 10
6 | 20
10 | 30
15 | 30
実際の使用例では、コストと価格を使用して在庫の動きの時系列を追跡しましたが、請求書の行と一緒にコストを保存することを怠っていました。私の在庫移動は通常、請求書の直前に発生しました。
暦日/週/月の売上を報告する同様のカレンダーレポートクエリがありますが、CROSS JOIN条件(> now && < next-day
)が1行しか返さないためにのみ機能します。騒々しいクエリを許してください(それをクリーンアップします):
SELECT [t3].[FirstDateOfWeek] AS [Date], [t3].[value] AS [Total], [t3].[value2] AS [Count]
FROM (
SELECT SUM([t0].[Total]) AS [value], COUNT(*) AS [value2], [t2].[FirstDateOfWeek]
FROM [dbo].[vw_Invoices] AS [t0]
LEFT OUTER JOIN [dbo].[Cases] AS [t1] ON ([t1].[CaseId]) = [t0].[CaseId]
CROSS JOIN [dbo].[Calendar] AS [t2]
WHERE ([t0].[Date] >= [t2].[CalendarDate]) AND ([t0].[Date] < [t2].[NextDayDateTime]) AND ([t0].[Date] >= @p0) AND ([t0].[Date] < @p1) AND (NOT ([t0].[IsVoided] = 1))
GROUP BY [t2].[FirstDateOfWeek]
) AS [t3]
ORDER BY [t3].[FirstDateOfWeek]
これはSQLでも可能ですか?おそらく、抽出された時間でグループ化された「現在の合計」クエリを使用しますか?
UNION ALLクエリで価格データとイベントデータを組み合わせると、問題は最後のNULL以外の値を見つけることになります。 Itzik Ben-Ganがその問題について書いています ここ :
Idと呼ばれるキー列とcol1と呼ばれるNULL可能値列を持つテーブルT1が与えられると、idの順序に基づいて最後の非NULLcol1値を返します。
問題に戻って、以下はサンプルデータをカバーするために行ったデータ準備です(簡単にするためにINT列を使用しましたが、日時を格納する列に簡単に切り替えることができるはずです):
_CREATE TABLE #X_PRICE_CHANGE (CHANGED_TIME INT, PRICE INT);
INSERT INTO #X_PRICE_CHANGE
VALUES (1, 10), (5, 20), (10, 30), (20, 40);
CREATE TABLE #X_EVENT (EVENT_TIME INT);
INSERT INTO #X_EVENT
VALUES (0), (3), (6), (10), (15);
_
ウィンドウ関数の問題を解決する1つの方法は次のとおりです。
_SELECT
CHANGED_TIME AS EVENT_TIME
, CURRENT_PRICE
FROM
(
SELECT
CHANGED_TIME
, SRC
, MAX(PRICE) OVER (PARTITION BY CHANGED_TIME_OF_LAST_PRICE) CURRENT_PRICE
FROM
(
SELECT
CHANGED_TIME
, PRICE
, SRC
, MAX(CASE WHEN SRC = 'PRICE' THEN CHANGED_TIME ELSE NULL END)
OVER (ORDER BY CHANGED_TIME ASC, SRC DESC
) CHANGED_TIME_OF_LAST_PRICE
FROM
(
SELECT
CHANGED_TIME, PRICE, 'PRICE' AS SRC
FROM #X_PRICE_CHANGE
UNION ALL
SELECT
EVENT_TIME, NULL, 'EVENT' AS SRC
FROM #X_EVENT
) t
) tt
) ttt
WHERE ttt.SRC = 'EVENT';
_
コードを段階的に見ていきましょう。 t
派生テーブルは、価格とイベントデータを_UNION ALL
_と組み合わせるだけです。ここでエキサイティングなことは何もありません:
_╔══════════════╦═══════╦═══════╗
║ CHANGED_TIME ║ PRICE ║ SRC ║
╠══════════════╬═══════╬═══════╣
║ 1 ║ 10 ║ PRICE ║
║ 5 ║ 20 ║ PRICE ║
║ 10 ║ 30 ║ PRICE ║
║ 20 ║ 40 ║ PRICE ║
║ 0 ║ NULL ║ EVENT ║
║ 3 ║ NULL ║ EVENT ║
║ 6 ║ NULL ║ EVENT ║
║ 10 ║ NULL ║ EVENT ║
║ 15 ║ NULL ║ EVENT ║
╚══════════════╩═══════╩═══════╝
_
tt
派生テーブルは、MAX
ウィンドウ関数をt
に適用します。ウィンドウ関数の目的は、最新の価格を含む各「EVENT」行のchanged_timeを見つけることです。
_╔══════════════╦═══════╦═══════╦════════════════════════════╗
║ CHANGED_TIME ║ PRICE ║ SRC ║ CHANGED_TIME_OF_LAST_PRICE ║
╠══════════════╬═══════╬═══════╬════════════════════════════╣
║ 0 ║ NULL ║ EVENT ║ NULL ║
║ 1 ║ 10 ║ PRICE ║ 1 ║
║ 3 ║ NULL ║ EVENT ║ 1 ║
║ 5 ║ 20 ║ PRICE ║ 5 ║
║ 6 ║ NULL ║ EVENT ║ 5 ║
║ 10 ║ 30 ║ PRICE ║ 10 ║
║ 10 ║ NULL ║ EVENT ║ 10 ║
║ 15 ║ NULL ║ EVENT ║ 10 ║
║ 20 ║ 40 ║ PRICE ║ 20 ║
╚══════════════╩═══════╩═══════╩════════════════════════════╝
_
_CHANGED_TIME
_が15の行について考えてみます。_CHANGED_TIME_OF_LAST_PRICE
_の値は10なので、戻って_CHANGED_TIME_OF_LAST_PRICE = 10
_の「PRICE」行の価格値を取得できれば、次のようになります。 _CHANGED_TIME
_が15の行の適切な価格。これは、3番目の派生テーブルttt
で発生することです。
_╔══════════════╦═══════╦═══════════════╗
║ CHANGED_TIME ║ SRC ║ CURRENT_PRICE ║
╠══════════════╬═══════╬═══════════════╣
║ 0 ║ EVENT ║ NULL ║
║ 1 ║ PRICE ║ 10 ║
║ 3 ║ EVENT ║ 10 ║
║ 5 ║ PRICE ║ 20 ║
║ 6 ║ EVENT ║ 20 ║
║ 10 ║ PRICE ║ 30 ║
║ 10 ║ EVENT ║ 30 ║
║ 15 ║ EVENT ║ 30 ║
║ 20 ║ PRICE ║ 40 ║
╚══════════════╩═══════╩═══════════════╝
_
MAX()
ウィンドウ関数は、各パーティションでNULL以外の値を1つだけ検出します。ここでは、MAX()
を使用して、PRICE
値を「PRICE」行からすべての「EVENT」行に_CHANGED_TIME_OF_LAST_PRICE
_の値が同じで効果的に塗りつぶします。
最後に、「PRICE」のソースを持つ不要な行を結果から削除します。ウィンドウ関数から正しい結果を得るにはこれらの行が必要でしたが、最終的な結果セットには含めたくありません。フィルタリング後のttt
の結果は次のとおりです。
_╔════════════╦═══════════════╗
║ EVENT_TIME ║ CURRENT_PRICE ║
╠════════════╬═══════════════╣
║ 0 ║ NULL ║
║ 3 ║ 10 ║
║ 6 ║ 20 ║
║ 10 ║ 30 ║
║ 15 ║ 30 ║
╚════════════╩═══════════════╝
_
パフォーマンスの観点から、注意すべき重要な点の1つは、時間列が各テーブルで一意である場合、 ROWまたはRANGE句 から パフォーマンスの向上 にROWSを使用できることです。 =。
本当に凝ったものにしたい場合は、データをBINARYとしてキャストすることにより、ウィンドウ関数の1つを削除できます。 INT以外の列で機能するように、コードを調整する必要がある場合があります。これが1つの実装です:
_SELECT
CHANGED_TIME
, CURRENT_PRICE
FROM
(
SELECT
CHANGED_TIME
, PRICE
, SRC
, CAST(SUBSTRING(MAX(binval) OVER (ORDER BY CHANGED_TIME ASC, SRC DESC
), 5, 4) AS INT) CURRENT_PRICE
FROM
(
SELECT
CHANGED_TIME, PRICE, 'PRICE' AS SRC
, CAST(CHANGED_TIME AS BINARY(4)) + CAST(PRICE AS BINARY(4)) AS binval
FROM #X_PRICE_CHANGE
UNION ALL
SELECT
EVENT_TIME, NULL, 'EVENT' AS SRC
, CAST(EVENT_TIME AS BINARY(4)) + CAST(NULL AS BINARY(4)) AS binval
FROM #X_EVENT
) t
) tt
WHERE tt.SRC = 'EVENT';
_
そのテクニックについてもっと知りたい場合は、sqlmagの記事を読むことをお勧めします。
迅速で汚い、ライブインスタンスではテストされていません。
イベントデータの各行には、Priceから1行が必要です。これは最近発生した行ですが、beforeイベントのタイムスタンプです。 TSQLはtop 1 .. order by
表記をサポートしています。価格ルックアップをサブクエリとしてSELECTリストに埋め込むことにより、イベントの行ごとに1回実行されます。イベントの値に価格を予測すると、最新のものが確実に返されます。このようなもの:
select
e.event_id,
e.event_time,
( select top 1 -- return one value
p.price
from Prices as p
where p.price_time <= e.event_time -- ensure the price change happened at or before the event
order by p.price_time desc -- top ensure "top 1" picks the price with the gretest i.e. most recent, timestamp
) as price
from Events as e;
サブクエリはイベントの行ごとに1回実行されるため、非常に大きなセットではパフォーマンスが低下する可能性があります。 Price.price_timeにインデックスがあることを確認してください。