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:
Chào anh chị GPE!
Em có một bảng dữ liệu như này:

View attachment 253932

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:
View attachment 253933


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!
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
 
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
Dạ em cảm ơn anh Thành viên gạo cội Quang_Hải ạ! Code chạy rất nhanh ạ. Nhưng mã cuối nó ko được tổng, anh xem lại dùm em thử nhé! Cám ơn anh!

1612411745014.png
 
Upvote 0
Dạ em cảm ơn anh Thành viên gạo cội Quang_Hải ạ! Code chạy rất nhanh ạ. Nhưng mã cuối nó ko được tổng, anh xem lại dùm em thử nhé! Cám ơn anh!

View attachment 253942
Mình đã ghi chú phía trên rồi, code thiếu mất dữ liệu dòng cuối. Mình muốn bạn tìm ra nguyên nhân. Khi nào bó tay thì copy code này về xài
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)(2)).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
 
Upvote 0
Mình đã ghi chú phía trên rồi, code thiếu mất dữ liệu dòng cuối. Mình muốn bạn tìm ra nguyên nhân. Khi nào bó tay thì copy code này về xài
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)(2)).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
Ok, em sẽ nghiên cứu trước khi xài code này! Một lần nữa cảm ơn anh!
 
Upvote 0
Nhờ làm giùm thì nói là nhờ làm giùm.
Học thì học giải thuật chứ ai học đọc code của người khác.
Giải thuật code:
- Copy cột đầu vào một mảng
- đọc mảng, dùng Dictionary để lọc duy nhất.
- đọc đít sần, mỗi key thì thêm vào một chuỗi "SUB" ở sau, và ghi tiếp theo lên bảng tính
- sort bảng kết quả. Như vậy mấy dòng ghi sau sẽ thành dòng subtotals cho mỗi mã.
- Copy vào một mảng
- đọc và tính subtotal
- ghi mảng trở lại.
- hết
 
Upvote 0
Nhờ làm giùm thì nói là nhờ làm giùm.
Học thì học giải thuật chứ ai học đọc code của người khác.
Giải thuật code:
- Copy cột đầu vào một mảng
- đọc mảng, dùng Dictionary để lọc duy nhất.
- đọc đít sần, mỗi key thì thêm vào một chuỗi "SUB" ở sau, và ghi tiếp theo lên bảng tính
- sort bảng kết quả. Như vậy mấy dòng ghi sau sẽ thành dòng subtotals cho mỗi mã.
- Copy vào một mảng
- đọc và tính subtotal
- ghi mảng trở lại.
- hết
Cám ơn anh VetMini đã tư vấn!
 
Upvote 0
Nếu làm theo truyền thống GPE thì hơi khác một chút:

- Copy dữ liệu vào array a
- Redim Preserve array a, tăng số cột lên 1
- Tạo một array b, kích thước bằng a
- Tạo một đít sần d. Đít sần này sẽ có key là mã, và item là chỉ số dòng trong mảng b
- Đọc a:
- - copy mã từ cột đầu vào cột cuối. (Điều này cần thiết để sort)
- - nếu mã đã có trong d thì:
- - - tăng số đếm array b lên 1
- - - với dòng mới của b, ghi mã & "SUB" ở cột cuối
- - - dùng mã, ghi một key mới trong d, item là chỉ số mới của b
- - dùng chỉ số mới, hoặc chỉ số lấyb từ d, cộng dồn subtotals vào dòng của b
- Copy a trở lại bảng
- Copy b vào bảng, kế tiếp theo a
- Sort theo cột cuối cùng
- Delete cột cuối cùng.
 
Upvote 0
Chào anh chị GPE!
Em có một bảng dữ liệu như này:

View attachment 253932

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:
View attachment 253933


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!

Theo gợi ý tại bài 7 của Bác @VetMini , Bạn thử code sau:
Mã:
Option Explicit

Sub SumSumSum()
    Application.ScreenUpdating = False
    Dim sMa As String, aChuaSum() As Variant, aSumSum() As Variant
    Dim r As Long, k As Long, eA As Long, c As Long, i As Double
    Const sR As String = "A1"
    With Sheet1
        aChuaSum = .Range(sR).CurrentRegion.Value2
        eA = UBound(aChuaSum, 1)
        If eA < 2 Then Exit Sub
        ReDim aSumSum(1 To eA, 1 To 6)
        Dim dic As New Scripting.Dictionary
        For r = 2 To eA
            sMa = aChuaSum(r, 1)
            For c = 2 To 6
                If Not dic.Exists(sMa) Then
                    k = k + 1
                    dic.Add sMa, k
                    aSumSum(k, 1) = sMa & "Sub"
                    aSumSum(k, c) = aChuaSum(r, c)
                Else
                    i = dic(sMa)
                    aSumSum(i, c) = aSumSum(i, c) + aChuaSum(r, c)
                End If
            Next c
        Next r
    End With
    With Sheet2.Range(sR)
        .Resize(eA + k, 6).NumberFormat = "@"
        .Resize(eA, 6).Value = aChuaSum
        .Offset(eA).Resize(k, 6).Value = aSumSum
        .Resize(eA + k, 6).Sort Key1:=.Range(sR), Order1:=xlAscending, Header:=xlYes
    End With
    Application.ScreenUpdating = True
End Sub
 
Upvote 0
Theo gợi ý tại bài 7 của Bác @VetMini , Bạn thử code sau:
Mã:
Option Explicit

Sub SumSumSum()
    Application.ScreenUpdating = False
    Dim sMa As String, aChuaSum() As Variant, aSumSum() As Variant
    Dim r As Long, k As Long, eA As Long, c As Long, i As Double
    Const sR As String = "A1"
    With Sheet1
        aChuaSum = .Range(sR).CurrentRegion.Value2
        eA = UBound(aChuaSum, 1)
        If eA < 2 Then Exit Sub
        ReDim aSumSum(1 To eA, 1 To 6)
        Dim dic As New Scripting.Dictionary
        For r = 2 To eA
            sMa = aChuaSum(r, 1)
            For c = 2 To 6
                If Not dic.Exists(sMa) Then
                    k = k + 1
                    dic.Add sMa, k
                    aSumSum(k, 1) = sMa & "Sub"
                    aSumSum(k, c) = aChuaSum(r, c)
                Else
                    i = dic(sMa)
                    aSumSum(i, c) = aSumSum(i, c) + aChuaSum(r, c)
                End If
            Next c
        Next r
    End With
    With Sheet2.Range(sR)
        .Resize(eA + k, 6).NumberFormat = "@"
        .Resize(eA, 6).Value = aChuaSum
        .Offset(eA).Resize(k, 6).Value = aSumSum
        .Resize(eA + k, 6).Sort Key1:=.Range(sR), Order1:=xlAscending, Header:=xlYes
    End With
    Application.ScreenUpdating = True
End Sub
Dạ em cám ơn nhiều ạ! E sẽ thử.
 
Upvote 0
Theo gợi ý tại bài 7 của Bác @VetMini , Bạn thử code sau:
Mã:
Option Explicit

Sub SumSumSum()
    Application.ScreenUpdating = False
    Dim sMa As String, aChuaSum() As Variant, aSumSum() As Variant
    Dim r As Long, k As Long, eA As Long, c As Long, i As Double
    Const sR As String = "A1"
    With Sheet1
        aChuaSum = .Range(sR).CurrentRegion.Value2
        eA = UBound(aChuaSum, 1)
        If eA < 2 Then Exit Sub
        ReDim aSumSum(1 To eA, 1 To 6)
        Dim dic As New Scripting.Dictionary
        For r = 2 To eA
            sMa = aChuaSum(r, 1)
            For c = 2 To 6
                If Not dic.Exists(sMa) Then
                    k = k + 1
                    dic.Add sMa, k
                    aSumSum(k, 1) = sMa & "Sub"
                    aSumSum(k, c) = aChuaSum(r, c)
                Else
                    i = dic(sMa)
                    aSumSum(i, c) = aSumSum(i, c) + aChuaSum(r, c)
                End If
            Next c
        Next r
    End With
    With Sheet2.Range(sR)
        .Resize(eA + k, 6).NumberFormat = "@"
        .Resize(eA, 6).Value = aChuaSum
        .Offset(eA).Resize(k, 6).Value = aSumSum
        .Resize(eA + k, 6).Sort Key1:=.Range(sR), Order1:=xlAscending, Header:=xlYes
    End With
    Application.ScreenUpdating = True
End Sub
Mình nghĩ là code này sẽ khó cho người mới. Khai báo Dic kiểu đó nghi nghi là sé báo lỗi
 
Upvote 0
Mình nghĩ là code này sẽ khó cho người mới. Khai báo Dic kiểu đó nghi nghi là sé báo lỗi
Dạ cảm ơn anh Quang Hải đã góp ý ạ, vậy sẽ :
Xóa 1 dòng:
Mã:
 Dim dic As New Scripting.Dictionary
Thay bằng 2 dòng:
Mã:
        Dim Dic As Object
        Set Dic = CreateObject("scripting.dictionary")
 
Upvote 0
Dạ em cám ơn nhiều ạ! E sẽ thử.

Bạn thử theo cách truyền thống mà Bác @VetMini chỉ dẫn tại bài 9 xem:

Mã:
Option Explicit

Sub SumTruyenThong()
    
    Application.ScreenUpdating = False
    Application.EnableEvents = False
    Application.Calculation = xlCalculationManual
    On Error GoTo End_
    Dim Dic As Object, sMa As String, aChuaSum() As Variant, aSumSum() As Variant
    Dim r As Long, k As Long, eA As Long, c As Long, i As Double, a As Long
    Const sR As String = "A1"
    With Sheet1
        aChuaSum = .Range(sR).CurrentRegion.Value2
        eA = UBound(aChuaSum, 1): a = UBound(aChuaSum, 2) + 1
        If eA < 2 Then Exit Sub
        ReDim Preserve aChuaSum(1 To eA, 1 To a + 1)
        ReDim aSumSum(1 To eA, 1 To a)
        
        Set Dic = CreateObject("scripting.dictionary")
        For r = 2 To eA
            sMa = aChuaSum(r, 1)
            aChuaSum(r, a) = sMa
            For c = 2 To a - 1
                If Not Dic.Exists(sMa) Then
                    k = k + 1
                    Dic.Add sMa, k
                    aSumSum(k, a) = sMa & "Sub"
                    aSumSum(k, c) = aChuaSum(r, c)
                Else
                    i = Dic(sMa)
                    aSumSum(i, c) = aSumSum(i, c) + aChuaSum(r, c)
                End If
            Next c
        Next r
    End With
    
    With Sheet2.Range(sR)
        .Offset(, a - 1).Resize(eA + k).NumberFormat = "@"
        .Resize(eA, a).Value = aChuaSum
        .Offset(eA).Resize(k, a).Value2 = aSumSum
        .Resize(eA + k, a).Sort Key1:=.Offset(, a - 1), Order1:=xlAscending, Header:=xlYes
        .Offset(, a - 1).Resize(eA + k).ClearContents
    End With
    
End_:

    Application.ScreenUpdating = True
    Application.EnableEvents = True
    Application.Calculation = xlCalculationAutomatic
    If Err.Number <> 0 Then MsgBox Err.Description, vbCritical, Err.Number
    
End Sub
 
Upvote 0
Chưa tính đến cách làm, mà nói chuyện cách trình bày.
Không sort thì cái subtotal nó sẽ đặt nằm dưới cái gì?
Không phải vậy anh.
Kết quả thì vẫn gom lại một cụm, dòng tổng để dưới cụm đúng như yêu cầu.
Không sort ở đây là: Không sử dụng cách sort để gom các mã lại một chỗ và để tính tổng của cụm
 
Upvote 0
Không phải vậy anh.
Kết quả thì vẫn gom lại một cụm, dòng tổng để dưới cụm đúng như yêu cầu.
Không sort ở đây là: Không sử dụng cách sort để gom các mã lại một chỗ và để tính tổng của cụm
OT loay hoay mãi một cột mà chưa được, nhờ Bạn @befaint góp ý ạ:
Mã:
Sub SumKhongSort()

    Dim Dic As Object, sMa As String, aChuaSum() As Variant, aSumSum() As Variant
    Dim r As Long, k As Long, eA As Long, c As Long, i As Double, a As Long
    Const sR As String = "A1"
   
    With Sheet1
        aChuaSum = .Range(sR).CurrentRegion.Value2
        eA = UBound(aChuaSum, 1): a = UBound(aChuaSum, 2) + 1
        If eA < 2 Then Exit Sub
        ReDim Preserve aChuaSum(1 To eA, 1 To a)
        ReDim aSumSum(1 To eA * 2, 1 To a)
        Set Dic = CreateObject("scripting.dictionary")
        For r = eA To 2 Step -1
            sMa = aChuaSum(r, 1)
            aSumSum(r, 1) = sMa
'            For c = 2 To a - 1
                'aSumSum(r, 2) = aChuaSum(r, 2)
                If Not Dic.Exists(sMa) Then
                    k = r + 1
                    Dic.Add sMa, k
                    aSumSum(k, 2) = aChuaSum(r, 2)
                Else
                    i = Dic(sMa)
                    aSumSum(i, 1) = Empty
                    aSumSum(i, 2) = aSumSum(i, 2) + aChuaSum(r, 2)
                End If
'            Next c
        Next r
        .Range(sR).Offset(, 8).Resize(eA + k, a) = aSumSum
    End With
End Sub
 
Upvote 0
Thật ra cái này PowerQuery là dễ nhất.

Bó-nợt SQL String cho bạn nào ngắm nghé ADO

Select a.Ma, a.Sum1, a.Sum2, a.Sum3, a.Sum4, a.Sum5 From "
(Select Ma, Sum1, Sum2, Sum3, Sum4, Sum5, Ma [Ma2] From Bang "
Union All "
(Select '', Sum(Sum1), Sum(Sum2), Sum(Sum3), Sum(Sum4), Sum(Sum5), Ma & 'SUB' From Bang "
Group By Ma) "
) a Order By a.Ma2 "
 
Upvote 0
Web KT
Back
Top Bottom