Tìm Subtotal bằng VBA

Liên hệ QC

moihocvba

Thành viên thường trực
Tham gia
16/8/20
Bài viết
207
Được thích
49
Chào anh chị GPE!
Em có một bảng dữ liệu như này:

1612406380686.png

Em muốn tính tổng các cột từ Sum1 đến Sum5 theo cột Mã số, kết quả như thế này:
1612406545576.png


Nếu dữ liệu ít có thể dùng lệnh Subtotal của Excel cũng ra, nhưng vì file của em dữ liệu quá lớn (hơn 95.000 dòng), nên khi em dùng lệnh subtotal có sẵn của excel thì Excel của em đứng luôn. Nên em có ý tưởng đưa dữ liệu này vào mảng để xử lý sau đó dáng kết quả ra một nơi khác cho nhẹ ạ. Em up file lên đây nhờ các anh chị giúp em code để em học hỏi ạ!
Em cảm ơn anh chị nhiều!
 

File đính kèm

  • Subtotal.xlsb
    1.7 MB · Đọc: 67
Lần chỉnh sửa cuối:
Ta xem code
Mã:
For Each iKey In Dic.Keys
        S = Split(Dic.Item(iKey), ",")
        iSub = k + UBound(S) + 1
        aSumSum(iSub, 1) = "Tong cong: "    ' (A)
        txtRng = "I" & iSub & ":N" & iSub    ' (C)
        If txt = Empty Then
            txt = txtRng
        Else
            txt = txt & "," & txtRng
        End If
...
With rng.Offset(1, ofset)
        .ClearContents
        .Resize(sRow, a) = aSumSum    ' (B)
    End With

Sheet1.Range(txt).Font.Bold = True
1. Nhìn vào (A) thì thấy dòng có "Tong cong" là dòng có chỉ số iSub trong mảng aSumSum. Nhìn vào (B) thì thấy mảng aSumSum được đập xuống sheet1 bắt đầu từ I2 (rng.Offset(1, ofset) là I2). Như thế dòng tô đậm có chỉ số iSub trong mảng aSumSum sẽ là dòng có chỉ số trên sheet là (iSub + 1). Nhưng nhìn vào (C) thì thấy địa chỉ của dòng tô mầu được ghi trong biến txt là iSub. Tức tô mầu lệch 1 dòng. Ví dụ 2 Mã đầu giống nhau nên dòng 3 trong mảng sSumSum được tô mầu do là dòng "Tong cong", tức iSub = 3. Từ (C) thấy I3:N3 được ghi vào txt, tức từ (B) thấy là dòng I3:N3 được tô đậm. Trong khi đó dòng tô đậm phải là I4:N4 (dòng 1 là tiêu đề, dòng 2 và 3 là Mã, và phải dòng 4 mới là Tong cong)

2. Sheet1.Range(txt).Font.Bold = True có thể bị lỗi do (đoán mò thôi):
a. chuỗi txt quá dài.
Nếu đúng thế thì thay vì gom hàng mấy chục nghìn địa chỉ vào biến txt thì thử gom hàng mấy chục nghìn vùng ̣(dùng UNION) vào biến Range. Nếu UNION thành công thì cũng có thể thời gian thực hiện là "đi nhậu về mới xong"?
Trong tập tin có 46329 dòng Tong cong

b. Lỗi do gom quá nhiều (hàng mấy chục nghìn) Range. Nếu đúng thế thì chỉ còn nước chia nhỏ thành "vài" nhóm và tô đậm từng nhóm.
Con cảm ơn Bác đã chỉ dẫn.
Dạ, đúng như Bác đã đoán, con đã chia nhỏ làm nhiều nhóm vùng để tô đậm ạ:
Mã:
Public Function TangTocCode(TangToc As Boolean)
    With Application
        .ScreenUpdating = Not (TangToc)
        .EnableEvents = Not (TangToc)
        .Calculation = IIf(TangToc, xlCalculationManual, xlCalculationAutomatic)
    End With
End Function

Sub SumKhongSort()
   
    On Error GoTo End_

    Call TangTocCode(True)
   
    Dim Dic As Object, sMa As String, aChuaSum() As Variant, aSumSum() As Variant
    Dim iKey, S, ik&, iSub&, sRow&, r As Long, k As Long, eA As Long, c As Long, a As Long
    Dim rng As Range, RngU As Range, rTam As Range, txtRng As String, txt As String
    Dim aTam() As Variant, nhom As Long, TongCong As Long, t As Single
   
    t = Timer


    Const ofset As Integer = 8
    Set rng = Sheet1.Range("A1")
    rng.CurrentRegion.Sort Key1:=rng, Order1:=xlAscending, Header:=xlYes
    aChuaSum = rng.CurrentRegion.Value
    eA = UBound(aChuaSum, 1): a = UBound(aChuaSum, 2)
    If eA < 2 Then Exit Sub
    Set Dic = CreateObject("scripting.dictionary")
    For r = 2 To eA
        sMa = aChuaSum(r, 1)
        If Not Dic.Exists(sMa) Then k = k + 1
        Dic.Item(sMa) = Dic.Item(sMa) & "," & r
    Next r
    sRow = eA + k - 1: k = 0
    ReDim aSumSum(1 To sRow, 1 To a)
    For Each iKey In Dic.Keys
        S = Split(Dic.Item(iKey), ",")
        iSub = k + UBound(S) + 1
        aSumSum(iSub, 1) = "Tong cong: "
        TongCong = TongCong + 1
        txtRng = "I" & iSub + 1 & ":N" & iSub + 1
        If txt = Empty Then
            txt = txtRng
        Else
            If Len(txt) < 100 Then
                txt = txt & "," & txtRng
            Else
                txt = txt & "," & txtRng
                nhom = nhom + 1
                ReDim Preserve aTam(1 To 1, 1 To nhom)
                aTam(1, nhom) = txt: txt = Empty
            End If
        End If
        For r = 1 To UBound(S)
            k = k + 1
            ik = CLng(S(r))
            aSumSum(k, 1) = aChuaSum(ik, 1)
            For c = 2 To a
                aSumSum(k, c) = aChuaSum(ik, c)
                aSumSum(iSub, c) = aSumSum(iSub, c) + aChuaSum(ik, c)
            Next c
        Next r
        k = k + 1
    Next iKey

    With rng.Offset(1, ofset)
         .CurrentRegion.Clear
         .Resize(sRow, a) = aSumSum
    End With
    rng.Offset(, ofset).Resize(, a).Value = rng.Resize(, a).Value

    For r = 1 To nhom
        txt = aTam(1, r)
        Sheet1.Range(txt).Font.Bold = True
    Next r
   
End_:

    Call TangTocCode(False)
   
    If Err.Number <> 0 Then
        MsgBox Err.Description, vbCritical, Err.Number
    Else
        txt = vbNewLine & "So dong tong cong la:  " & TongCong & vbNewLine & _
                          "So vung duoc to dam la:  " & nhom & vbNewLine & _
                          "Thoi gian xy ly la:  " & Timer - t
        MsgBox "Xong roi," & txt, vbInformation + vbOKOnly
    End If
   
End Sub

Kết hợp với hàm 'TangTocCode' thì thời gian xử lý cũng rất nhanh:
1612538510343.png

Còn nếu không sử dụng hàm 'TangTocCode' code thì code chạy hơn một phút:
1612538582363.png

:yahoo:
 
Upvote 0
Xin chào anh chị ạ!
Em áp dụng được bài của anh Quang_Hải vào file em rồi, nhưng quy trình của em gồm rất nhiều bước nên sau khi lắp ráp lại thì nó chạy rất là chậm, em treo máy nó chạy khoảng 1,5 giờ.
Em xin gửi file đầy đủ của em lên đây nhờ mấy anh chị em thử, vì em cũng tìm nhiều cách rồi mà nó ko chạy nhanh lên được. Trong file em có sheet mô tả ạ.

Riêng code của bạn NHN_Phương thì hơi quá sức với em nên em chưa áp dụng được, có lẽ vì cách đặt biến khá lạ, nên vừa đọc code vừa nhìn lại biến, đọc một xíu là não em rối nùi luôn :D

File của em ạ: https://drive.google.com/file/d/1_QZLWA5sKYNS8BMHqzii_nmFQPvDhdBK/view?usp=sharing

Cảm ơn anh chị rất nhiều vì đã hỗ trợ em!
Bạn thử xem có nhanh hơn chút nào không ạ: Tải file Excel
 

File đính kèm

  • TongCong.txt
    6.2 KB · Đọc: 28
Upvote 0
Con cảm ơn Bác đã chỉ dẫn.
Dạ, đúng như Bác đã đoán, con đã chia nhỏ làm nhiều nhóm vùng để tô đậm ạ:
Cứ thế này mấy ngày nữa bạn sẽ vào câu lạc bộ "Cao thủ VBA" thôi.

Để bạn không ngủ quên trên chiến thắng :D thì tôi góp ý chút.
Bạn dùng mảng 2 chiều aTam nhưng thực ra không có nhu cầu dùng mảng 2 chiều. Mảng một chiều cũng đủ. Sửa thành
Mã:
ReDim Preserve aTam(1 To nhom)
                aTam(nhom) = txt
...
For r = 1 To nhom
    txt = aTam(r)
    Sheet1.Range(txt).Font.Bold = True
Next r
 
Upvote 0
Mình có thắc mắc một chút. Xét trong bài này, nếu dùng For Each iKey in Dic.Keys rồi sau đó lại dùng Dic.Item(iKey) thì sao mình không sử dụng For Each iItem in Dic.Items ngay từ đầu luôn cho gọn.
 
Upvote 0
Cứ thế này mấy ngày nữa bạn sẽ vào câu lạc bộ "Cao thủ VBA" thôi.

Để bạn không ngủ quên trên chiến thắng :D thì tôi góp ý chút.
Bạn dùng mảng 2 chiều aTam nhưng thực ra không có nhu cầu dùng mảng 2 chiều. Mảng một chiều cũng đủ. Sửa thành
Mã:
ReDim Preserve c(1 To nhom)
                aTam(nhom) = txt
...
For r = 1 To nhom
    txt = aTam(r)
    Sheet1.Range(txt).Font.Bold = True
Next r

Dạ, nhờ Bác truyền cảm hứng cho con đó ạ.
Thời gian này trung tâm Nhật ngữ cũng đang nghỉ do dịch bệnh nên con không nặng nề với việc đó, do vậy trong lúc có thời gian con luyện thêm chút code ạ, nhưng con cũng nhanh quên lắm Bác ạ.
Đúng là mới đầu mảng 'aTam' con định dùng mảng một chiều nhưng do chưa quen nên đã loay hoay con nên con chuyển sang mảng dùng mảng hai chiều.
Cảm ơn Bác đã phát hiện và chỉ dẫn thêm cho con ạ.

Bạn thử xem có nhanh hơn chút nào không ạ: Tải file Excel

Bác cho con hỏi thêm với ạ, trong bài 42 ở trên code trong file txt và file Excel có đoạn:
Mã:
....
    aDuLieu = .Range("B14:G" & lr).Value
        r = UBound(aDuLieu, 1) 'r = 95716
        c = UBound(aDuLieu, 2)
        
        ReDim aKQ_PC(1 To r, 1 To 2)
        ReDim aKQ_Phu(1 To r, 1 To 2)
        
        For i = 1 To r 'Chay r lan 1 de tinh toan tong so tien cho moi ma so thue
            skey = aDuLieu(i, 1)
            If Not Dic.Exists(skey) Then
                a = a + 1
                Dic.Add skey, a
                aKQ_Phu(a, 1) = skey
                aKQ_Phu(a, 2) = aDuLieu(i, 6)
            Else
                ik = Dic.Item(skey)
                aKQ_Phu(ik, 2) = aKQ_Phu(ik, 2) + aDuLieu(i, 6)
            End If
        Next i

        For i = 1 To r 'Chay r lan 2 de tim kiem tong so tien cho moi ma so thue
            skey = aDuLieu(i, 1)
            For j = 1 To UBound(aKQ_Phu, 1)
                If skey = aKQ_Phu(j, 1) Then
                    aKQ_PC(i, 2) = aKQ_Phu(j, 2)
                    Exit For
                End If
            Next j
            aKQ_PC(i, 1) = aTimKiem(rFindPC, skey, 3)
            skey = aKQ_PC(i, 1)
            If Not dFile.Exists(skey) Then
                k = k + 1
                dFile.Add skey, k
            End If
            '------Chay thu code voi 1000 dong
            If i = 100 Then Exit For '<------Neu muon chay toan bo thi xoa doan nay
        Next i
....

Phải chạy 2 lần vòng lặp i với 95716 dòng, và trong vòng lặp chạy lần 2 có vòng lăp J,
Mã:
           For j = 1 To UBound(aKQ_Phu, 1)
                If skey = aKQ_Phu(j, 1) Then
                    aKQ_PC(i, 2) = aKQ_Phu(j, 2)
                    Exit For
                End If
            Next j

Như vậy số phần tử duyệt qua trong tất cả các vòng lặp rất khủng khiếp,
Con đã thử chạy xong vòng lặp i lần 1 rồi gán kết quả xuống sheet sau đó sẽ dùng phương thức find để bỏ vòng lặp J nhưng có vẻ lúc này code càng chậm hơn rất nhiều do mỗi for i lại set range tìm được khi find và gán giá trị vào mảng ạ.

Bác có ý tưởng gì để giảm số vòng lặp này không ạ hoặc là cách nào xử lý nhanh hơn ạ?
Con cảm ơn Bác.
 
Upvote 0
Mình có thắc mắc một chút. Xét trong bài này, nếu dùng For Each iKey in Dic.Keys rồi sau đó lại dùng Dic.Item(iKey) thì sao mình không sử dụng For Each iItem in Dic.Items ngay từ đầu luôn cho gọn.
Mỗi bài Toán luôn có nhiều cách giải mà bạn. Nhiều khi ta dùng keys do thói quen. Và keys thì luôn dùng được, còn Items chỉ trong vài trường hợp, vd. như trường hợp này. Vì thế người ta dùng keys mãi rồi quen.
 
Upvote 0
Như vậy số phần tử duyệt qua trong tất cả các vòng lặp rất khủng khiếp,
Con đã thử chạy xong vòng lặp i lần 1 rồi gán kết quả xuống sheet sau đó sẽ dùng phương thức find để bỏ vòng lặp J nhưng có vẻ lúc này code càng chậm hơn rất nhiều do mỗi for i lại set range tìm được khi find và gán giá trị vào mảng ạ.

Bác có ý tưởng gì để giảm số vòng lặp này không ạ hoặc là cách nào xử lý nhanh hơn ạ?
Con cảm ơn Bác.
Lưu ý:
1. Chủ thớt có sắp xếp dữ liệu. Cái này ta sẽ bàn sao. Trước hết về 2 vòng FOR.

2. mảng aKQ_Phu. Theo tôi là không cần thiết. Trong vòng FOR 1 kiểm tra skey. Nếu chưa có thì thêm nó với tư cách là KEY, và aDuLieu(i, 6), tức Số lượng, với tư cách là ITEM. Nếu có skey rồi thì cộng dồn Số lượng (là ITEM). Trong vòng FOR 2 với mỗi skey thì đọc tổng số lượng từ ITEM của Dic thôi. Không phải dùng For j nữa.

3. Về hàm aTimKiem thì tôi đề nghị thử bỏ vòng FOR. Thử thay bằng Application.Match + Application.Index.

4. Do mỗi skey có thể lặp lại nên trước tiên phải kiểm tra xem đã có "Tên viết tắt" cho Mã ấy chưa. Do cách tìm "Tên viết tắt" trong sheet PC dựa vào MÃ nên nếu cùng mã sẽ cùng "Tên viết tắt". Tức kiểm tra có Mã trong dFile chưa. Nếu chưa có thì mới gọi hàm aTimKiem. Kết quả trả về ta ghi nhớ trong dFile với tư cácch là ITEM, còn skey với tư cách là KEY.

Tóm lại vòng FOR 2
Mã:
For i = 1 To r           'Chay r lan 2 de tim kiem tong so tien cho moi ma so thue
            skey = aDuLieu(i, 1)
            aKQ_PC(i, 2) = Dic.Item(skey)   ' so luong
            If Not dFile.Exists(skey) Then dFile.Add skey, aTimKiem(rFindPC, skey, 3)
            aKQ_PC(i, 1) = dFile.Item(skey)
            '------Chay thu code voi 1000 dong
            If i = 1000 Then Exit For
            '------Neu muon chay toan bo thi xoa doan nay
        Next i

5. Do ta thay đổi cấu trúc của dFile (mã là KEY chứ không phải "Tên viết tắt" là KEY) nên phải sửa
Mã:
If k Then
    For ik = 0 To dFile.Count - 1
        skey = dFile.Keys()(ik)
        Call LocKetQua(skey, Book, shtM12, lr, shtMau)
    Next ik
End If
thành
Mã:
If dFile.Count Then
    For Each skey In dFile.keys
        Call LocKetQua(dFile.Item(skey), Book, shtM12, lr, shtMau)
    Next skey
End If

6. Về Sort thì tôi đề nghị Sort lần nhưng trước tiên theo cột AM (Key1), tiếp theo theo cột B (Key2)
 
Upvote 0
Lưu ý:
1. Chủ thớt có sắp xếp dữ liệu. Cái này ta sẽ bàn sao. Trước hết về 2 vòng FOR.

2. mảng aKQ_Phu. Theo tôi là không cần thiết. Trong vòng FOR 1 kiểm tra skey. Nếu chưa có thì thêm nó với tư cách là KEY, và aDuLieu(i, 6), tức Số lượng, với tư cách là ITEM. Nếu có skey rồi thì cộng dồn Số lượng (là ITEM). Trong vòng FOR 2 với mỗi skey thì đọc tổng số lượng từ ITEM của Dic thôi. Không phải dùng For j nữa.

3. Về hàm aTimKiem thì tôi đề nghị thử bỏ vòng FOR. Thử thay bằng Application.Match + Application.Index.

4. Do mỗi skey có thể lặp lại nên trước tiên phải kiểm tra xem đã có "Tên viết tắt" cho Mã ấy chưa. Do cách tìm "Tên viết tắt" trong sheet PC dựa vào MÃ nên nếu cùng mã sẽ cùng "Tên viết tắt". Tức kiểm tra có Mã trong dFile chưa. Nếu chưa có thì mới gọi hàm aTimKiem. Kết quả trả về ta ghi nhớ trong dFile với tư cácch là ITEM, còn skey với tư cách là KEY.

Tóm lại vòng FOR 2
Mã:
For i = 1 To r           'Chay r lan 2 de tim kiem tong so tien cho moi ma so thue
            skey = aDuLieu(i, 1)
            aKQ_PC(i, 2) = Dic.Item(skey)   ' so luong
            If Not dFile.Exists(skey) Then dFile.Add skey, aTimKiem(rFindPC, skey, 3)
            aKQ_PC(i, 1) = dFile.Item(skey)
            '------Chay thu code voi 1000 dong
            If i = 1000 Then Exit For
            '------Neu muon chay toan bo thi xoa doan nay
        Next i

5. Do ta thay đổi cấu trúc của dFile (mã là KEY chứ không phải "Tên viết tắt" là KEY) nên phải sửa
Mã:
If k Then
    For ik = 0 To dFile.Count - 1
        skey = dFile.Keys()(ik)
        Call LocKetQua(skey, Book, shtM12, lr, shtMau)
    Next ik
End If
thành
Mã:
If dFile.Count Then
    For Each skey In dFile.keys
        Call LocKetQua(dFile.Item(skey), Book, shtM12, lr, shtMau)
    Next skey
End If

6. Về Sort thì tôi đề nghị Sort lần nhưng trước tiên theo cột AM (Key1), tiếp theo theo cột B (Key2)
Con chào Bác Siwtom, cơn Bác đã chỉ dẫn ạ nhưng với con có lẽ tới đây là quá sức với con rồi Bác ơi:
Về mục 2 con chưa hiểu lắm nên con cũng chưa làm được Bác ạ, cụ thể bước: "Nếu có skey rồi thì cộng dồn Số lượng (là ITEM)"
Về mục 3 thì với Indext và Match con đã thử Application & Application.WorksheetFunction nhưng đều đang có vấn đề:
Application trả về Error 2042,
còn Application.WorksheetFunction thì lỗi debug "Unable to get the Match property of the WorksheetFunction class"

1612624002197.png
Mục 4 & mục 5 do vướng mục 3 con cũng dừng lại ạ.
Bác chỉ dẫn giúp con với ạ.
 
Lần chỉnh sửa cuối:
Upvote 0
Khi tính subtotal mình ít khi nào dùng đến Dic và thường chỉ xử lý 1 vòng lặp cho dữ liệu nguồn.
Đúng là không ai giống ai về thuật toán. Nếu dùng đến Dic thì cũng chỉ là 1 vòng lặp cho dữ liệu nguồn
Mã:
Sub Sub_Total()
Dim Dic As Object, sArr(), dArr(), i As Long, j As Long, k As Long, tmp As String, SubTotal()
Set Dic = CreateObject("scripting.dictionary")
With Sheets("Sheet1")
    .Range("A2", .Range("A" & Rows.Count).End(3)).Resize(, 6).Sort .[A1]
    sArr = .Range("A2", .Range("A" & Rows.Count).End(3)).Resize(, 6).Value
End With
ReDim dArr(1 To UBound(sArr) * 2, 1 To UBound(sArr, 2))
ReDim SubTotal(1 To UBound(sArr, 2))
Dic(sArr(1, 1)) = Empty
k = 1
For j = 2 To UBound(sArr, 2)
    SubTotal(j) = sArr(1, j)
Next
For j = 1 To UBound(sArr, 2)
    dArr(k, j) = sArr(1, j)
Next
For i = 2 To UBound(sArr)
    tmp = sArr(i, 1)
    If Not Dic.exists(tmp) Then
        k = k + 1
        Dic.Add tmp, Empty
        For j = 2 To UBound(sArr, 2)
            dArr(k, j) = SubTotal(j)
        Next
        k = k + 1
        For j = 1 To UBound(sArr, 2)
            dArr(k, j) = sArr(i, j)
        Next
        ReDim SubTotal(1 To UBound(sArr, 2))
        For j = 2 To UBound(sArr, 2)
            SubTotal(j) = SubTotal(j) + sArr(i, j)
        Next
    Else
        k = k + 1
        For j = 1 To UBound(sArr, 2)
            dArr(k, j) = sArr(i, j)
        Next
        For j = 2 To UBound(sArr, 2)
            SubTotal(j) = SubTotal(j) + sArr(i, j)
        Next
    End If
Next
k=k+1
For j = 2 To UBound(sArr, 2)
    dArr(k, j) = SubTotal(j)
Next
Sheets("Sheet3").[A2].Resize(k, UBound(dArr, 2)) = dArr
End Sub
 
Lần chỉnh sửa cuối:
Upvote 0
Con chào Bác Siwtom, cơn Bác đã chỉ dẫn ạ nhưng với con có lẽ tới đây là quá sức với con rồi Bác ơi:
Về mục 2 con chưa hiểu lắm nên con cũng chưa làm được Bác ạ, cụ thể bước: "Nếu có skey rồi thì cộng dồn Số lượng (là ITEM)"
Về mục 3 thì với Indext và Match con đã thử Application & Application.WorksheetFunction nhưng đều đang có vấn đề:
Application trả về Error 2042,
còn Application.WorksheetFunction thì lỗi debug "Unable to get the Match property of the WorksheetFunction class"
Thực ra tôi không muốn tham gia chủ đề này vì dữ liệu rất nhiều, hàng chục nghìn dòng. Nếu kiểm tra tính đúng đắn của dữ liệu đầu vào từng dòng 1 thì rất cực. Còn nếu không kiểm tra mà dữ liệu không chuẩn thì khi có lỗi do dữ liệu việc tìm lỗi cũng cực.

Nếu dùng Application.Match + Index có vấn đề thì có thể thử dùng Application.Vlookup. Nhưng thôi, ta bỏ hàm aTimKiem.

Tôi đề nghị dùng đối tượng tenviettat để thay thế.
Hãy kiểm tra code sau
Mã:
Sub TaoBaoCao()
    Dim Dic As Object, dFile As Object, tenviettat As Object, skey
    Dim aDuLieu() As Variant, aKQ_PC() As Variant
    Dim i As Long, j As Long, lr As Long, a As Long, k As Long, c As Long, r As Long
    Dim Book As Workbook, Rng As Range, ik As Long, FindPC_data()
    Dim shtM12 As Worksheet, shtPC As Worksheet, shtPhu As Worksheet, shtMau As Worksheet

    Dim t As Single
    t = Timer
    
    Call TangTocCode(True)
    
    Set Book = ThisWorkbook
    Set shtM12 = Book.Worksheets("M12")
    Set shtPC = Book.Worksheets("PC")
    Set shtMau = Book.Worksheets("Mau")
    
    Set Dic = CreateObject("Scripting.dictionary")
    Set dFile = CreateObject("Scripting.dictionary")
    
    With shtPC
        lr = .Range("B" & .Rows.Count).End(xlUp).Row            ' nen dung cot B
        FindPC_data = .Range("B5").Resize(lr - 4, 3).Value                      ' du lieu vung tim kiem trong PC
    End With
    Set tenviettat = CreateObject("Scripting.dictionary")
    For r = 1 To UBound(FindPC_data, 1)
        skey = FindPC_data(r, 1)
        If Not tenviettat.exists(skey) Then tenviettat.Add skey, FindPC_data(r, 3)
    Next r
    
    With shtM12
        lr = .Range("B" & .Rows.Count).End(xlUp).Row
        If lr < 14 Then
            MsgBox "Khong co du lieu.", vbInformation
            GoTo End_
        End If
        
        aDuLieu = .Range("B14:G" & lr).Value
        r = UBound(aDuLieu, 1)  'r = 95716
        c = UBound(aDuLieu, 2)
        
        ReDim aKQ_PC(1 To r, 1 To 2)
        For i = 1 To r              'Chay r lan 1 de tinh toan tong so tien cho moi ma so thue
            skey = aDuLieu(i, 1)
            If Not Dic.exists(skey) Then
                Dic.Add skey, aDuLieu(i, 6)         ' Ma voi tu cach KEY, So luong voi tu cach ITEM
            Else
                Dic.Item(skey) = Dic.Item(skey) + aDuLieu(i, 6)  ' cong don So luong
            End If
        Next i

        For i = 1 To r           'Chay r lan 2 de tim kiem tong so tien cho moi ma so thue
            skey = aDuLieu(i, 1)
            aKQ_PC(i, 2) = Dic.Item(skey)   ' so luong
            If tenviettat.exists(skey) Then
                aKQ_PC(i, 1) = tenviettat.Item(skey)
            Else
                aKQ_PC(i, 1) = "Khong xac dinh"
            End If
            If Not dFile.exists(aKQ_PC(i, 1)) Then dFile.Add aKQ_PC(i, 1), 0
        Next i
        
        .Range("AL14:AM" & lr).Value = aKQ_PC
        .Range("B14:AM" & lr).Sort Key1:=.Range("AM14"), Order1:=xlDescending, key2:=.Range("B14"), Order2:=xlDescending
        
        If dFile.Count Then
            For Each skey In dFile.keys
                Call LocKetQua(skey, Book, shtM12, lr, shtMau)
            Next skey
        End If

    End With
End_:

    Call TangTocCode(False)
    
    MsgBox "Thoi gian chay code la: " & Round((Timer - t) / 60, 2) & " phut"
    
End Sub

Tôi không xem sub LocKetQua và SumTongCong đâu nhé. Nhưng trong LocKetQua ít nhất phải có ByVal sFile As String
 
Lần chỉnh sửa cuối:
Upvote 0
Khi tính subtotal mình ít khi nào dùng đến Dic và thường chỉ xử lý 1 vòng lặp cho dữ liệu nguồn.
Đúng là không ai giống ai về thuật toán. Nếu dùng đến Dic thì cũng chỉ là 1 vòng lặp cho dữ liệu nguồn
Chắc hiểu sai ý.
Trong bài toán ở bài #1 giả sử dữ liệu không được sắp xếp theo cột A - Mã.

Nếu code không sắp xếp lại dữ liệu theo cột A, và cũng không dùng DIC hoặc "cái gì đó giống DIC" thì không làm được. Làm bằng niềm tin à? ***

Nếu dùng SORT như bài trích này thì không cần DIC. Nếu dùng DIC thì lại không cần SORT. Ý là thế.

***: thực ra không sort và không dic cũng được. Viết một code phức tạp với vòng lặp. Nhưng đã thế thì thà sort hoặc dic còn sướng hơn.
 
Upvote 0
Con chào Bác Siwtom, cơn Bác đã chỉ dẫn ạ nhưng với con có lẽ tới đây là quá sức với con rồi Bác ơi:
Về mục 2 con chưa hiểu lắm nên con cũng chưa làm được Bác ạ, cụ thể bước: "Nếu có skey rồi thì cộng dồn Số lượng (là ITEM)"
Về mục 3 thì với Indext và Match con đã thử Application & Application.WorksheetFunction nhưng đều đang có vấn đề:
1612660167960.png
Mục 2 cộng dồn mà không hiểu thì quá tệ
Mục 3 giả sử không bị lỗi Match cũng bị lỗi khác: Index thiếu tham số và trả về 1 mảng, làm sao mà Print a
 
Upvote 0
Về mục 3 thì với Indext và Match con đã thử Application & Application.WorksheetFunction nhưng đều đang có vấn đề:
Application trả về Error 2042,
còn Application.WorksheetFunction thì lỗi debug "Unable to get the Match property of the WorksheetFunction class"
Về lý thuyết thì ví dụ
Mã:
Public Function aTimKiem(ByVal rFind As Range, ByVal sTim As String, ByVal c As Integer) As String
Dim k
    Const KXD As String = "Khong xac dinh"

    k = Application.Match(sTim, rFind.Resize(, 1), 0)
    If IsError(k) Then
        aTimKiem = KXD
    Else
        aTimKiem = rFind(k, c)
    End If
End Function

Public Function aTimKiem1(ByVal rFind As Range, ByVal sTim As String, ByVal c As Integer) As String
    Dim result
    Const KXD As String = "Khong xac dinh"

    result = Application.VLookup(sTim, rFind, 3, 0)
    If IsError(result) Then
        aTimKiem1 = KXD
    Else
        aTimKiem1 = result
    End If
End Function

Tức có điểm quan trọng:

- sau khi gọi Match, Vlookup thì phải kiểm tra xem hàm trả về lỗi hay không lỗi, và tùy vào kết quả đó mà xử lý.
 
Lần chỉnh sửa cuối:
Upvote 0
Thực ra tôi không muốn tham gia chủ đề này vì dữ liệu rất nhiều, hàng chục nghìn dòng. Nếu kiểm tra tính đúng đắn của dữ liệu đầu vào từng dòng 1 thì rất cực. Còn nếu không kiểm tra mà dữ liệu không chuẩn thì khi có lỗi do dữ liệu việc tìm lỗi cũng cực.

Nếu dùng Application.Match + Index có vấn đề thì có thể thử dùng Application.Vlookup. Nhưng thôi, ta bỏ hàm aTimKiem.

Tôi đề nghị dùng đối tượng tenviettat để thay thế.
Hãy kiểm tra code sau
Mã:
Sub TaoBaoCao()
    Dim Dic As Object, dFile As Object, tenviettat As Object, skey
    Dim aDuLieu() As Variant, aKQ_PC() As Variant
    Dim i As Long, j As Long, lr As Long, a As Long, k As Long, c As Long, r As Long
    Dim Book As Workbook, Rng As Range, ik As Long, FindPC_data()
    Dim shtM12 As Worksheet, shtPC As Worksheet, shtPhu As Worksheet, shtMau As Worksheet

    Dim t As Single
    t = Timer
  
    Call TangTocCode(True)
  
    Set Book = ThisWorkbook
    Set shtM12 = Book.Worksheets("M12")
    Set shtPC = Book.Worksheets("PC")
    Set shtMau = Book.Worksheets("Mau")
  
    Set Dic = CreateObject("Scripting.dictionary")
    Set dFile = CreateObject("Scripting.dictionary")
  
    With shtPC
        lr = .Range("B" & .Rows.Count).End(xlUp).Row            ' nen dung cot B
        FindPC_data = .Range("B5").Resize(lr - 4, 3).Value                      ' du lieu vung tim kiem trong PC
    End With
    Set tenviettat = CreateObject("Scripting.dictionary")
    For r = 1 To UBound(FindPC_data, 1)
        skey = FindPC_data(r, 1)
        If Not tenviettat.exists(skey) Then tenviettat.Add skey, FindPC_data(r, 3)
    Next r
  
    With shtM12
        lr = .Range("B" & .Rows.Count).End(xlUp).Row
        If lr < 14 Then
            MsgBox "Khong co du lieu.", vbInformation
            GoTo End_
        End If
      
        aDuLieu = .Range("B14:G" & lr).Value
        r = UBound(aDuLieu, 1)  'r = 95716
        c = UBound(aDuLieu, 2)
      
        ReDim aKQ_PC(1 To r, 1 To 2)
        For i = 1 To r              'Chay r lan 1 de tinh toan tong so tien cho moi ma so thue
            skey = aDuLieu(i, 1)
            If Not Dic.exists(skey) Then
                Dic.Add skey, aDuLieu(i, 6)         ' Ma voi tu cach KEY, So luong voi tu cach ITEM
            Else
                Dic.Item(skey) = Dic.Item(skey) + aDuLieu(i, 6)  ' cong don So luong
            End If
        Next i

        For i = 1 To r           'Chay r lan 2 de tim kiem tong so tien cho moi ma so thue
            skey = aDuLieu(i, 1)
            aKQ_PC(i, 2) = Dic.Item(skey)   ' so luong
            If tenviettat.exists(skey) Then
                aKQ_PC(i, 1) = tenviettat.Item(skey)
            Else
                aKQ_PC(i, 1) = "Khong xac dinh"
            End If
            If Not dFile.exists(aKQ_PC(i, 1)) Then dFile.Add aKQ_PC(i, 1), 0
        Next i
      
        .Range("AL14:AM" & lr).Value = aKQ_PC
        .Range("B14:AM" & lr).Sort Key1:=.Range("AM14"), Order1:=xlDescending, key2:=.Range("B14"), Order2:=xlDescending
      
        If dFile.Count Then
            For Each skey In dFile.keys
                Call LocKetQua(skey, Book, shtM12, lr, shtMau)
            Next skey
        End If

    End With
End_:

    Call TangTocCode(False)
  
    MsgBox "Thoi gian chay code la: " & Round((Timer - t) / 60, 2) & " phut"
  
End Sub

Tôi không xem sub LocKetQua và SumTongCong đâu nhé. Nhưng trong LocKetQua ít nhất phải có ByVal sFile As String
Con cảm ơn Bác Siwtom đã chỉ dẫn cho con phương pháp ạ, đúng là đề bài dữ liệu nhiều đã là một khó khăn nhưng cách làm cho đề bài này cũng phải lặp lại nhiều vòng lặp để xử lý.
Về cơ bản với đề bài này con cũng đã nắm được rất nhiều đặc biệt là cách dùng Dic.
Về lý thuyết thì ví dụ
Mã:
Public Function aTimKiem(ByVal rFind As Range, ByVal sTim As String, ByVal c As Integer) As String
Dim k
    Const KXD As String = "Khong xac dinh"
    On Error Resume Next
    k = Application.Match(sTim, rFind.Resize(, 1), 0)
    If IsError(k) Then
        aTimKiem = KXD
    Else
        aTimKiem = rFind(k, c)
    End If
End Function

Public Function aTimKiem1(ByVal rFind As Range, ByVal sTim As String, ByVal c As Integer) As String
    Dim result
    Const KXD As String = "Khong xac dinh"
    On Error Resume Next
    result = Application.VLookup(sTim, rFind, 3, 0)
    If IsError(result) Then
        aTimKiem1 = KXD
    Else
        aTimKiem1 = result
    End If
End Function

Tức có 2 điểm quan trọng:
- dùng On Error Resume Next
- sau khi gọi Match, Vlookup thì phải kiểm tra xem hàm trả về lỗi hay không lỗi, và tùy vào kết quả đó mà xử lý.
Dạ Bác, sau khi Bác chỉ dẫn cụ thể vậy con hiểu ạ.
Cảm ơn Bác Siwtom nhiều nhiều,con chúc Bác nhiều sức khỏe ạ.
Mục 2 cộng dồn mà không hiểu thì quá tệ
Mục 3 giả sử không bị lỗi Match cũng bị lỗi khác: Index thiếu tham số và trả về 1 mảng, làm sao mà Print a
Hôhô con chào chú Mỹ,có thể trước đây con đã gặp nhiều nhưng đầu óc con thì không thể nhớ lâu thù dai được bằng chú Mỹ, giờ con mới luyện kỹ hơn ạ :"'
Thường là con thấy kết quả cộng dồn sẽ gán vào mảng, nên khi gặp tình huống gán vào Dic thì con chưa quen:
Mã:
 Dic.Item(skey) = Dic.Item(skey) + aDuLieu(i, 6)  ' cong don So luong
Giờ thì con đã hiểu, đúng là giải thuật rất quan trọng.. giờ chú Mỹ mà có vướng mắc gì tương tự kiểu này thì
----------
Xin kính chúc tất cả mọi người đón một cái tết bình an ạ. _)(#;
OT dọn dẹp nhà cửa đây ạ, hihi. :yahoo:
 
Upvote 0
Dạ Bác, sau khi Bác chỉ dẫn cụ thể vậy con hiểu ạ.


Bạn xem lại bài #53.

Tôi đã xóa On Error Resume Next ở cả 2 hàm. Do tôi nhầm với WorksheetFunction.Match và WorksheetFunction.Vlookup.

Khi dùng Application.WorksheetFunction.Match, Application.WorksheetFunction.Vlookup, tổng quát Application.WorksheetFunction.<hàm nào đó>, thì phải dùng On Error Resume Next vì nếu không có mà khi hàm gọi có lỗi thì việc thực hiện code sẽ bị ngắt. Dùng On Error Resume Next và ngay sau khi gọi hàm thì kiểm tra xem đã có lỗi hay không (dùng đối tượng Err).

Khi dùng Application.Match, Application.Vlookup, tổng quát Application.<hàm nào đó>, thì không cần On Error Resume Next vì nếu gọi hàm có lỗi thì việc thực hiện code không bị ngắt. Hàm sẽ trả về giá trị lỗi nếu gọi hàm có lỗi, hoặc giá trị đúng. Vì thế sau khi gọi hàm thì phải kiểm tra xem giá trị mà hàm trả về có là lỗi hay không. Nếu không là lỗi thì đó là giá trị đúng mà hàm trả về.

Còn việc gọi LocKetQua để ghi > 40 tập tin nữa. Có thể code của bạn sẽ chạy lâu.
 
Lần chỉnh sửa cuối:
Upvote 0
Nhớ lưu ý mấy cái tên sheet. Hiện tại theo file bạn gởi là sheet nguồn là sheet1, kết quả hiện ở sheet3. Nếu cần thì sửa lại cho đúng thực tế
Mã:
Sub Sub_total()
Dim sArr(), i As Long, Tmp(), j As Long, k As Long, SubTotal()
With Sheets("sheet1")
   .Range("A2", .Range("A" & Rows.Count).End(3)).Resize(, 6).Sort .[A1]
   sArr = .Range("A2", .Range("A" & Rows.Count).End(3)).Resize(, 6).Value
End With
ReDim Tmp(1 To UBound(sArr) * 2, 1 To UBound(sArr, 2))
ReDim SubTotal(1 To UBound(sArr, 2))
For i = 1 To UBound(sArr) - 1
   If sArr(i, 1) = sArr(i + 1, 1) Then
      k = k + 1
      Tmp(k, 1) = sArr(i, 1)
      For j = 2 To UBound(sArr, 2)
         Tmp(k, j) = sArr(i, j)
         SubTotal(j - 1) = SubTotal(j - 1) + sArr(i, j)
      Next
    
   Else
      k = k + 1
      Tmp(k, 1) = sArr(i, 1)
      For j = 2 To UBound(sArr, 2)
         Tmp(k, j) = sArr(i, j)
         SubTotal(j - 1) = SubTotal(j - 1) + sArr(i, j)
      Next
      k = k + 1
      For j = 2 To UBound(sArr, 2)
         Tmp(k, j) = SubTotal(j - 1)
      Next
      ReDim SubTotal(1 To UBound(sArr, 2))
   End If
Next
Sheets("sheet3").[A2].Resize(k, UBound(Tmp, 2)) = Tmp
End Sub
******
Code còn thiếu kết quả của dòng cuối. Bạn cố gắng suy nghĩ và sửa nhẹ lại cho đủ hén
xin loi gi làm phiền : thêm code vòng lập For
e có 1 bảng đã tạo From nhập liệu : sau khi bấm nút "Nhập Ngày" thì e muốn nó tự động chở về ô C1 , để nhập tiếp cho nhanh
Bài đã được tự động gộp:

xin loi gi làm phiền : thêm code vòng lập For
e có 1 bảng đã tạo From nhập liệu : sau khi bấm nút "Nhập Ngày" thì e muốn nó tự động chở về ô C1 , để nhập tiếp cho nhanh
mail em : txnghids@gmail.com
 

File đính kèm

  • danh sach CoVid 2021_nghi (1).xlsm
    47.1 KB · Đọc: 5
Upvote 0
Về lý thuyết thì ví dụ
Mã:
Public Function aTimKiem(ByVal rFind As Range, ByVal sTim As String, ByVal c As Integer) As String
Dim k
    Const KXD As String = "Khong xac dinh"

    k = Application.Match(sTim, rFind.Resize(, 1), 0)
    If IsError(k) Then
        aTimKiem = KXD
    Else
        aTimKiem = rFind(k, c)
    End If
End Function

Public Function aTimKiem1(ByVal rFind As Range, ByVal sTim As String, ByVal c As Integer) As String
    Dim result
    Const KXD As String = "Khong xac dinh"

    result = Application.VLookup(sTim, rFind, 3, 0)
    If IsError(result) Then
        aTimKiem1 = KXD
    Else
        aTimKiem1 = result
    End If
End Function

Tức có điểm quan trọng:

- sau khi gọi Match, Vlookup thì phải kiểm tra xem hàm trả về lỗi hay không lỗi, và tùy vào kết quả đó mà xử lý.
Cảm ơn Bác đã chỉ dẫn cho con: sau khi xem lại cách dùng 'Match' của Bác,con xem lại bài 48 thì đã thấy con cả Index và Match con bị nhầm tham chiếu dẫn đến sai cột (vậy mà chú Mỹ @ptm0412 cũng không để ý để phát hiện ra ahihi)
Mã:
    ...
    k = Application.Match(sTim, rFind.Resize(, 1), 0)
    If IsError(k) Then
        aTimKiem = KXD
    Else
        aTimKiem = rFind(k, c)
    ...
Có lẽ 'Match' là khá nhanh, nhưng so với 'tenviettat' thì 'Match' chẳng là gì , 'tenviettat' cho tốc độ siêu nhanh }}}}}
Còn việc gọi LocKetQua để ghi > 40 tập tin nữa. Có thể code của bạn sẽ chạy lâu.
Ý,vấn đề lọc kết quả thì con lại không để ý đến vì hoàn toàn dựa theo cái cũ của code cũ bài 36 , con chỉ rút gọn và cho nó rõ ràng hơn cho sub 'LocKetQua' và trong 'LocKetQua' thì cũng 'SumTongCong' là vấn đề chính đã được bàn nhiều trong chủ đề này rồi ạ. Nên con cũng chưa nghĩ đến tối ưu về tăng tốc.
Bác thấy vấn đề gì chỉ thêm cho với ạ.
 
Lần chỉnh sửa cuối:
Upvote 0
cả Index và Match con bị nhầm tham chiếu dẫn đến sai cột (vậy mà chú Mỹ @ptm0412 cũng không để ý để phát hiện ra ahihi)
Đọc lại cho kỹ đi nhá! Tôi nói rõ là Index thiếu 1 tham số nhá! Còn match thì đúng cột hay không thì kệ, tôi không xem file, chỉ đọc code
 
Upvote 0
xin loi gi làm phiền : thêm code vòng lập For
e có 1 bảng đã tạo From nhập liệu : sau khi bấm nút "Nhập Ngày" thì e muốn nó tự động chở về ô C1 , để nhập tiếp cho nhanh
Bài đã được tự động gộp:


mail em : txnghids@gmail.com

Bạn thử thay Sub Ngay_data() bằng đoạn bên dưới thử xem ạ:

Mã:
Sub Ngay_data()

    ''''''khai báo bien'''''
    Dim form As Worksheet
    Dim Ngay As Worksheet
    Dim hang_cuoi As Long
    Dim Data() As Variant
    '''''''khai bao ten sheet'''''''
    Set form = ThisWorkbook.Sheets("Form")
    Set Ngay = ThisWorkbook.Sheets("Ngay")
    ''''''copy danh sach'''''''''''
   
    hang_cuoi = Ngay.Cells(Ngay.Rows.Count, "B").End(xlUp).Row + 1
    If hang_cuoi < 12 Then hang_cuoi = 12
   
    Data = form.Range("C1:C15").Value
    Ngay.Range("B" & hang_cuoi).Resize(, UBound(Data, 1)).Value = WorksheetFunction.Transpose(Data)
    Data = form.Range("C17:C19").Value
    Ngay.Range("S" & hang_cuoi).Resize(, UBound(Data, 1)).Value = WorksheetFunction.Transpose(Data)
   
    form.Range("C1").Activate

End Sub
 

File đính kèm

  • danh sach CoVid 2021_nghi (1).xlsm
    47.9 KB · Đọc: 5
Lần chỉnh sửa cuối:
Upvote 0
Xin chào anh chị ạ!
Em áp dụng được bài của anh Quang_Hải vào file em rồi, nhưng quy trình của em gồm rất nhiều bước nên sau khi lắp ráp lại thì nó chạy rất là chậm, em treo máy nó chạy khoảng 1,5 giờ.
Em xin gửi file đầy đủ của em lên đây nhờ mấy anh chị em thử, vì em cũng tìm nhiều cách rồi mà nó ko chạy nhanh lên được. Trong file em có sheet mô tả ạ.

Riêng code của bạn NHN_Phương thì hơi quá sức với em nên em chưa áp dụng được, có lẽ vì cách đặt biến khá lạ, nên vừa đọc code vừa nhìn lại biến, đọc một xíu là não em rối nùi luôn :D

File của em ạ: https://drive.google.com/file/d/1_QZLWA5sKYNS8BMHqzii_nmFQPvDhdBK/view?usp=sharing

Cảm ơn anh chị rất nhiều vì đã hỗ trợ em!
Chào bạn,
Từ 1,5 giờ xuống còn chưa đến 1,5 phút, thực sự quá kinh điển. Bạn xem bài # 50 nhé, toàn bộ code của bạn đã được sửa lại trong trong file txt.
Do các file xuất ra nặng mỗi file 10MB vì thế sheet 'Mau' bạn phải xóa toàn bộ các dòng từ dòng 16 trở đi (để lại dòng 15) như file bài # 42.
 

File đính kèm

  • Code.txt
    5.7 KB · Đọc: 10
Upvote 0
Web KT
Back
Top Bottom