2

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

请在下面找到一个简化的示例。PriceTable 包含三个属性:一个周期、一个证券 ID 和该周期内证券的价格。我正在寻找一个查询,该查询为我提供每个最后一个时期(在本例中为 201210)在最后一个 XY(在本例中为 3)中价格变化超过正负 XX%(在本例中为 3%)的所有证券个月。右侧的三列提供了一些计算以进一步阐明这一点:

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

  • Delta>Threshold:检查变化是否大于(正负)3%(参数XX)

  • Counter:检查价格变化是否连续 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

4 回答 4

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其中的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 回答
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;

结果: 在此处输入图像描述

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 总计 = 3。也许你们可以对此有所了解。在完成的大多数实验中,JOIN 和日期之间似乎或多或少是一个错误,我们试图将其作为代码放在此处。

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

于 2012-11-18T17:38:41.560 回答
1

我手头没有访问权限,但这里有一个 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

于 2012-11-17T18:01:34.947 回答
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 回答