8

日々の株価を格納する SQL テーブルがあります。市場が閉じた後、新しいレコードが毎日挿入されます。連続して値上がりしている銘柄を見つけたい。

テーブルには多くの列がありますが、これは関連するサブセットです。

quoteid     stockid      closeprice     createdate
--------------------------------------------------
    1           1               1       01/01/2012
    2           2              10       01/01/2012
    3           3              15       01/01/2012

    4           1               2       01/02/2012
    5           2              11       01/02/2012
    6           3              13       01/02/2012

    7           1               5       01/03/2012
    8           2              13       01/03/2012
    9           3              17       01/03/2012

   10           1               7       01/04/2012
   11           2              14       01/04/2012
   12           3              18       01/04/2012

   13           1               9       01/05/2012
   14           2              11       01/05/2012
   15           3              10       01/05/2012

quoteid列は主キーです。

表では、銘柄 ID 1 の終値が毎日上昇しています。在庫 ID 3 は大きく変動し、在庫 ID 2 の価格は最終日に下落しました。

私はこのような結果を探しています:

stockid     Consecutive Count (CC)
----------------------------------
    1                5
    2                4

連続ストリークの日付を含む出力を取得できれば、さらに良いでしょう:

stockid     Consecutive Count (CC)      StartDate      EndDate
---------------------------------------------------------------
    1                5                 01/01/2012    01/05/2012
    2                4                 01/01/2012    01/04/2012

StartDate価格が上昇し始めたEndDateときであり、ブルランが実際に終了したときです。

これは簡単な問題ではないと思いました。ここで、この連続したシナリオを扱っている他の投稿を見てきましたが、私のニーズには合いません。私と似たような記事をご存知でしたら教えてください。

4

3 に答える 3

9

いずれにせよ、1 株あたりの行数を増やすという観点から考えると役に立ちます (実際のquoteid値はここではあまり役に立ちません)。キャプチャされた日数(この表) が最も簡単です。他の何か (営業日のみ、週末/休日を無視するなど) が必要な場合は、さらに複雑になります。おそらくカレンダーファイルが必要です。まだ持っていない場合は、 [ stockid、 ]のインデックスが必要になります。createdate

WITH StockRow AS (SELECT stockId, closePrice, createdDate,
                         ROW_NUMBER() OVER(PARTITION BY stockId 
                                           ORDER BY createdDate) rn
                  FROM Quote),

     RunGroup AS (SELECT Base.stockId, Base.createdDate,
                         MAX(Restart.rn) OVER(PARTITION BY Base.stockId
                                              ORDER BY Base.createdDate) groupingId
                  FROM StockRow Base
                  LEFT JOIN StockRow Restart
                         ON Restart.stockId = Base.stockId
                            AND Restart.rn = Base.rn - 1
                            AND Restart.closePrice > Base.closePrice)

SELECT stockId, 
       COUNT(*) AS consecutiveCount, 
       MIN(createdDate) AS startDate, MAX(createdDate) AS endDate
FROM RunGroup
GROUP BY stockId, groupingId
HAVING COUNT(*) >= 3
ORDER BY stockId, startDate

提供されたデータから次の結果が得られます。

Increasing_Run
stockId   consecutiveCount  startDate    endDate
===================================================
1         5                 2012-01-01   2012-01-05
2         4                 2012-01-01   2012-01-04
3         3                 2012-01-02   2012-01-04

SQL Fiddle Example
(フィドルには複数回実行する例もあります)

この分析では、すべてのギャップが無視され、すべての実行が正しく一致します (次に肯定的な実行が開始されたとき)。


それで、ここで何が起こっているのですか?

StockRow AS (SELECT stockId, closePrice, createdDate,
                    ROW_NUMBER() OVER(PARTITION BY stockId 
                                      ORDER BY createdDate) rn
             FROM Quote)

この CTE は 1 つの目的で使用されています。次/前の行を見つける方法が必要なので、最初に (日付の) 順番に各行に番号を付けます...

RunGroup AS (SELECT Base.stockId, Base.createdDate,
                    MAX(Restart.rn) OVER(PARTITION BY Base.stockId
                                         ORDER BY Base.createdDate) groupingId
             FROM StockRow Base
             LEFT JOIN StockRow Restart
                    ON Restart.stockId = Base.stockId
                       AND Restart.rn = Base.rn - 1
                           AND Restart.closePrice > Base.closePrice)

...そして、インデックスに基づいてそれらを結合します。LAG()/を含むものにたどり着いた場合LEAD()は、代わりにそれらを使用する方がほぼ確実に優れたオプションになります。ただし、ここで重要なことが 1 つあります。一致するのは、行の順序が正しくない (前の行より少ない) 場合のみです。それ以外の場合、値は最終的に になりますnull(これをLAG()実行するには、after のようなものを使用する必要がありますCASE)。次のような一時的なセットを取得します。

B.rn   B.closePrice   B.createdDate  R.rn   R.closePrice   R.createdDate  groupingId
1      15             2012-01-01     -      -              -              -
2      13             2012-01-02     1      15             2012-01-01     1
3      17             2012-01-03     -      -              -              1
4      18             2012-01-04     -      -              -              1
5      10             2012-01-05     4      18             2012-01-04     4

...したがってRestart、前の行が「現在の」行よりも大きかった場合にのみ値があります。ウィンドウ関数での使用はMAX()、これまでに見られた最大値に使用されています...これnullは最小であるため、別の不一致が発生するまで他のすべての行の行インデックスが保持されます(新しい値が得られます)。この時点で、基本的にクエリの中間結果が得られ、最終的な集計の準備が整います。

SELECT stockId, 
       COUNT(*) AS consecutiveCount, 
       MIN(createdDate) AS startDate, MAX(createdDate) AS endDate
FROM RunGroup
GROUP BY stockId, groupingId
HAVING COUNT(*) >= 3
ORDER BY stockId, startDate

クエリの最後の部分では、実行の開始日と終了日を取得し、それらの日付間のエントリ数をカウントします。日付の計算にもっと複雑なことがあった場合は、おそらくこの時点で行う必要があります。は、句に列を含めないGROUP BY数少ない正当な例の 1 つを示しています。この句は、「短すぎる」ランを排除するために使用されます。SELECTHAVING

于 2012-04-27T18:57:25.740 に答える
1

おおよそ次のようなCTEを試してみます。

with increase (stockid, startdate, enddate, cc) as
(
    select d2.stockid, d1.createdate as startdate, d2.createdate as enddate, 1
    from quote d1, quote d2
    where d1.stockid = d2.stockid
    and d2.closedprice > d1.closedprice
    and dateadd(day, 1, d1.createdate) = d2.createdate

    union all

    select d2.stockid, d1.createdate as startdate, cend.enddate as enddate, cend.cc + 1
    from quote d1, quote d2, increase cend
    where d1.stockid = d2.stockid and d2.stockid = cend.stockid
    and d2.closedprice > d1.closedprice
    and d2.createdate = cend.startdate
    and dateadd(day, 1, d1.createdate) = d2.createdate
)
select o.stockid, o.cc, o.startdate, o.enddate
from increase o where cc = (select max(cc) from increase i where i.stockid = o.stockid and i.enddate = o.enddate)

これはギャップがないことを前提としています。基準dateadd(day, 1, d1.createdate) = d2.createdateは、d2 が d1 の「次の」日であるかどうかを示す別のものに置き換える必要があります。

于 2012-04-27T17:07:43.687 に答える
1

これは、私のニーズに応じて最終的に機能する SQL です。テストは、それが正しく機能していることを示しています。@OranのCCのメソッドを使用しています

WITH StockRow (stockId, [close], createdDate, rowNum)
 as
 (
     SELECT stockId,         [close],                   createdDate,
            ROW_NUMBER() OVER(PARTITION BY stockId ORDER BY createdDate)
     FROM dbo.Quote
     where createddate >= '01/01/2012' --Beginning of this year
     ),

     RunStart (stockId, [close], createdDate, runId) as (
     SELECT      a.stockId,       a.[close], a.createdDate,
            ROW_NUMBER() OVER(PARTITION BY a.stockId ORDER BY a.createdDate)
     FROM StockRow as a
     LEFT JOIN StockRow as b
     ON b.stockId = a.stockId
     AND b.rowNum = a.rowNum - 1
     AND b.[close] < a.[close]
     WHERE b.stockId IS NULL)
     ,
 RunEnd (stockId, [close], createdDate, runId) as (
     SELECT a.stockId, a.[close], a.createdDate,
            ROW_NUMBER() OVER(PARTITION BY a.stockId ORDER BY a.createdDate)
     FROM StockRow as a
     LEFT JOIN StockRow as b
     ON b.stockId = a.stockId
     AND b.rowNum = a.rowNum + 1
     AND b.[close] > a.[close]
     WHERE b.stockId IS NULL) 

SELECT a.stockId,        s.companyname,         s.Symbol, 
a.createdDate as startdate,        b.createdDate as enddate,
(select count(r.createdDate)       from      dbo.quote r      where r.stockid = b.stockid and        r.createdDate          between  a.createdDate      and       b.createdDate) as BullRunDuration
FROM RunStart as a JOIN RunEnd as b
ON b.stockId = a.stockId
join dbo.stock as s
on a.stockid = s.stockid
AND b.runId = a.runId
AND b.[close] > a.[close]
and (select count(r.createdDate) from dbo.quote r where r.stockid = b.stockid and 
r.createdDate between  a.createdDate and b.createdDate)  > 2 -- trying to avoid cluter
order by 6 desc, a.stockid
于 2012-04-28T16:19:16.013 に答える