nguyenvanlinhab
Thành viên mới
- Tham gia
- 27/2/22
- Bài viết
- 40
- Được thích
- 0
Sub hoc_vba()
Dim lastRow As Long, r As Long, count As Long, k As Long, maSP As Object, VT As Object, kq(), dulieu()
With ThisWorkbook.Worksheets("Sheet1")
.Range("L2:N1000").ClearContents ' xoa ket qua cu
lastRow = .Cells(Rows.count, "F").End(xlUp).Row
If lastRow < 2 Then Exit Sub ' khong co du lieu thi don do choi
dulieu = .Range("D2:F" & lastRow).Value ' lay cot D:F vao mang dulieu
End With
ReDim kq(1 To UBound(dulieu, 1), 1 To 3)
Set maSP = CreateObject("Scripting.Dictionary")
Set VT = CreateObject("Scripting.Dictionary")
For r = 1 To UBound(dulieu, 1)
If maSP.exists(dulieu(r, 3)) Then ' da co ma SP
If VT.exists(dulieu(r, 1)) Then
k = VT.item(dulieu(r, 1)) ' doc chi so dong cua vi tri hien hanh, duoc ghi nho o (*) hoac (**)
kq(k, 3) = kq(k, 3) + 1 ' tang so luong o dong k them 1
Else
count = count + 1 ' vi tri moi nen tang count them 1
kq(count, 2) = dulieu(r, 1) ' vi tri
VT.Add dulieu(r, 1), count ' vi tri hien hanh se o dong count cua mang ket qua kq (**)
kq(count, 3) = kq(count, 3) + 1 ' tang so luong o dong count them 1
End If
Else ' la ma SP moi
VT.RemoveAll ' xoa cac vi tri cu
maSP.Add dulieu(r, 3), "" ' them ma SP moi vao tu dien voi tu cach KEY
count = count + 1 ' vi tri moi nen tang count them 1
kq(count, 1) = dulieu(r, 3) ' ma SP
kq(count, 2) = dulieu(r, 1) ' vi tri
VT.Add dulieu(r, 1), count ' vi tri hien hanh se o dong count cua mang ket qua kq (*)
kq(count, 3) = kq(count, 3) + 1 ' tang so luong o dong count them 1
End If
Next r
ThisWorkbook.Worksheets("Sheet1").Range("L2").Resize(count, 3).Value = kq
End Sub
Khi tối hỏi thử code rồi nhưng thấy bạn trước code dữ quá nên thôi mình lui, thôi thì góp vui:Đã chạy thử nó vẫn không như em nghĩ ạ.Cột mã sản phẩm nó vẫn không lọc trùngView attachment 272841
Sub test()
Dim dict As Object
Dim data As Variant, res As Variant
Dim dongcuoi As Long, i As Long, k As Long, r As Long
Dim str As String, itemCode As String, rackNo As String
Dim Quanity As Double
Dim this_sheet As Worksheet
Set this_sheet = ThisWorkbook.ActiveSheet
With this_sheet
dongcuoi = .Cells(.Rows.count, "F").End(xlUp).Row
If dongcuoi < 2 Then Exit Sub
data = .Range("D2:G" & dongcuoi).Value
Set dict = CreateObject("scripting.dictionary")
dict.CompareMode = vbTextCompare
ReDim res(1 To UBound(data, 1), 1 To 3)
For i = LBound(data, 1) To UBound(data, 1)
rackNo = data(i, 1)
itemCode = data(i, 3)
Quanity = 1 'data(i, 4)
str = rackNo & itemCode
If Not dict.Exists(str) Then
k = k + 1
dict.Add str, k
res(k, 2) = rackNo
res(k, 3) = Quanity
If Not dict.Exists(itemCode) Then
dict.Add itemCode, k
res(k, 1) = itemCode
End If
Else
r = dict.Item(str)
res(r, 2) = rackNo
res(r, 3) = res(r, 3) + Quanity
End If
Next i
.Range("L6").Resize(1000, 3).ClearContents
.Range("L6").Resize(k, UBound(res, 2)).Value = res
End With
End Sub
Cháu vừa đăng bài thì thấy bài của chú.Muốn người ta giúp thì hãy nói cho người ta hiểu. Tung tập tin lên rồi muốn người ta đoán ý? Thế nào, khi nào thì "không lấy", khi nào thì "cộng dồn" thì phải mô tả. Đã không biết nói, không biết giải thích thì sau đó phải lấy thêm ví dụ cụ thể, và giải thích tại sao chỗ này này "không lấy", chỗ này này "cộng dồn". Tóm lại nói về cách thức làm BẰNG TAY. Người khác chỉ đưa cách thức đó vào máy cho tay khỏi mỏi, công việc chạy nhanh hơn mà thôi. Ý mình thì cứ giữ lại mà bắt người khác phải đoán mò? Chủ đề "bên kia" chưa đủ dài hay sao mà lập thêm chủ đề này? Không mô tả kỹ mà chỉ kiểu: "Em muốn hỏi là key chính của em là mã sản phẩm,em muốn add thêm key nữa là vị trí .nếu key mã sản phẩm đã tồn tại thì không lấy. key vị trí đã tồn tại thì cộng dồn ra số lượng ạ" thì là bắt người khác phải nhọc công hơn không cần thiết rồi.
Nếu vấn đề không quá dễ thì bắt buộc phải có 3 phần: tập tin, mô tả, và điền bằng tay kết quả mong đợi.
Không thể chấp nhận kiểu tung tập tin hay ảnh lên rồi mất 10 bài để nói cho người khác hiểu ý.
Bạn mới học code nên tôi đề xuất code dưới đây. Nếu bạn muốn dùng chỉ 1 "đít thon dễ thương", code ngắn nhất, ít dòng ít chữ nhất thì đợi người khác nhé.
Mã:Sub hoc_vba() Dim lastRow As Long, r As Long, count As Long, k As Long, maSP As Object, VT As Object, kq(), dulieu() With ThisWorkbook.Worksheets("Sheet1") .Range("L2:N1000").ClearContents ' xoa ket qua cu lastRow = .Cells(Rows.count, "F").End(xlUp).Row If lastRow < 2 Then Exit Sub ' khong co du lieu thi don do choi dulieu = .Range("D2:F" & lastRow).Value ' lay cot D:F vao mang dulieu End With ReDim kq(1 To UBound(dulieu, 1), 1 To 3) Set maSP = CreateObject("Scripting.Dictionary") Set VT = CreateObject("Scripting.Dictionary") For r = 1 To UBound(dulieu, 1) If maSP.exists(dulieu(r, 3)) Then ' da co ma SP If VT.exists(dulieu(r, 1)) Then k = VT.item(dulieu(r, 1)) ' doc chi so dong cua vi tri hien hanh, duoc ghi nho o (*) hoac (**) kq(k, 3) = kq(k, 3) + 1 ' tang so luong o dong k them 1 Else count = count + 1 ' vi tri moi nen tang count them 1 kq(count, 2) = dulieu(r, 1) ' vi tri VT.Add dulieu(r, 1), count ' vi tri hien hanh se o dong count cua mang ket qua kq (**) kq(count, 3) = kq(count, 3) + 1 ' tang so luong o dong count them 1 End If Else ' la ma SP moi VT.RemoveAll ' xoa cac vi tri cu maSP.Add dulieu(r, 3), "" ' them ma SP moi vao tu dien voi tu cach KEY count = count + 1 ' vi tri moi nen tang count them 1 kq(count, 1) = dulieu(r, 3) ' ma SP kq(count, 2) = dulieu(r, 1) ' vi tri VT.Add dulieu(r, 1), count ' vi tri hien hanh se o dong count cua mang ket qua kq (*) kq(count, 3) = kq(count, 3) + 1 ' tang so luong o dong count them 1 End If Next r ThisWorkbook.Worksheets("Sheet1").Range("L2").Resize(count, 3).Value = kq End Sub
Bài của bạn tốt mà.Khi tối hỏi thử code rồi nhưng thấy bạn trước code dữ quá nên thôi mình lui, thôi thì góp vui:
Mã:Sub test() Dim dict As Object Dim data As Variant, res As Variant Dim dongcuoi As Long, i As Long, k As Long, r As Long Dim str As String, itemCode As String, rackNo As String Dim Quanity As Double Dim this_sheet As Worksheet Set this_sheet = ThisWorkbook.ActiveSheet With this_sheet dongcuoi = .Cells(.Rows.count, "F").End(xlUp).Row If dongcuoi < 2 Then Exit Sub data = .Range("D2:G" & dongcuoi).Value Set dict = CreateObject("scripting.dictionary") dict.CompareMode = vbTextCompare ReDim res(1 To UBound(data, 1), 1 To 3) For i = LBound(data, 1) To UBound(data, 1) rackNo = data(i, 1) itemCode = data(i, 3) Quanity = 1 'data(i, 4) str = rackNo & itemCode If Not dict.Exists(str) Then k = k + 1 dict.Add str, k res(k, 2) = rackNo res(k, 3) = Quanity If Not dict.Exists(itemCode) Then dict.Add itemCode, k res(k, 1) = itemCode End If Else r = dict.Item(str) res(r, 2) = rackNo res(r, 3) = res(r, 3) + Quanity End If Next i .Range("L6").Resize(1000, 3).ClearContents .Range("L6").Resize(k, UBound(res, 2)).Value = res End With End Sub
Bài đã được tự động gộp:
Cháu vừa đăng bài thì thấy bài của chú.
Cháu cảm ơn chú đã động viên, code của cháu còn tệ lắm.Bài của bạn tốt mà.
Cả hai bên kẹt nhau.Đã bảo không ăn xổi được mà.
Đúng kiểu như anh nói, chưa biết đi nhưng cứ đòi chạy.Cả hai bên kẹt nhau.
1. Bên thớt muốn học chạy trước khi học đi. Bài này là Đít-sần dạng nâng cao. Mới học thì cứ lo copy/páte code thôi. Chuyện hiểu thì nên quên đi.
2. Bên viết code thì viết theo kiểu truyền thống GPE, chỉ chú tâm về tốc độ chứ không coi trọng sự dễ hiểu (*1).
(*1) tôi dùng "dễ hiểu" trên nguyên tắc tương đối.
GPE lâu ngày đã tự lập cho mình một "tiêu chuẩn" viết code. Những người chưa quen với truyền thống này đương nhiên đọc code rất khó hiểu.
Điển hình:
Input/Output:
- copy dữ liệu đầu vào từ sheet vào mảng và duyệt mảng
- kết quả đầu ra đợc ghi vào mảng trung gian. Làm việc xong mới chép xuống sheet.
Cách dùng khối With:
- rất hay dùng With cho đối tượng sheet. Ngay cả khi có đối tượng khác hữu lý hơn với With, như Dictionary.
Dùng With cho sheet rất nguy hiểm. Chỉ cần quên dấu chấm trước Range là nó mặc định trở về sheet hiện hành. Đồng thời, code bên trong With khó đem ra copy/paste.
Dùng cho các đối tượng khác ít nguy hiểm hơn. Thường thì quên dấu chấm trước hàm hay thuộc tính sẽ bị VBA đẩy ra.
Cách đặt tên và sử dụng biến:
- lúc khai báo biến, không bao giờ chú thích nhiệm vụ của biến. Chỉ cần vài từ giải thích nhiệm vụ, người đọc code sẽ đỡ tốn rất nhiều công sức đoán mò.
- k được dùng để đếm số dòng của mảng đầu ra. Thường thì lập trình giành tên biến k cho chuyện khác.
- hầu hết các code chỉ sử dụng 1 hay 2 mảng. Trường hợp này thì đặt tên a hay sArr cũng như nhau.
Chú cho người cần giải thích code cho thớt:
Code như bài #22 không phải là đơn giản Đít sần: Key-Item, tức là dùng Key để tham chiếu Item.
Đây là kỹ thuật tham chiếu gián tiếp. Chi tiết Item thực ra được chứa trong một mảng. Khi tham chiếu thì Key sẽ cho biết chỉ số của chi tiết này trong mảng. Từ chỉ số, truy cập mảng sẽ ra chi tiết.
Code bài #22 cũng hơi lười biếng, dùng 1 đít sần cho hai loại tra khác nhau. Đáng lẽ nếu mỗi loại dùng một đít sần riêng thì code dễ hiểu, dễ chỉnh sửa hơn.
Sub ABC()
Dim sh As Worksheet, rng As Range, res(), sp$, vt$
Dim sRow&, i&, k&
Set sh = Sheets("sheet1")
Set rng = sh.Range("D2", sh.Range("D" & Rows.count).End(xlUp)) 'Vung du lieu
sRow = rng.Rows.count 'So dong vung du lieu
ReDim res(1 To sRow, 1 To 3) 'Mang ket qua
For i = 1 To sRow
If vt <> rng(i, 1) Or sp <> rng(i, 3) Then 'Khac Vi tri hoac san pham
k = k + 1 'Them 1 dong ket qua
vt = rng(i, 1) 'vi tri
res(k, 2) = vt 'Ghi nhan vi tri
res(k, 3) = 1 'Dem so luong
If sp <> rng(i, 3) Then 'Khac san pham
sp = rng(i, 3) 'Ma san pham
res(k, 1) = sp 'Ghi nhan Ma san pham
End If
Else
res(k, 3) = res(k, 3) + 1 'Dem so luong
End If
sh.Range("L2").Resize(1000, 3).ClearContents ' xoa ket qua
sh.Range("L2").Resize(k, 3).Value = res 'Gan ket qua
Next i
End Sub
Cũng bài #22: Tên biến vừa đặt tiếng Việt, vừa đặt tiếng Anh, tôi đọc thấy hụt hẫng. Tiếng Anh lại sai chính tả (quanity).lúc khai báo biến, không bao giờ chú thích nhiệm vụ của biến
Ở đâu đó tôi cũng từng nói việc này. 1 Dict dùng chung khi cần sẽ không đếm được, cũng không khai thác được hiệu quả. Nếu 2 Dict trở lên thì tên Dict cũng nên đặt cho có hình tượng. Tên mảng cũng thế. Nói chung tên biến đặt có hình tượng thì chưa cần chú thích cho tên biến.Code bài #22 cũng hơi lười biếng, dùng 1 đít sần cho hai loại tra khác nhau. Đáng lẽ nếu mỗi loại dùng một đít sần riêng thì code dễ hiểu, dễ chỉnh sửa hơn.
à tiếng anh là tiêu đề trong file của thớt cháu chỉ copy vào là cột nào thôi chứ tiếng anh cháu không rành, còn ban đầu thì cháu định khai báo tiếng việt để người học dễ hiểu.Cũng bài #22: Tên biến vừa đặt tiếng Việt, vừa đặt tiếng Anh, tôi đọc thấy hụt hẫng. Tiếng Anh lại sai chính tả (quanity).
Ở đâu đó tôi cũng từng nói việc này. 1 Dict dùng chung khi cần sẽ không đếm được, cũng không khai thác được hiệu quả. Nếu 2 Dict trở lên thì tên Dict cũng nên đặt cho có hình tượng. Tên mảng cũng thế. Nói chung tên biến đặt có hình tượng thì chưa cần chú thích cho tên biến.
Vầng,cháu cảm ơn chú đã góp ý.@tác giả bài #22:
Code trong bài này sử dụng key kép, tức là nhiều keys nhỏ gộp lại.
Theo kinh nghiệm thì cách gộp bằng phép concat (&) đơn giản khá nguy hiểm
Alpha: key1 = "abc"; key2 = "def"
Beta: key1 = "abcd"; key2 = "ef"
Sau khi gộp, hai keys Alpha và Beta ra in hệt nhau.
Người code kinh nghiệm luôn luôn có cách gộp chuỗi phân biệt. Cách thường dùng nhấy là chèn một ký tự đặc biệt giữa các giá trị. Ký tự này gọi là delimeter, trong lập trình thường gọi tắt là delim.
Chỉnh code:
Const DELIM = "|"
...
str = rackNo & DELIM & itemCode
...
Chú thêm:
Hôm nọ, có người bạn (không liên quan đến GPE) mách cho tôi một cách gộp chuỗi khá thú vị
str = Join(Array(rackNo, itemCode), DELIM)
Đương nhiên, gọi thêm hai hàm Join và Array thì hơi tốn năng lượng (chắc cỡ vài chục phần triệu giây). Nhưng bù lại, đọc code quen sẽ thấy nó khá dễ hiểu, ít bị sai sót. Ở ví dụ này chỉ có hai chuỗi cho nên chưa thấy gì. Khi phải gộp 5 chuỗi trở lên thì sẽ thấy cái lợi.
Code của bạn rất khá, thử viết lại code với dữ liệu không được xếp thứ tựVầng,cháu cảm ơn chú đã góp ý.
Cháu cảm ơn chú đã nhận xét và đưa thêm yêu cầu tổng quát hơn.Code của bạn rất khá, thử viết lại code với dữ liệu không được xếp thứ tự
Cách cho sao, huân chương của diễn đàn này nó sến như cải lương....
Ở đây có nhiều Sao lớn quá,cháu xin phép ngồi ngoài học hỏi thôi, không dám phát biểu gì nữa.
Đó cũng là cách hiệu quả, chỉ cần ra kết quả đúng và mình hiểu được cách làm là tốt rồiCháu cảm ơn chú đã nhận xét và đưa thêm yêu cầu tổng quát hơn.
Với dữ liệu và yêu cầu của bạn chủ thớt ban đầu có lẽ tất cả cũng đã đủ, nhưng với yêu cầu thêm như dữ liệu chưa được sắp xếp thì cháu sẽ làm như sau:
Khai báo thêm một mảng đặt là: dulieugoc
B1: Gán dữ liệu chưa sắp xếp vào mảng dulieugoc
B2: Sắp xếp dữ liệu trên bảng tính (điều kiện sort itemCode)
B3: Vẫn là code cũ (gán dữ liệu đã sắp xếp vào mảng data rồi làm như bài 22)
B4: gán dữ liệu trong mảng dulieugoc chèn lên bảng dữ liệu đã được sắp xếp trở lại vị trí gốc.
Tuy bác Hiếu nói là mới học VBA, nhưng em cực kỳ nể phục tư duy của bác. Code chỉ là công cụ để viết ra tư duy của mình. Trong số các bác, các thầy trên GPE này, thì bác là một trong số những người em cực kỳ ngưỡng mộ!Đó cũng là cách hiệu quả, chỉ cần ra kết quả đúng và mình hiểu được cách làm là tốt rồi
Tuy nhiên đã xếp thứ tự thì không nên dùng dic mà sử dụng thuần mảng như bạn @VetMini từng hướng dẫn, ý mình là không cần bước xếp thứ tự và dùng dic để xử lý, tuy khó 1 chút nhưng kỷ năng dùng dic sẽ thuần thục hơn
Trước đây mình hầu như không biết các lệnh VBA, chỉ dùng bộ thu macro rồi chỉnh sửa tí xíu chạy theo ý mình, từ từ bắt chước code của các bạn trên diễn đàn, được mọi người gợi ý các tình huống xử lý dần dần mới quen các lệnh. Từ lúc có các loạt bài viết https://www.giaiphapexcel.com/diendan/threads/index-các-bài-viết-về-vba.129388/ của bạn @befaint mình mới biết được phần nào các lệnh VBA
Cha nội này có tư duy toán ứng dụng khá cao (*1)Tuy bác Hiếu nói là mới học VBA, nhưng em cực kỳ nể phục tư duy của bác. Code chỉ là công cụ để viết ra tư duy của mình. Trong số các bác, các thầy trên GPE này, thì bác là một trong số những người em cực kỳ ngưỡng mộ!
Gởi bạn code dùng 2 dic cho trường hợp không xếp thứ tự bài #32Cháu cảm ơn chú đã nhận xét và đưa thêm yêu cầu tổng quát hơn.
Với dữ liệu và yêu cầu của bạn chủ thớt ban đầu có lẽ tất cả cũng đã đủ, nhưng với yêu cầu thêm như dữ liệu chưa được sắp xếp thì cháu sẽ làm như sau:
Khai báo thêm một mảng đặt là: dulieugoc
B1: Gán dữ liệu chưa sắp xếp vào mảng dulieugoc
B2: Sắp xếp dữ liệu trên bảng tính (điều kiện sort itemCode)
B3: Vẫn là code cũ (gán dữ liệu đã sắp xếp vào mảng data rồi làm như bài 22)
B4: gán dữ liệu trong mảng dulieugoc chèn lên bảng dữ liệu đã được sắp xếp trở lại vị trí gốc.
Nếu viết thì cháu sẽ viết như vậy,nói gì thì nói dữ liệu phải sắp xếp trước thì cháu mới làm được còn không cháu chịu thua.
Ở đây có nhiều Sao lớn quá,cháu xin phép ngồi ngoài học hỏi thôi, không dám phát biểu gì nữa.
Sub XYZ()
Dim sh As Worksheet, dic As Object, dicSP As Object, sarr(), S, res()
Dim sRow&, i&, k&, sp, sp_vt$
Set dicSP = CreateObject("scripting.dictionary")
Set dic = CreateObject("scripting.dictionary")
Set sh = Sheets("Sheet1")
sarr = sh.Range("D2", sh.Range("F" & Rows.count).End(xlUp)).Value
sRow = UBound(sarr)
ReDim res(1 To sRow, 1 To 3)
For i = 1 To sRow
sp = sarr(i, 3)
sp_vt = sp & "|" & sarr(i, 1)
If dic.exists(sp_vt) = False Then dicSP.Item(sp) = dicSP.Item(sp) & "^" & sp_vt
dic.Item(sp_vt) = dic.Item(sp_vt) + 1
Next i
For Each sp In dicSP.keys
res(k + 1, 1) = sp
S = Split(dicSP.Item(sp), "^")
For i = 1 To UBound(S)
k = k + 1
res(k, 2) = Split(S(i), "|")(1)
res(k, 3) = dic.Item(S(i))
Next i
Next sp
sh.Range("L6").Resize(1000, 3).ClearContents
sh.Range("L6").Resize(k, 3).Value = res
End Sub
Mã:Sub XYZ() ... S = Split(dicSP.Item(sp), "^") For i = 1 To UBound(S) k = k + 1 res(k, 2) = Split(S(i), "|")(1) res(k, 3) = dic.Item(S(i)) Next i ...