SQL - 检查多个期间的价格变化

时间:2012-11-17 17:02:31

标签: sql ms-access

我正在使用MS Access数据库,并且正在尝试进行查询,以提供在过去XY连续月份内价格变化超过XX%的证券概览。我已经尝试过所有类型的子查询,但无法理解这一点。

请在下面找到一个简化的例子。 PriceTable包含三个属性:期间,安全ID和该期间的证券价格。我正在寻找一个查询,为我提供最后一个期间(在本例中为201210)所有证券在最后一个XY(在这种情况下为3)的价格变化超过正负XX%(在这种情况下为3%)个月。右边的三列提供了一些计算,以进一步澄清这一点:

  • Delta是从一个时期到另一个时期的价格变化((PT-PT-1)/ PT-1)

  • Delta> Threshold:检查更改是否大于(加或减)3%(参数XX)

  • 计数器:检查3(参数XY)连续月份的价格变化是否大于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 个答案:

答案 0 :(得分:1)

我没有Access,但这是SQL Server的查询: 内部'h'表几乎是你的助手表。外部位连接3个周期,并显示阈值为“Y”的计数是否为3 我这样做的方式你还需要用于计算下一个时期的函数,以及两个终点之间的时段数。这些应该很容易用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

答案 1 :(得分: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;

答案 2 :(得分:1)

+1表示您打算在没有UDF的情况下尝试在查询中获取此信息。出于极大的兴趣,我已经付出了一些努力来寻找解决方案。我承认以下代码不是最有效的代码。 (对于所有那些IIF,表现并不是那么好)

根据上表获得前5列非常简单。我在qryDelta中保存了这个。我发现问题的棘手部分是将Counter放在同一个结果表中。第二个查询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;

结果: enter image description here

<强> 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

结果:

enter image description here 但是我也遇到了SecId = 2,Period = 201208,总数= 2的问题。所以我改变了查询条件。现在结果似乎正确显示了累积的周期性计数,除了SectID = 2,Period = 201210 total = 3. Perhpas你们可以为此提供一些启示。在大多数完成的实验中,似乎或多或少是JOIN和日期之间的错误,我们试图将其作为编码放在这里。

PS: 如果您已决定构建用户定义的函数(UDF),那么您可以考虑两件事。您是使用Excel作为前端还是Access作为前端。然后你必须提供必要的安排来调用你的Access UDF&amp;从Excel查询。如果您只使用Access作为前端和后端,那么使用UDF会更容易处理。

答案 3 :(得分: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;

现在计算Delta,并显示Delta是否超过三倍,我们可以使用此查询,我们将调用 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,其中三倍数为“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个月。

我希望这个答案是正确的,尝试解决它很好!!