次の列の株式データを含むMSSQLテーブルがあります。Id, Symbol, Date, Open, High, Low, Close
。
テーブルに自己参加したいので、Close
の日々の変化率を取得できます。
すべてのレコードに前のセッションのデータも含まれるように、テーブルをそれ自体と結合するクエリを作成する必要があります(昨日の日付は使用できないことに注意してください)。
私の考えは次のようなことをすることです:
select * from quotes t1
inner join quotes t2
on t1.symbol = t2.symbol and
t2.date = (select max(date) from quotes where symbol = t1.symbol and date < t1.date)
しかし、それが正しい/最速の方法かどうかはわかりません。パフォーマンスについて考えるとき、何を考慮に入れる必要がありますか? (たとえば、(シンボル、日付)ペアにUNIQUEインデックスを配置すると、パフォーマンスが向上しますか?)
この表には、毎年約100,000件の新しいレコードがあります。 MS SQL Server2008を使用しています
1つのオプションは、再帰cteを使用することです(要件を正しく理解している場合)。
WITH RNCTE AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date) rn
FROM quotes
),
CTE AS (
SELECT symbol, date, rn, cast(0 as decimal(10,2)) perc, closed
FROM RNCTE
WHERE rn = 1
UNION ALL
SELECT r.symbol, r.date, r.rn, cast(c.closed/r.closed as decimal(10,2)) perc, r.closed
FROM CTE c
JOIN RNCTE r on c.symbol = r.symbol AND c.rn+1 = r.rn
)
SELECT * FROM CTE
ORDER BY symbol, date
パーセンテージの変化として使用する各シンボルの現在の合計が必要な場合は、その金額の列を追加するのは簡単です-意図が完全にわからなかったため、上記は現在のクローズ金額を以前の成約額。
このようなものはSQLiteで機能します:
SELECT ..
FROM quotes t1, quotes t2
WHERE t1.symbol = t2.symbol
AND t1.date < t2.date
GROUP BY t2.ID
HAVING t2.date = MIN(t2.date)
SQLiteが最も単純なものであることを考えると、おそらくMSSQLでは、これは最小限の変更でも機能します。
あなたはこのようなことをします:
with OrderedQuotes as
(
select
row_number() over(order by Symbol, Date) RowNum,
ID,
Symbol,
Date,
Open,
High,
Low,
Close
from Quotes
)
select
a.Symbol,
a.Date,
a.Open,
a.High,
a.Low,
a.Close,
a.Date PrevDate,
a.Open PrevOpen,
a.High PrevHigh,
a.Low PrevLow,
a.Close PrevClose,
b.Close-a.Close/a.Close PctChange
from OrderedQuotes a
join OrderedQuotes b on a.Symbol = b.Symbol and a.RowNum = b.RowNum + 1
最後の結合を左結合に変更すると、各シンボルの最初の日付の行が表示されますが、それが必要かどうかはわかりません。
(symbol, date)
のインデックス
SELECT *
FROM quotes q_curr
CROSS APPLY (
SELECT TOP(1) *
FROM quotes
WHERE symbol = q_curr.symbol
AND date < q_curr.date
ORDER BY date DESC
) q_prev
あなたが持っていたものは大丈夫です。サブクエリを結合に変換することが役立つかどうかはわかりません。しかし、あなたはそれを求めたので、それを行う方法は、テーブルをもう一度それ自体に結合することかもしれません。
select *
from quotes t1
inner join quotes t2
on t1.symbol = t2.symbol and t1.date > t2.date
left outer join quotes t3
on t2.symbol = t3.symbol and t2.date > t3.date
where t3.date is null
[〜#〜] cte [〜#〜] および ROW_NUMBER ランキング関数でオプションを使用できます
;WITH cte AS
(
SELECT symbol, date, [Open], [High], [Low], [Close],
ROW_NUMBER() OVER(PARTITION BY symbol ORDER BY date) AS Id
FROM quotes
)
SELECT c1.Id, c1.symbol, c1.date, c1.[Open], c1.[High], c1.[Low], c1.[Close],
ISNULL(c2.[Close] / c1.[Close], 0) AS perc
FROM cte c1 LEFT JOIN cte c2 ON c1.symbol = c2.symbol AND c1.Id = c2.Id + 1
ORDER BY c1.symbol, c1.date
パフォーマンスを向上させる(並べ替えとRIDルックアップを回避する)には、このインデックスを使用します
CREATE INDEX ix_symbol$date_quotes ON quotes(symbol, date) INCLUDE([Open], [High], [Low], [Close])
SQLFiddle の簡単なデモ
あなたはこのようなことをすることができます:
DECLARE @Today DATETIME
SELECT @Today = DATEADD(DAY, 0, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP))
;WITH today AS
(
SELECT Id ,
Symbol ,
Date ,
[OPEN] ,
High ,
LOW ,
[CLOSE],
DATEADD(DAY, -1, Date) AS yesterday
FROM quotes
WHERE date = @today
)
SELECT *
FROM today
LEFT JOIN quotes yesterday ON today.Symbol = yesterday.Symbol
AND today.yesterday = yesterday.Date
そうすれば、オプションであれば、「今日」の結果を制限できます。
編集:他の質問としてリストされているCTEはうまく機能する可能性がありますが、100K行以上を処理する場合はROW_NUMBERを使用することを躊躇する傾向があります。前日が必ずしも昨日であるとは限らない場合は、独自のクエリで前日の小切手を引き出して、参照用に使用する傾向があります。
DECLARE @Today DATETIME, @PreviousDay DATETIME
SELECT @Today = DATEADD(DAY, 0, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP));
SELECT @PreviousDay = MAX(Date) FROM quotes WHERE Date < @Today;
WITH today AS
(
SELECT Id ,
Symbol ,
Date ,
[OPEN] ,
High ,
LOW ,
[CLOSE]
FROM quotes
WHERE date = @today
)
SELECT *
FROM today
LEFT JOIN quotes AS previousday
ON today.Symbol = previousday.Symbol
AND previousday.Date = @PreviousDay