2

私はMSAccessデータベースを使用しており、過去XYか月間に価格がXX%以上変化した証券の概要を提供するクエリを作成しようとしています。私はあらゆる種類のサブクエリを試しましたが、これについて頭を悩ませることはできません。

簡単な例を以下に示します。PriceTableには、期間、セキュリティID、およびその期間のセキュリティの価格の3つの属性が含まれています。最後の期間(この場合は201210)ごとに、最後のXY(この場合は3)でプラスマイナスXX%(この場合は3%)を超える価格変動があるすべての証券を提供するクエリを探しています。数ヶ月。右側の3つの列は、これをさらに明確にするための計算を提供します。

  • デルタは、ある期間から別の期間への価格変動です((PT-PT-1)/ PT-1)

  • Delta> Threshold:変化が(プラスまたはマイナス)3%より大きいかどうかをチェックします(パラメーターXX)

  • カウンター:3か月連続で価格変動が3%より大きいかどうかをチェックします。以下の例では、クエリにproductID番号1のみが表示されます。

PriceTable Supporting calculations

+--------+------+-------+--------+-----------------+---------+
+ Period |SecID | Price | Delta% | Delta>Threshold | Counter |
+--------+------+-------+--------+-----------------+---------+
| 201206 |    1 |   105 |     0% |               N |       0 |
| 201207 |    1 |   100 | -4.76% |               Y |       1 |
| 201208 |    1 |    95 |    -5% |               Y |       2 |
| 201209 |    1 |    90 | -5.26% |               Y |       3 |
| 201210 |    1 |    85 | -5.56% |               Y |       4 |
| 201207 |    2 |    95 |     0% |               N |       0 |
| 201208 |    2 |   100 |  5.26% |               Y |       1 |
| 201209 |    2 |   103 |     3% |               N |       0 |
| 201210 |    2 |    99 | -3.88% |               Y |       1 |
+--------+------+-------+--------+-----------------+---------+

誰かが私を助けてくれることを願っています!

前もって感謝します、

ポール

4

4 に答える 4

1

@Laurence:コードの下を見つけてください

Public Function NextPer(Nperiod As Long) As Long
Dim Month As Long

If Not IsNull(Nperiod) Then
Month = 100 * ((Nperiod / 100) - Round(Nperiod / 100, 0))
If Month < 12 Then
NextPer = Nperiod + 1
Else
NextPer = Nperiod - Month + 101
End If
End If

End Function

Public Function PCount(SPeriod As Long, EPeriod As Long) As Long

Dim SMonth As Long
Dim EMonth As Long
Dim SYear As Long
Dim EYear As Long

If Not IsNull(SPeriod) And Not IsNull(EPeriod) Then
SMonth = 100 * ((SPeriod / 100) - Round(SPeriod / 100, 0))
SYear = (SPeriod - SMonth) / 100
EMonth = 100 * ((EPeriod / 100) - Round(EPeriod / 100, 0))
EYear = (EPeriod - EMonth) / 100
PCount = (12 * EYear + EMonth) - (12 * SYear + SMonth)
End If

End Function

And the QUERY (the parameters are for the moment hardcoded)

SELECT p0.SecurityID, p0.Period
FROM (PriceTable AS p0 
INNER JOIN PriceTable AS p1 ON (p0.SecurityID = p1.SecurityID)
AND (PCount(p0.Period,p1.Period)>=0) AND (PCount(p0.Period,p1.Period)<=2)) 
INNER JOIN PriceTable AS p2 ON (p1.SecurityID = p2.SecurityID) 
AND (p1.Period =   NextPer(p2.Period))
WHERE Abs(100*(p1.Price-p2.Price)/p2.Price)>0.03
GROUP BY p0.SecurityID, p0.Period
HAVING Count(*) = 3
ORDER BY p0.SecurityID asc , p0.Period asc;
于 2012-11-18T16:48:05.743 に答える
1

UDFなしでクエリ自体でこれを取得しようとする意図に対して+1。極度の関心から、解決策を見つけるためにいくつかの努力をしました。次のコードが最も効率的なコードではないことは認めます。(これらすべての IIF では、パフォーマンスはそれほど優れていません)

上記の表に従って最初の 5 列を取得するのは非常に簡単です。それをqryDeltaに保存しました。質問の難しい部分は、カウンターを同じ結果テーブルに入れることです。2 番目のクエリ qryCounter は、期待どおりの最終的なテーブルを提供します。

qryDelta

   SELECT a.period, a.secid, a.price, 
   iif(isnull(ROUND((a.price-b.price)/b.price*100,2)),0, 
   ROUND((a.price-b.price)/b.price*100,2)) AS Delta, 
   iif(abs((a.price-b.price)/b.price)*100>3,"Y","N") AS Threshold, 
   SUM(iif(abs((a.price-b.price)/b.price)*100>3,1,0)) AS [Counter]
   FROM tbldelta AS a LEFT JOIN tbldelta AS b 
   ON (a.secid = b.secid) AND (a.period = b.period + 1)
   GROUP BY a.period, a.secid, a.price, 
   iif(isnull(ROUND((a.price-b.price)/b.price*100,2)),0,
   ROUND((a.price-b.price)/b.price*100,2)), 
   iif(abs((a.price-b.price)/b.price)*100>3,"Y","N")
   ORDER BY a.secid, a.period;

結果: ここに画像の説明を入力

qryCounter

SELECT q.period, q.secid, q.price, q.delta, q.threshold, 
SUM(iif(q.counter=0,0,1)) AS Counter
FROM qryDelta q
LEFT JOIN tblDelta t
ON q.secid = t.secid
AND (t.period < q.period)
GROUP BY q.secid, q.period, q.price, q.delta, q.threshold

結果:

ここに画像の説明を入力 ただし、SecId = 2、Period = 201208、合計 = 2 という問題にも直面しました。そのため、クエリ条件を変更しました。これで、SectID = 2、Period = 201210 total = 3 を除いて、累積周期カウントが適切に表示されているように見えます。行われたほとんどの実験のうち、JOIN のバグと、ここで条件として入れようとしている日付の間のバグのようです。

PS: ユーザー定義関数 (UDF) を作成することに決めた場合は、次の 2 つのことを考慮する必要があります。Excel をフロント エンドとして使用していますか、それとも Access をフロント エンドとして使用していますか。次に、Excel から Access UDF とクエリを呼び出すために必要な手配を行う必要があります。フロント エンドとバック エンドの両方で Access のみを使用している場合は、もちろん UDF を使用する方がはるかに簡単に処理できます。

于 2012-11-18T17:38:41.560 に答える
1

Access は手元にありませんが、SQL Server のクエリは次のとおりです。内側の 'h' テーブルはほとんどヘルパー テーブルです。外側のビットは 3 つの期間で結合し、しきい値 'Y' のカウントが 3 の場合に表示されます。私が行った方法では、次の期間を計算するための関数と、2 つの終点間の期間の数も必要です。これらは、VBA で簡単に記述できるはずです。これを回避するために、シーケンス番号を使用して期間テーブルを作成することもできます。

-- Function that works out the next period
-- i.e. if you supply 201112, it will return 201201
Create Function dbo.NextPeriod(@Period As Int) Returns Int As
Begin
  Declare
    @Month int,
    @Ret int = Null

  If @Period Is Not Null
  Begin
    Set @Month = @Period - 100 * (@Period / 100)
    If @Month < 12
      Set @Ret = @Period + 1
    Else
      Set @Ret = @Period - @Month + 101
  End
  Return @Ret
End;

-- Function that works out how many periods between the two endpoints
-- dbo.PeriodCount(201112, 201201) = 1
Create Function dbo.PeriodCount(@StartPeriod As Int, @EndPeriod As Int) Returns Int As
Begin
  Declare
    @StartMonth int,
    @EndMonth int,
    @StartYear int,
    @EndYear int,
    @Ret int = Null
  If @StartPeriod Is Not Null And @EndPeriod Is Not Null
  Begin
    Set @StartMonth = @StartPeriod - 100 * (@StartPeriod /100)
    Set @StartYear = (@StartPeriod - @StartMonth) / 100
    Set @EndMonth = @EndPeriod - 100 * (@EndPeriod / 100)
    Set @EndYear = (@EndPeriod - @EndMonth) / 100
    Set @Ret = (12 * @EndYear + @EndMonth) - (12 * @StartYear + @StartMonth)
  End
  Return @Ret
End;

-- Show periods that are the start of a run
-- of @Periods periods with threshold 
-- of at least @Threshold
Declare @Threshold Decimal(10, 2) = 3
Declare @Periods int = 3

Select
  p0.SecurityID,
  p0.Period
From
  PriceTable p0
    Inner Join (
      Select
        p1.*,
        100 * (p1.Price - p2.Price) / p2.Price As Delta,
        Case When Abs(100 * (p1.Price - p2.Price) / p2.Price) > @Threshold Then 'Y' Else 'N' End As OverThreshold
      From
        PriceTable p1
          Left Outer Join
        PriceTable p2
          On p1.SecurityID = p2.SecurityID And
             p1.Period = dbo.NextPeriod(p2.Period)
    ) h
    On p0.SecurityID = h.SecurityID And
       dbo.PeriodCount(p0.Period, h.Period) Between 0 And (@Periods - 1) And
       h.OverThreshold = 'Y'
Group By
  p0.SecurityID,
  p0.Period
Having
  Count(*) = @Periods 
Order By
  p0.SecurityID,
  p0.Period;

これは、メソッドがどのように機能するかを示しています。次のように単純化できます。

Declare @Threshold Decimal(10, 2) = 3
Declare @Periods int = 3

Select
  p0.SecurityID,
  p0.Period
From
  PriceTable p0
    Inner Join
  PriceTable p1
    On p0.SecurityID = p1.SecurityID And
       dbo.PeriodCount(p0.Period, p1.Period) Between 0 And (@Periods - 1)
    Inner Join
  PriceTable p2
    On p1.SecurityID = p2.SecurityID And
       p1.Period = dbo.NextPeriod(p2.Period)
Where
  Abs(100 * (p1.Price - p2.Price) / p2.Price) > @Threshold
Group By
  p0.SecurityID,
  p0.Period
Having
  Count(*) = @Periods 
Order By
  p0.SecurityID,
  p0.Period;

http://sqlfiddle.com/#!3/8eff9/2

于 2012-11-17T18:01:34.947 に答える
1

SQLだけで解決しました。これが私がした方法です。

まず、各行について、最後の期間からの距離を行単位で表示するクエリが必要です。

Period  SecID  Price  Row
===============================
201206  1      105    4
201207  1      100    3
201208  1      95     2
201209  1      90     1
201210  1      85     0
201207  2      95     3
201208  2      100    2
201209  2      103    1
201210  2      99     0

これをPriceTable_Orderedと呼びます:

SELECT
  PriceTable.Period,
  PriceTable.SecID,
  PriceTable.Price,
  (select count(*) from PriceTable PriceTable_1
   where PriceTable_1.SecID = PriceTable.SecID
   AND PriceTable_1.Period > PriceTable.Period) AS Row
FROM PriceTable;

ここでデルタを計算し、デルタがスリーソルドよりも大きいかどうかを示すために、PriceTable_Total1 と呼ばれるこのクエリを使用できます

SELECT
  PriceTable_Ordered.*,
  PriceTable_Ordered_1.Price,
  (PriceTable_Ordered.Price-PriceTable_Ordered_1.Price)/(PriceTable_Ordered_1.Price) AS Delta,
  iif((ABS(Delta*100)>3),"Y","N") AS DeltaThreesold
FROM
  PriceTable_Ordered LEFT JOIN PriceTable_Ordered AS PriceTable_Ordered_1
  ON (PriceTable_Ordered.SecID = PriceTable_Ordered_1.SecID)
  AND (PriceTable_Ordered.[Row]=PriceTable_Ordered_1.[Row]-1);

そして、これは次を返します:

Period  SecID  Price1  Row  Price2  Delta  DeltaThreesold
=========================================================
201206  1      105     4                   N
201207  1      100     3    105     -4,76  Y
201208  1      95      2    100     -0,05  Y
201209  1      90      1    95      -5,26  Y
201210  1      85      0    90      -5,55  Y
201207  2      95      3                   N
201208  2      100     2    95       5,26  Y
201209  2      103     1    100      0,03  N
201210  2      99      0    103     -3,88  Y

これで、 PriceTable_Total1に基づいて PriceTable_Total2 を作成できます。

SELECT
  PriceTable_Total1.Period,
  PriceTable_Total1.SecID,
  PriceTable_Total1.PriceTable_Ordered.Price,
  PriceTable_Total1.Delta,
  PriceTable_Total1.DeltaThreesold,
  PriceTable_Total1.Row,
  (select min(row) from PriceTable_Total1 PriceTable_Total1_1
   where PriceTable_Total1.SecID = PriceTable_Total1_1.SecId
   and PriceTable_Total1.Row < PriceTable_Total1_1.Row
   and PriceTable_Total1_1.DeltaThreesold="N") AS MinN,
  IIf([DeltaThreesold]="Y",[MinN]-[row],0) AS CountRows
FROM PriceTable_Total1;

PriceTable_Total1 のすべての列を選択し、各行minimum row number > than current rowで threesold が "N" である場所を数えます。現在の行がスリーソルドを超えている場合、必要なカウントはこの差だけです。それ以外の場合は 0 です。結果は次のとおりです。

Period  SecID  Price  Delta  DelTh  Row  MinN  CountRows
========================================================
201206  1      105           N      4               0
201207  1      100    -4,76  Y      3    4          1
201208  1      95     -0,05  Y      2    4          2
201209  1      90     -5,26  Y      1    4          3
201210  1      85     -5,55  Y      0    4          4
201207  2      95            N      3               0
201208  2      100     5,26  Y      2    3          1
201209  2      103     0,03  N      1    3          0
201210  2      99     -3,88  Y      0    1          1

その後、不要な列を非表示にすることができます。このクエリは、年をまたいでいる場合や、一部の期間が欠落している場合でも機能するはずです。

SELECT PriceTable_Total2.Period, PriceTable_Total2.SecID
FROM PriceTable_Total2
WHERE (PriceTable_Total2.Period=
        (select max(period)
         from PriceTable
         where PriceTable.SecID=PriceTable_Total2.SecID)
      AND (PriceTable_Total2.[CountRows])>=3);

これは以下を返します:

Period  SecID
201210  1

これは、SecID 1 のみが最後の期間に 3 か月以上 3 売上げを超えていることを意味します。

この答えが正しいことを願っています。解決しようとしてよかったです!!

于 2012-11-19T20:52:07.790 に答える