程式可以執行,但資料有好幾千筆,電腦跑了20分鐘還是沒結果(白畫面),尋求各位的協助完成資料!!
先講想要改的程式碼:
總共有兩張表分別是資料的aaa與資料輸出的表,aaa有3行分別是type(有三種FCST、SO和Ship)、產品ID、值;資料輸出的表有三行,分別是產品ID、N或公司名、要輸出值的行
aaa!A:A= """ & cate1 & """ 第一個條件,即FCST、SO或Ship
aaa!B:B= """ & Bnum & """ 第二個條件,產品ID
aaa!E:E 尋找的值
ifna(,0) 因為會有空值無法比較,故讓空值為0
Application.Evaluate("=ifna(Lookup(1,0/((aaa!A:A= """ & cate1 & """ ) * (aaa!B:B= """ & Bnum & """ )), aaa!E:E),0)")
用for迴圈在輸出表由第二筆ID順著往下找,跑到最後一筆資料為止
ex: 若for到4,尋找輸出表中第一行第四列的ID並與表aaa第二行比對,a得到表aaa的FCST值、b得到表aaa的SO值、c得到表Ship的FCST值,當得到FCST、SO和Ship的值後會進行比較,若輸出表第三行相對的值為N,則會再做比對,若不是則直接傳a也就是FCST的值到輸出表的相對應的第四行。
完整程式碼
i = 1
cate1 = "FCST"
cate2 = "SO"
cate3 = "Ship"
row1 = Cells(Rows.Count, 1).End(xlUp).Row
'Debug.Print row1
Do Until i >= row1
i = i + 1
Debug.Print i
Bnum = Range("a" & i)
a = Application.Evaluate("=ifna(Lookup(1,0/((aaa!A:A= """ & cate1 & """ ) * (aaa!B:B= """ & Bnum & """ )), aaa!E:E),0)")
b = Application.Evaluate("=ifna(Lookup(1,0/((aaa!A:A= """ & cate2 & """ ) * (aaa!B:B= """ & Bnum & """ )), aaa!E:E),0)")
c = Application.Evaluate("=ifna(Lookup(1,0/((aaa!A:A= """ & cate3 & """ ) * (aaa!B:B= """ & Bnum & """ )), aaa!E:E),0)")
If Cells(i, 3) = "N" Then
If a > b + c Then
Cells(i, 4) = b + c
Else
Cells(i, 4) = a
End If
Else
Cells(i, 4) = a
End If
Loop
測試檔:https://www.mediafire.com/file/u0e4booej78jhdr/Test.xlsx/file
補充:FCST、SO、Ship一個ID只會各出現一次,只會少不會多,除非ID相同公司卻不同
這是 Libre Office Calc 可以跑的程式碼,應該可以轉成 Excel 用的
轉換規則我想應該是這樣:
ThisComponent.getCurrentController.activesheet
=> ActiveSheet
ThisComponent.Sheets.getByName("aaa")
=> Sheets("aaa")
.getCellByPosition(x, i).string
=> .Cells(i+1, x+1).Value
.getCellByPosition(x, i).Value
=> .Cells(i+1, x+1).Value
t <> ""
,我不確定 Excel 會怎麼判斷,還是應該要用 Nothing 來判斷,可能也要改一下Sub Main
Dim curSheet, dataSheet
curSheet = ThisComponent.getCurrentController.activesheet
dataSheet = ThisComponent.Sheets.getByName("aaa")
'先掃描目前資料表(輸出資料表),先建立字典Dict key => index(0開始算)
Dim dict, count, i, t
Set dict = CreateObject("Scripting.Dictionary")
i = 1
count = 0
t = curSheet.getCellByPosition(0, i).String
key = t & "/" & curSheet.getCellByPosition(1, i).string
while t <> ""
dict.Add key, count
count = count + 1
i = i + 1
t = curSheet.getCellByPosition(0, i).String
key = t & "/" & curSheet.getCellByPosition(1, i).String
wend
'建立陣列Arr,共有 count*3 個 Double 元素(因為有三種)
Dim arr(count*3) As Double
'開始掃描資料檔,如果符合 key 則存入 Arr[Dict[key]*3+type]
Dim idx, tpy, val
i = 1
t = dataSheet.getCellByPosition(1, i).String
key = t & "/" & dataSheet.getCellByPosition(3, i).string
while t <> ""
if dict.Exists(key) then
idx = CInt(dict.Item(key))*3
tpy = dataSheet.getCellByPosition(0, i).String
val = dataSheet.getCellByPosition(4, i).Value
if tpy = "FCST" then
arr(idx) = arr(idx) + val
elseif tpy = "SO" then
arr(idx+1) = arr(idx+1) + val
elseif tpy = "Ship" then
arr(idx+2) = arr(idx+2) + val
else
endif
endif
i = i + 1
t = dataSheet.getCellByPosition(1, i).String
key = t & "/" & dataSheet.getCellByPosition(3, i).string
wend
'將 Arr 寫入到目前資料表
Dim a,b,c
for i=1 to count
idx=(i-1)*3
a=arr(idx)
b=arr(idx+1)
c=arr(idx+2)
if curSheet.getCellByPosition(2, i).String = "N" And a > b + c then
t=b+c
else
t=a
endif
curSheet.getCellByPosition(3, i).Value=t
'curSheet.getCellByPosition(4, i).String= a & "/" & b & "/" & c
next i
End Sub
Dim arr(count*3) As Double
顯示count要是常數,該怎麼轉換呢?
試試看這樣可不可以
Dim arr() As Double
ReDim arr(count*3)
參考自:https://blog.gtwang.org/programming/excel-vba-array/ 裡面動態陣列的部分
這段可以了,請問以下應該改成.Cells(i, x).Value就可以了吧?
.getCellByPosition(x, i).string => .Cells(i+1, x+1).Value
Excel 好像是從 1 開始算,而 Calc 是從 0 開始算,所以才有那個 +1
例如儲存格 "B5" 在 Excel 是 Cells(5, 2)
但是在 Calc 是 getCellByPosition(1, 4)
如果不確定空值判斷的方式,建議可以把 while t <> ""
先改成 while t <> "" And i<5000
做測試,確認沒問題再拿掉 And i<5000
,避免變成無窮迴圈。
跑完了...速度快的嚇人,非常感謝!!
請問這個概念是什麼呢? 通常是用在多條件比對的場合嗎?
一般搜尋資料的函式,為了通用性,並不會假設資料有排序。所以每次搜尋都是一個一個比對。這也是原本的狀況。
假設資料有排序,那麼可以用每次都對切一半,這樣找到資料就快很多。這是可以考慮優化的地方。(雖然我不是用這個)
很多程式都有類似 字典(Dictionary) 的資料結構,這種資料結構插入、搜尋的效率至少都有「對切一半」的效能(視內部實作而定),也就是 ,用 4096筆做比喻,原本要花4096單位的時間,會變成 單位的時間。
試試看
如果可用再來看細節
如果不能用或一樣慢就忽略這程式吧
Sub FindInRange()
i = 1
cate1 = "FCST"
cate2 = "SO"
cate3 = "Ship"
row1 = Cells(Rows.Count, 1).End(xlUp).Row
'Debug.Print row1
Do Until i >= row1
i = i + 1
Debug.Print i
Bnum = Range("a" & i)
a = 0
b = 0
c = 0
With Worksheets("aaa").Range("b:b")
Set Rng = .Find(Bnum, LookIn:=xlValues)
If Not Rng Is Nothing Then
firstAddress = Rng.Address
Do
sType = Rng.Offset(0, -1).Value
If (sType = "FCST") Then
a = Rng.Offset(0, 3).Value
ElseIf (sType = "SO") Then
b = Rng.Offset(0, 3).Value
ElseIf (sType = "Ship") Then
c = Rng.Offset(0, 3).Value
End If
Set Rng = .FindNext(Rng)
Loop While Not Rng Is Nothing And Rng.Address <> firstAddress
End If
End With
If Cells(i, 3) = "N" Then
If a > b + c Then
Cells(i, 4) = b + c
Else
Cells(i, 4) = a
End If
Else
Cells(i, 4) = a
End If
Loop
End Sub
程式可以執行,但結果都是空值(補一下頁面),然後還要再加一個限制條件該放哪呢? Bnum2是輸入值的公司,D:D是aaa中的公司行。然後FCST、SO、Ship三個值都要,找不到的話要補零因為最後還是要比較該回傳哪個值
a = Application.Evaluate("=ifna(Lookup(1,0/((aaa!A:A= """ & cate1 & """ ) * (aaa!B:B= """ & Bnum & """ )* (aaa!D:D= """ & Bnum2 & """ )), aaa!E:E),0)")
除了第二、三格正常之外,第四隔開始程式都找不到表aaa b列的值
h93243 要不要給一下測試用檔案阿?不然幫你測試還要自己建立資料。
PS. 我是幫別人說的,我手邊沒有 Excel 沒法幫忙。
程式已修改
只加了 a=0 b=0 c=0
執行結果如下
謝謝! 執行完約21分鐘(4261筆),是因為判斷的項目太多所以VBA必須要執行這麼久,還是電腦效能不夠呢? 因為目標是希望在10分鐘內完成