先入先出(FIFO)比較容易理解,買入早的優先賣出。與之對應的是后人先出(LIFO),就是優先賣出最近買入的,例如:第8行賣出2K,當天還沒有買入記錄,只能找前一天的買入記錄(即第7行),此示例中恰巧二者出入平衡,如果第7行買入數據不足,那么繼續向前找(假設還有可用庫存)。這只是一個最簡單的粗略介紹,如果讀者需要更深入了解LIFO的相關知識,請自行網絡搜索。
接下來用幾個輔助列粗略解釋一下統計邏輯
I列
為數量累加值,統計從期初到當前行的數量匯總值,如果數值為0,說明到當前行位置出入平衡J列
為當日的累加值,如果數值為0,說明該天出入平衡,例如:20250410
步驟1:確定起始日期,查找滿足如下條件的最后一行日期(或者行號),結果為20250314(第5行),第6行將作為起始行。
- 累加值(I列)為0 【滿足此條件的有5行,標記為淺紅色】
- 某行是當日最后一條記錄 【滿足兩個條件的有第3,5行,此處應取第5行】
步驟2:在累加列中從上到下查找首次出現的最終累加值(5800)作為終止日期,此示例中為20250409(行號20)。其中的邏輯是由于20行和第28行累加值相同,說明第21~28行的出入平衡,讀者可以自行驗證。
步驟3:對于起始日期(不包含)到截止日期之間的,剔除當日小計為零的日期,在每日最后一行提取均價作為最新價(填充H列),當日小計作為數量(填充G列)
結果如下圖中G列和H列所示。
示例代碼如下。
Sub Demo()Dim rngData As Range, i As Long, sKey As String, vDim iSum As Long, sStart As String, sEnd As StringDim objDic As Object: Set objDic = CreateObject("scripting.dictionary")Dim objDicDay As Object: Set objDicDay = CreateObject("scripting.dictionary")Set rngData = Range("A1").CurrentRegion.Offset(1)Dim arr: arr = rngData.ValueDim iTotal As Long: iTotal = Application.Sum(rngData.Columns(4))For i = LBound(arr) To UBound(arr) - 1sKey = arr(i, 1)iSum = iSum + arr(i, 4)objDicDay(sKey) = objDicDay(sKey) + arr(i, 4)objDic(sKey) = Array(i, iSum, objDicDay(sKey))If arr(i, 1) <> arr(i + 1, 1) ThenIf iSum = 0 Then sStart = arr(i, 1)If iSum = iTotal And sEnd = "" Then sEnd = arr(i, 1)End IfNext iRange("G:H").ClearContentsRange("G1:H1").Value = Array("數量", "最新價")For Each v In objDic.keysIf v <= sStart Or v > sEnd Or objDic(v)(1) = 0 Or objDic(v)(2) = 0 ThenobjDic.Remove vElsei = objDic(v)(0)Cells(i + 1, "G") = objDic(v)(2)Cells(i + 1, "H") = arr(i, 5)End IfNext
End Sub
【代碼解析】
第4~5行代碼創建字典對象。
第6行代碼獲取當前數據區域向下偏移一行的單元格區域,其目的在于
- 去除標題行
- 單元格區域末尾包含一個空行,后續代碼中可以更簡潔的定位每日最后數據行
第7行代碼將數據讀取到數組中。
第8行代碼調用工作表函數獲取數量
列匯總值,也就是最終累加值。
第9~18行代碼循環遍歷數據表。
第10行代碼獲取第一列日期作為字典的鍵。
第11行代碼獲取數量累加值。
第12行代碼使用objDicDay
統計當日的匯總值。
第13行代碼將數組保存在objDic
中,數組包含三個元素
- 第1個元素:當前數據所在行號(注意此處比數據在工作表中的實際行號少一,第6行代碼區域偏移導致的)
- 第2個元素:數量累加值
- 第3個元素:當日數量小計
對于每個日期存在多行數據的情況,由于我們只關心最后數據行,所以此處無需使用Exists判斷sKey是否存在于字典中。
第14行代碼判斷下一個數據行的日期是否與當前行相同,如果不同,說明當前行為當日最后一行數據。
第15行代碼判斷數量累加值是否為0,如果滿足條件,那么該數據行的日期作為起始日期(sStart)。
第16行代碼判斷數量累加值是否與累加終值,并且結束日期為空,如果滿足條件,那么該數據行的日期為結束日期(sEnd)。
第19行代碼清空G和H列,用于保存結果。
第20行代碼填充標題行。
第21~29行代碼循環遍歷字典對象objDic
,將結果寫入工作表。
第22行代碼判斷字典中數組是否滿足如下任意條件
- 行號小于等于起始行
- 行號大于結束行
- 數量累加為0
- 當日數量小計為0
如果滿足任意一個條件,第23行代碼將從字典中刪除該記錄(并非必需)。
如果不滿足任意條件,那么將結果寫入工作表。
第25行代碼獲取數據行的行號。
第26行代碼將當日數量小計寫入G列。
第27行代碼將均價寫入H列。