使用ADO记录集计算和更新加权移动平均预测数据

时间:2019-02-02 22:39:34

标签: vba ms-access access-vba

在Access 2016年,我试图计算出加权移动平均预测与结果更新我的预测表。

我的代码遍历记录集没有任何问题,但是我当前的逻辑不会计算加权平均值。实际上,它只是返回每个期间的实际销售额。我一直在调试这个了一段时间,不能换我的头周围。

我将ADO类用于SQL Server的CRUD操作,作为我通过ODBC的后端。在下面的代码中,我删除了错误处理以压缩代码。如下:

Sub WMAForecast( _
                    lngCompanyID As Long, _
                    lngItemID As Long, _
                    dtmStartDate As Date, _
                    dtmEndDate As Date, _
                    intPeriods As Integer)                    

    ' Object related declarations ->
    Dim objRs As ADODB.Recordset
    Dim objDb1 As clADO
    Dim objDb2 As clADO
    Dim objEh As clError
    Dim strSQL1 As String: strSQL1 = vbNullString
    Dim strSQL2 As String: strSQL2 = vbNullString

    '// Generics variables ->
    Dim lngRecords As Long: lngRecords = 0
    Dim lngDetailsCount: lngDetailsCount = 0
    Dim lngDetailRecords: lngDetailRecords = 0
    Dim dblReturn As Double: dblReturn = 0
    Dim dblTempSum As Double: dblTempSum = 0
    Dim dblWeightSum As Double: dblWeightSum = 0

    '// Loop counters ->
    Dim i As Long: i = 0
    Dim j As Long: j = 0
    Dim k As Long: k = 0

    '// Calculate the sum of weights ->
    dblWeightSum = intPeriods * (intPeriods + 1) / 2

    '// Declare an array to store the weights ->
    Dim arrWeights As Variant
    ReDim arrWeights(1 To intPeriods)

    '// Construct SQL ->
    strSQL1 = "SELECT Sum(ItemDemandHistory.DemandUnits) AS Issues, PlanningCalendar.WeekEndDate, ItemDemandHistory.ItemID " & _
    "FROM PlanningCalendar INNER JOIN ItemDemandHistory ON PlanningCalendar.WeekEndDate = ItemDemandHistory.WeekEndDate " & _
    "GROUP BY PlanningCalendar.WeekEndDate, ItemDemandHistory.ItemID, PlanningCalendar.CompanyID " & _
    "HAVING PlanningCalendar.WeekEndDate>=? " & _
    "AND PlanningCalendar.WeekEndDate<=? " & _
    "AND ItemDemandHistory.ItemID=? " & _
    "AND PlanningCalendar.CompanyID=?"

    '// Validate parameters ->
    If Not fIsNullOrEmpty(strSQL1) And _
        Not fIsNullOrEmpty(lngCompanyID) And _
        Not fIsNullOrEmpty(lngItemID) And _
        Not fIsNullOrEmpty(dtmStartDate) And _
        Not fIsNullOrEmpty(dtmStartDate) And _
        Not fIsNullOrEmpty(intPeriods) Then

        '// Initialize database ->
        Set objDb1 = New clADO
        With objDb1
            .Initialize DatabaseType:=DBTypeEnum.TypeODBC
            .CursorLocation = adUseClient: .CommandType = adCmdText: .CursorType = adOpenStatic

            '// Retrieve recordset ->
            Set objRs = .ExecuteQuery(strSQL1, dtmStartDate, dtmEndDate, lngItemID, lngCompanyID)
            With objRs
                If Not (.EOF And .BOF) Then
                    If .RecordCount > 0 Then

                        '// Collect the number of records ->
                        lngRecords = .RecordCount

                        '// Construct and array to store the cummulative values ->
                        Dim arrCumulative As Variant
                        ReDim arrCumulative(1 To lngRecords) As Double

                        '// Construct and array to store the cummulative values ->
                        Dim arrWMA As Variant
                        ReDim arrWMA(1 To lngRecords) As Double

                        '// Move cursor to first position ->
                        .MoveFirst

                        '// Traverse through the recordset ->
                        For i = 1 To lngRecords

                            '// Set counter defaults ->
                            dblTempSum = 0
                            k = 0

                            '// Check if first record and assign first value to cummulative array ->
                            If i = 1 Then
                                arrCumulative(i) = .Fields(0)
                            Else
                                arrCumulative(i) = .Fields(0) + arrCumulative(i - 1)
                            End If

                            '// At points <= period N, calculate a simple average ->
                            '// Example using 3 Periods: If N=3, MA(1) = first series point, MA(2) = Average(first two points), MA(3) = Average(first three points)...etc ->
                            If i <= intPeriods Then
                                arrWMA(i) = arrCumulative(i) / i
                            Else
                                '// When i > intPeriods, the moving average calculation kicks in ->
                                For j = i - intPeriods + 1 To i
                                    k = k + 1
                                    dblTempSum = dblTempSum + .Fields(0) * k
                                Next j
                                arrWMA(i) = dblTempSum / dblWeightSum

                                '// Initialize database ->
                                Set objDb2 = New clADO
                                With objDb2
                                    .Initialize DatabaseType:=DBTypeEnum.TypeODBC: .CommandType = adCmdText

                                    '// Construct SQL ->
                                    strSQL2 = "UPDATE ItemDemandForecast " & _
                                    "SET ForecastUnits=? " & _
                                    "WHERE CompanyID=? " & _
                                    "AND ItemID=? " & _
                                    "AND WeekEndDate=?"

                                    '// Execute SQL ->
                                    lngDetailRecords = .ExecuteNonQuery(strSQL2, CDbl(arrWMA(i)), lngCompanyID, lngItemID, objRs.Fields(1))

                                    '// Increment record count ->
                                    lngDetailsCount = lngDetailsCount + lngDetailRecords
                                End With
                            End If
                        .MoveNext
                        Next
                    End If
                End If
            End With
        End With
    End If

    '// Cleanup ->
    Erase arrCumulative
    Erase arrWMA
    Erase arrWeights
    If Not objRs Is Nothing Then Set objRs = Nothing
    If Not objDb1 Is Nothing Then Set objDb1 = Nothing
    If Not objDb2 Is Nothing Then Set objDb2 = Nothing
    If Not objEh Is Nothing Then Set objEh = Nothing

End Function

以下是我的预期输出数据:

CompanyID   ItemID  Planning_Period Period_Ending   Demand_Units    Forecast_Units
1           10      1               2016-01-10      814             814
1           10      2               2016-01-17      1386            1386
1           10      3               2016-01-24      571             1100
1           10      4               2016-01-31      827             883.17
1           10      5               2016-02-07      1217            834.83
1           10      6               2016-02-14      1143            979.33
1           10      7               2016-02-21      1249            1115.00
1           10      8               2016-02-28      1303            1208.33
1           10      9               2016-03-06      1283            1258.33
1           10      10              2016-03-13      1379            1284.00
1           10      11              2016-03-20      990             1334.33
1           10      12              2016-03-27      1241            1168.50

总结我的目标:

  1. 根据先前的销售历史记录计算项目/期间的加权移动平均预测。
  2. 使用所计算出的预测,更新每个匹配项/周期的预测。

1 个答案:

答案 0 :(得分:2)

简单的答案是该代码未计算移动平均值,因为求和代码未引用回前几行的值。


首先查看此代码:

dblWeightSum = intPeriods * (intPeriods + 1) / 2

这就是从1到intPeriods的整数之和,例如1 + 2 + 3 + ... + intPeriods

现在转到代码

dblTempSum = 0
k = 0
...

For j = i - intPeriods + 1 To i
    k = k + 1
    dblTempSum = dblTempSum + .Fields(0) * k
Next j
arrWMA(i) = dblTempSum / dblWeightSum

首先请注意,总和中没有添加任何先前的值。换句话说,它根本不包含先前的值。没有对先前值的引用。因此,这不能是多行的运行平均值。

接下来,请考虑循环迭代的总数仅仅是intPeriods。 k有效地从1开始,然后从1递增到intPeriods。循环的每次迭代都将相同的当前值 .Fields(0)与当前的k值相乘。总体而言,循环产生的总和重写为以下内容

dblTempSum = .Fields(0) * (1 + 2 + 3 + ... + intPeriods)

这看起来很熟悉吗?应该包含它,因为它包含的存储量与先前在dblWeightSum中存储的存储量相同。

因此,上面代码段中的最后一行代码最终像以下内容一样减少了。

arrWMA(i) == dblTempSum / dblWeightSum
          == .Fields(0) * (1 + 2 + 3 + ... + intPeriods) / (1 + 2 + 3 + ... + intPeriods)
          == .Fields(0)

arrWMA(i)的值将更新后续代码中的字段ForecastUnits。因此,用于移动平均的字段最终以同一行中的原始单个值结尾,就像您观察到的那样。


对不起,但是我现在无法发布正确的加权移动平均代码。但是,关键是要用当前加权值的总和减去前一个加权总和来替换上面摘录中的当前循环。为了正确执行此操作,我认为您至少需要再一个数组来存储加权总和,并且需要从现有总和中减去超出移动周期大小(intPeriod)的值。查看可信赖的算法以获取准确的步骤。