[THI] Tạo sổ TH NXT với tốc độ nhanh nhất, dữ liệu 65,532 dòng
Cuộc thi tạo sổ tổ hợp nhập xuất tồn trong Excel tốc độ nhanh nhất
MỤC ĐÍCH
Trao đổi học tập để cùng nâng cao trình độ lập trình VBA về tối ưu code chạy nhanh và rõ ràng.ĐỐI TƯỢNG THAM GIA
Là tất cả các thành viên GPE từ thành viên thường đến các Admin của GPE
Tôi cũng tham gia. Thực tế tôi đã viết code lâu rồi để phục vụ công việc quản lý kho, bản thân thấy chạy khá nhanh nhưng vẫn tin nó chưa phải hoàn hảo.
Nếu code của ai tối ưu nhất hoặc rõ ràng nhất sẽ trình bày code và giải thích cặn kẽ kỹ thuật để làm được ra nó trong topic này để mọi người tham khảo và học hỏi.GIẢI THƯỞNG
Giải thưởng là cho tất cả thành viên của diễn đàn GPE được các bài học tốt về lập trình VBA trong Excel trong việc làm sổ sách tổng hợp.THỜI GIAN DỰ THI, GỬI BÀI VÀ CÔNG BỐ
Dự thi từ ngày 10/02/2014.
Bài gửi chậm nhất là 12hAM ngày 15/02/2014.
Thời gian công bố kết quả đánh giá 14h 17/02/2014
Tất cả các bài dự thi, kết quả đánh giá sẽ được upload lên trang đầu của topic này.Các bạn nén file đáp án rồi gửi bài vào email:
duytuan@bluesofts.net hoặc email của một thành viên BQT GPE (tôi bổ sung sau)
(Tôi sẽ là người nộp sớm nhất không sợ copy của người khác 🙂 )ĐỀ BÀI:
Tôi cung cấp tập tin dữ liệu với 65,532 dòng cùng module chứa các hàm và thủ tục đo tốc tộ, cấu trúc lệnh.
Bảng dữ liệu:
2551Nếu các bạn thắc mắc về phương pháp lập sổ tôi sẽ giải thích bài sau
Cấu trúc code:
Sub DoThoiGian()
Dim T1@, T2@, Freq@, Overhead@
QueryPerformanceFrequency Freq
QueryPerformanceCounter T1
QueryPerformanceCounter T2
Overhead = T2 – T1
QueryPerformanceCounter T1'Thủ tuc của bạn
LapSo 'Thủ tuc của bạn phải làm
'Kết thúc chạy, đo thời gian thực hiện
QueryPerformanceCounter T2
'Debug.Print (T2 – T1 – Overhead) / Freq * 1000; "milliseconds(ms)"
MsgBox "milliseconds(ms): " & (T2 – T1 – Overhead) / Freq * 1000
End SubDoThoiGian là thủ tục mẹ được gán vào nút lệnh "Thực hiện" trên bảng tính. Nội dung trong thủ tục này bạn không được sửa. Bạn cần phải tạo thủ tục LapSo để lập sổ tổng hợp NXT.
Sub LapSo()
'Code của bạn để tạo ra sổ
End SubKết quả thực hiện phải ra được sổ có cấu trúc và dữ liệu như sau
2550
Lưu ý, sổ mẫu đã được định dạng vì vậy bạn không cần viết code để định dạng để giảm các yếu tốt ảnh hưởng tới tốc độ của code.(Nếu bạn không biết lập trình VBA có thể lập công thức Excel thông thường. Tuy nhiên nó có thể được dùng để so sánh giữa lập trình VBA "thiện chiến" thế nào với cách lập công thức Excel thông thường mà thôi).
Hướng dẫn tính toán
Các thành viên lưu ý. Sheet "Setting" có thông tin về ngày lập sổ: Từ ngày…đến ngày với các name NGAY1, NGAY2. Điều kiện để lập sổ phải dựa vào thời gian và Loại_phieuLượng Tồn đầu = lượng nhập với ngày < NGAY1 – lượng xuất với ngày < NGAY1
Lượng Nhập trong kỳ = lượng nhập với ngày >= NGAY1 và ngày <= NGAY2
Lượng Xuất trong kỳ = lượng xuất với ngày >= NGAY1 và ngày <= NGAY2
Lượng tồn cuối = Lượng Tồn đầu + Lượng Nhập trong kỳ – Lượng Xuất trong kỳTương tự khi tính giá trị…
TIÊU CHÍ ĐÁNH GIÁ
Tìm ra các code đạt tốc độ nhanh nhất. Các bài làm cố gắng trình bày dễ hiểu và kèm comment trong code để giải thích.
Tất cả các bài với các phương pháp khác nhau cũng sẽ đăng lên để chúng ta học được nhiều phương pháp từ đó có thể vận dụng linh hoạt trong các việc khác.Xin nói trước với các bạn là ta có thể đánh giá ở mức tương đối. Tất cả các code sẽ chạy trên một máy tính. Excel sẽ được khởi động lại với mỗi code mới, mỗi code được chạy 3 lần rồi lấy tốc độ trung bình. Tất cả các bài dự thi được upload lên đây để tất cả mọi người tham khảo.
Với tinh thần cầu thị, tạo sân chơi chung cho mọi người tôi rất mong chúng ta cùng tham gia. Mong các thành viên đừng e ngại về trình độ của mình thế này thế khác, cứ xác định tham gia để học để biết mình đã làm được gì và cần cải tiến cái gì về lập trình VBA.
—————–
Đã có bài tổng hợp kết quả test và các file có mã nguồn của các tác giả gửi. Các thành viên xem bài [URL='https://www.giaiphapexcel.com/forum/showthread.php?89723-THI-T%E1%BA%A1o-s%E1%BB%95-TH-NXT-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng/page18']#175 để download.
—————–
Em có thắc mắc chút xíu về dữ liệu:
Cột SO_CT 2 kí tự đầu là thể hiện việc nhập kho (NK) và xuất kho (XK), nhưng hình sau sẽ không phải như vậy???
2552Nếu không dựa vào điều kiện 2 kí tự đầu ở cột SO_CT thì dựa vào cột LOAI_PHIEU + thêm 1 điều kiện nữa thì kết quả như sau:
2553
Xin lỗi các anh chị vì số chứng từ và ký hiệu chứng từ không được khớp. Do là mình phải copy pase để tạo nhiều dữ liệu nên số lượng các mặt hàng nhập xuất nó không được đẹp nên đã tự ý đổi "LOẠI_PHIEU" mà không đổi số phiếu cho logic. Vậy các anh chị và các thành viên chỉ dựa vào cột "LOẠI_PHIEU" để xác định phiếu nhập hay phiếu xuất, còn SO_CT bỏ qua.
Mình cho hiện kết quả tại là:
1039.252835Bác HYen17 làm nhanh thật, tốc độ cũng nhanh đó.
Trân trọng.
Nhờ ăn gian đó: 973.707795
Sub LapSo()
Dim Rws As Long, J As Byte, W As Byte
Dim SNh As Double, TNh As Double
Dim Rng As Range, Sh As Worksheet, WF As Object
ReDim sArr(1 To 12, 1 To 1): ReDim dArr(1 To 12, 1 To 6)
5 Set Sh = ThisWorkbook.Worksheets("KHO")
Rws = Sh..CurrentRegion.Rows.Count
7 Set Rng = Sh..Resize(, 10)
sArr() = .Resize(12).Value
9 Set WF = Application.WorksheetFunction
For J = 1 To 12
11 Sh..Value = sArr(J, 1)
For W = 1 To 2
13 Sh..Value = Choose(W, "N", "X")
If W = 1 Then
15 SNh = WF.DSum(Rng, Sh., Sh.Range("AA1:AD2"))
TNh = WF.DSum(Rng, Sh., Sh.Range("AA1:AD2"))
17 dArr(J, 3) = WF.DSum(Rng, Sh., Sh.Range("Ac1:Af2"))
dArr(J, 4) = WF.DSum(Rng, Sh., Sh.Range("Ac1:Af2"))
19 Else
dArr(J, 1) = SNh – WF.DSum(Rng, Sh., Sh.Range("AA1:AD2"))
21 dArr(J, 2) = TNh – WF.DSum(Rng, Sh., Sh.Range("AA1:AD2"))
dArr(J, 5) = WF.DSum(Rng, Sh., Sh.Range("Ac1:Af2"))
23 dArr(J, 6) = WF.DSum(Rng, Sh., Sh.Range("Ac1:Af2"))
End If
25 Next W
Next J
27 .Resize(12, 6).Value = dArr()
End Sub
Ủa, code của bác chạy nhanh chư chớp, tốc độ của nó là "296.xxxx". Quá nhanh so với quy định :).
Bác kiểm tra giúp sao em copy vào file dữ liệu để chạy thì toàn ra số 0?
Có tạo thêm vùng dữ liệu phụ ở ngoài đó anh Tuân ơi.
Làm sao Tuân có thiết trí vùng của trang thẻ kho, nó nè
| aa | ab | ac | ad | ae | af 1 |ngay_ct|ngay_ct|ma_vlsphh|loai_phieu|ngay_ct|ngay_ct
2 |=">"&aa4|="<"&ab4|="=" &ac4||=">="&ab4|="<="&ab5
3 ||
4 |=min(kho!b:b)-1|=ngay1|;;;;;;;;;;;|||
5 ||=ngay2||||Các thành viên lưu ý. Sheet "Setting" có thông tin về ngày lập sổ: Từ ngày…đến ngày. với các name NGAY1, NGAY2. Điều kiện để lập sổ phải dựa vào thời gian và Loại_phieu
Lượng Tồn đầu = lượng nhập với ngày < NGAY1 – lượng xuất với ngày < NGAY1
Lượng Nhập trong kỳ = lượng nhập với ngày >= NGAY1 và ngày <= NGAY2
Lượng Xuất trong kỳ = lượng xuất với ngày >= NGAY1 và ngày <= NGAY2
Lượng tồn cuối = Lượng Tồn đầu + Lượng Nhập trong kỳ – Lượng Xuất trong kỳTương tự khi tính giá trị…
Mong các thành viên có kinh nghiệm tham gia nhiều hơn để các thành viên khác được học hỏi.
Dùng ADO chỉ có thể ra kết quả có thời gian như trên thôi, em xem như thua cuộc, đang đợi học hỏi đây anh ơi.
ADO cũng được, nhưng so sánh về thời gian mình chưa biết ai hơn ai trong topic này. Anh cứ tìm cách làm đi, chỉnh sửa code cho dễ hiểu rồi gửi. Quan trọng các thành viên sẽ có tập hợp các phương pháp hay để học hỏi.
Mình xin đính chính lại. Khi tổng kết các bài thi sẽ đánh giá bài nào đạt tốc độ cao nhất. Tất cả các bài với các phương pháp khác nhau cũng sẽ đăng lên để chúng ta học được nhiều phương pháp từ đó có thể vận dụng linh hoạt trong các việc khác.
Ở sheet KHO, có dòng "Dữ liệu nguồn – Không sửa", vậy cho hỏi có được sắp xếp (sort) được không vậy?
Nếu sắp xếp thì phải dùng lệnh VBA để làm, lệnh này bị tính vào thời gian chạy :). "Dữ liệu nguồn – Không sửa" ý là không sửa thủ công, nếu sửa thì phải bằng code mà chỉ liên quan đến Sort mà thôi.
Hướng dẫn nghiệp vụ lập sổ THNXT – Tổng hợp nhập xuất tồnHướng dẫn nghiệp vụ lập sổ THNXT – Tổng hợp nhập xuất tồn
2554Các cột trong sheet KHO (dữ liệu nguồn)
SO_CT: số phiếu nhập, phiếu xuất. Không cần quan tâm
NGAY_CT: ngày của các phiếu khi nhập, xuất kho. Cột này được dùng để biết các phiếu thực hiện thời gian nào.
MA_KH: Không cần quan tâm
MA_NB: Không cần quan tâm
MA_NV: Không cần quan tâm
KHO: Không cần quan tâm
MA_VLSPHH: Chỉ ra mã vật tư, hàng hóa nào nhập hoặc xuất
SLG: Số lượng hàng nhập, xuất. Được dùng để tổng hợp số lượng trong báo cáo.
DON_GIA: Là đơn giá nhập, xuất cho một đơn vị hàng hóa. Không cần quan tâm
LOAI_PHIEU: Chỉ ra dòng/bản ghi này thuộc loại phiếu nhập(N) hay là xuất(X)
THANH_TIEN: Tính bằng SLG*DON_GIA. Cột này được dùng để tính tổng số tiền nhập hoặc xuất.Cách tính toán để đưa vào sổ th nxt
Sheet "Setting" có thông tin về ngày lập sổ: Từ ngày…đến ngày. với các name NGAY1, NGAY2. Điều kiện để lập sổ phải dựa vào thời gian và Loại_phieuLượng Tồn đầu = lượng nhập với ngày < NGAY1 – lượng xuất với ngày < NGAY1
Lượng Nhập trong kỳ = lượng nhập với ngày >= NGAY1 và ngày <= NGAY2
Lượng Xuất trong kỳ = lượng xuất với ngày >= NGAY1 và ngày <= NGAY2
Lượng tồn cuối = Lượng Tồn đầu + Lượng Nhập trong kỳ – Lượng Xuất trong kỳTương tự khi tính giá trị…
Ví dụ: Nhìn vào hình trên ta tính cho HH001 với khoảng thời gian từ NGAY1(02/08/2005) đến NGAY2(31/08/2005)
+ Lượng Tồn đầu. Tra trên dòng những ngày < 02/08/2005 với mặt hàng HH001 ta có
Nhập=4
Xuất=3
Lượng Tồn đầu = 4-3 = 1
+ Lượng Nhập, Xuất trong kỳ. Tra trên dòng có ngày trong khoảng [02/08/2005-31/08/2005] với mặt hàng HH001 ta có
Nhập=2+4=6
Xuất=3+ Lượng tồn cuối = 1 + 6 – 3 = 4
Hy vọng bài hướng dẫn này chúng ta đều nắm được yêu cầu tính toán để bắt tay vào code.
Nghĩa thử viết câu lệnh sắp xếp (SX) CSDL ấy lại xem tốn bao nhiêu là thời gian?
Riêng chuyện SX này cầm thua ADO là cái chắc luôn!
Riêng mình thì suýt sấp bẫy 2 mã hàng 'BE' & 'BE1'
Còn chuyện này hỏi luôn thầy Tuan nè:
Ở cột sao có mặt hàng nhập do được cho hay fải xuất đem hối lộ hay sao mà không có thu/chi tiền làm vậy?
Dạ, đó là đồ được biếu đấy bác à /-*+/
Những công cụ đặc thù như ADO (sắp xếp và tổng hợp qua cổ máy Access?) chúng có "vùng mượt" (sweet spot) của chúng. Tuỳ theo mật độ dữ liệu mà tốc độ có tối ưu hay không. Chỉ dùng một nhóm dữ liệu thì không thể kết luận gì khi so sánh được.
Các kỹ thuật lập trình cũng có nhiều môn như vậy. Ví dụ SX (sort) có đến vài phương pháp, tuỳ theo số lượng và tình trạng dữ liệu mà hiệu quả của chúng khác nhau.
Đã 70 lượt download dữ liệu rồi mà chưa thấy thêm người trao đổi gì? Các Admin và một số Smod hình như cũng không quan tâm?
Theo đánh giá của tôi thì với ví dụ về lập sổ này chắc chắn chúng ta sẽ học được nhiều về VBA. Cùng kết quả nhưng sẽ có nhiều hướng giải quyết. Với diễn đàn GPE sẽ có thêm loạt bài chất lượng và sống động thực sự!
Có thể đang tìm phương án tối ưu đó anh Tuân, theo em thời hạn chỉ có 5 ngày kể từ ngày 10/02/2014 thì nó quá ngắn phải không anh?
Cũng có thể với 5 ngày là hơn ngắn. Hy vọng các thành viên tập trung nghiên cứu để làm được ví dụ này.
Lu bu chỗ mã hàng không cố định, chỉ ghi ra mã hàng nào có phát sinh chứ không lấy tất theo DM VLSPHH.2555
Em đã nhận được hàng bác Ba Tê gửi rồi nhé. Hàng của bác cũng ngon đấy :). Chỉ có điều bác thay "LapSo" thành "ToTiTe" làm em tìm hoa cả mắt –=0 .
Cảm ơn bác.
Xin lỗi Tuân nhé, vì mình đọc đề bài rồi viết Sub, xong gán vào nút lệnh của Tuân mà không đọc thấy dòng này:
'Khong sua noi dung trong thu tuc "DoThoiGian". Thu tuc nay duoc gan vao nut lenh "Thuc hien" trong sheet "THNXT"
Bài dự thi viết code ngắn nhất
/-(ầu như chỉ với 1 dòng lệnh!
,,,,,,,,,,,,,,,,,,,,,
Đúng là cuộc thi lạ, và giải thưởng cũng lạ, Nhưng như thế mới là GPE bạn ah, chắc là lính mới cóc hiểu gì (?)
———-
Topic của cuộc thi, bài dạng này quá giản đơn – bác bate, concogia (và quanghai, chanhTQ) viết suốt giúp thành viên hàng ngày – sao lại lấy ra thi??
Tuy thế thời gian so sánh cũng là vấn đề vì chênh nhau chắc không nhiều, và tốc độ máy khác nhau
Để tiện so sánh chúng ta hãy chạy cùng một sub "test thời gian" (theo cách đo như code của NDT) trong file kèm sau, hãy bấm nút test thời gian, chạy 10 lần, lấy trung bình tại Q5 –> và báo con số lên đây (mở màn là bác bate nhé) –> khi đó mới có con số so sánh –> suy luận tốc độ so với ng khác -> làm căn cứ khi báo con số này cùng con số thời gian của sub lapso (lưu ý: chạy cùng ngay thời điểm đó nhé, vì windows xử lý đa luồng, nên tùy thuộc vào thời điểm bạn đang chạy thì nó chạy cung nhiều chương trình khác bên cạnh excel nữa)
www.giaiphapexcel.com/diendan/threads/thi-t%E1%BA%A1o-s%E1%BB%95-th-nxt-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng.89723/page-4#posts
Khoá học Trưởng phòng nhân sự
Nguồn nhân lực là một trong Tứ trụ kinh doanh của doanh nghiệp, có tác động tới sự tồn tại và phát triển bền...
Xem khóa học
Và code của bác thì chạy con số bao nhiêu??? BẤM "Test thời gian" mới đúng bác nhé –> Kết quả ở ô Q5 (còn O5 là đê chạy code lapso sau khi lắp code vào – dĩ nhiên người lập trình phải ghép vào các sheet data)
ví dụ code của bác chạy 350 và test thời gian (Q5) là 1212.16
code của 1 anh A khác chạy 330 và test thời gian 1000
–> quy đổi nếu code của bác bate chạy trên máy tính của anh A là =350*1000/1212.16 = 288.74 mls – con số này nhỏ hơn 330mls (sub lapso của anh A) ==> code của bác bate thắng , chúc mừng chúc mừng
Ví dụ thế
ý là , lấy dạng bài này đó
Nếu đó là ý muốn của chủ topic thì không có gì để bàn cả, chỉ thấy đó là vấn đề đã giải nhiều trên GPE, và so sánh tốc độ ở bài dạng này, dường như không khác biệt nhau nhiều lắm, vài phần % giây nên Không rõ đánh giá thế nào, như thế khó tạo ra được cách biệt, ý tôi chỉ thế thôi,
Và muốn nói thế, để chỉ ra các thành viên khác đừng lo việc khi ai đó nói là đã chạy 400mls hay ít hơn , tiếp tục thử theo cách của riêng đi- vì nó phụ thuộc vào máy tính và trạng thái máy tính chạy ghi đó (RAM, CPU , các chương trình đang chạy vv) – – con nếu máy tính khác nhau thì đã khác nhau tốc độ là cái chắc – nên có khi chạy 400mls ở máy này thì sang máy khác lại là 600mls .
Có lẽ bạn chủ topic nên công bố thời gian chạy thử nghiệm các bài nộp đã gửi đến — như thế thì mới dễ so sánh hơn (vì thử trên cùng 1 môi trường gần như nhau). và người có bài nộp cũng biết cần hoàn thêm để cải thiện tốc độ nữa hay không (?)
Đôi lời bàn vậy thôi.
Topic này tôi thấy ngay khi mới tạo, nhưng chưa kịp xem, thậm chí còn không thấy chữ , nên cứ tưởng Tuân giới thiệu 1 thành quả nào đó của mình.
Mãi đến khi đọc fb mới thấy Nghĩa khích bác tận tên tôi, vào đây lại thấy khích nữa:
Tính tôi vẫn thế, sai thì tôi phê, không chính xác thì tôi nói, copy paste mà nhận là tự dịch thì tôi vạch ra. Ăn nói hồ đồ thì tôi mắng. Vậy đấy.
Còn bài trong topic này tôi viết theo kiểu truyền thống, máy Core 2 Dual 2.9, kết quả tốc độ 537.77551409. Viết xong thì khuya rồi nên chưa sửa, chưa comment.
Khi làm một chương trình thì người thực hiện phải hoạch định xem mình sẽ làm gì để chương trình chạy mượt mà hơn.
Thứ hai là phải cân nhắc giữa cái nhanh và việc lường trước các lỗi phát sinh.
Với bài tập này, thay vì tôi chọn code chạy 450 ms thì tôi sẽ chọn loại 550 ms.
Lý do:
1) Tôi phải kiểm tra dữ liệu nguồn, cụ thể là sheet KHO có trạng thái AutoFilter hay không, nếu có thì tôi sẽ bỏ chế độ này. Một cơ sở dữ liệu mà đang bị Filter thì có khả năng chúng ta không thể gán vào Array đầy đủ.
2) Kiểm tra xem dữ liệu trong sheet KHO đã được nhập dòng nào chưa, nếu chưa nhập thì thông báo.
3) Kiểm tra xem Từ ngày, Đến ngày đã được nhập vào hay chưa, nếu chưa nhập hoặc không phải là dạng ngày cũng phải thông báo.
4) Xóa dữ liệu cũ trên biểu mẫu, bởi khi dữ liệu sắp nhập vào ít hơn dữ liệu cũ trên biểu mãu sẽ bị trộn dữ liệu.
5) Trong biểu mẫu, tôi luôn bảo toàn một số hàng nhất định, trong trường hợp này, tôi bảo toàn số hàng là 15 dòng. Vì vậy, tôi phải kiểm tra trước xem biểu mẫu đó có đủ 15 dòng chưa, nếu đủ thì thôi, không đủ thì Insert thêm, còn nếu hơn thì Delete đi, làm sao cho Insert hoặc Delete phải bảo toàn 15 dòng. Mặt khác ta phải xem số hàng mà ta sắp gán vào biểu mẫu có nhiều hơn 15 dòng hay không, nếu nhiều hơn thì ta Insert thêm (vẫn đảm bảo cấu trúc định dạng), không để tình trạng dữ liệu tràn.
————————————————
Tôi vẫn dùng Dictionary để thực hiện code vì nó đảm bảo mã vật tư không trùng.
Sao máy của bạn nhanh thế? Máy của tôi là quãng 4200
————
Theo tôi để đo tốc độ chỉ cần GetTickCount là đủ. Vì sao?
Một code dài thì bản thân code đó, chạy trên cùng một máy, ở thời điểm gần như nhau với trạng thái máy như nhau (các phần mềm đang chạy) đã cho các kết quả khác nhau rồi. Nếu code chạy mất vd. 800 ms và ta giả thiết là kết quả giữa các lần thử khác nhau là 25 phần nghìn thì có nghĩa là kết quả thực là 800 +- 20 ms.
Như thế sai số có thể là 20 ms. Vậy thì chả lý gì lại phải dùng QueryPerformanceFrequency + QueryPerformanceCounter. Với sai số cỡ đó thì dùng GetTickCount là đủ.
QueryPerformanceFrequency + QueryPerformanceCounter ta chỉ dùng khi mà phải đo tốc độ với độ chính xác cực lớn. Tức vd. đo tốc độ của code mà thời gian chạy nó là rất rất nhỏ. Nói cách khác dùng để đo khoảng thời gian cực ngắn.
————
Ngay cả khi so sánh 2 code mà thời gian cho code 1 là 500 còn cho code 2 là 510 thì code nào nhanh hơn? Chạy cùng trên một máy, cùng "môi trường" nhưng chưa chắc đó là 2 môi trường như nhau. Vậy khi mà 2 kết quả khác nhau một lượng nằm trong giới hạn sai số do "dao động" trong system thì khó mà có thể phán code nào chạy nhanh hơn. Tất nhiên khi lượng khác nhau đó là lớn (tỉ lệ "lượng khác nhau" / thời gian gần đúng) thì có thể "yên tâm" đánh giá.
—
Vả lại nếu cần so sánh 2 gói đường mà chúng khác nhau ít nhất là cỡ 1 gam thì chả cần dùng cân có độ chính xác là 1 phần trăm (phần nghìn) gam
Đã ăn gian thì tới bến luôn chứ sao lại nửa vời thế
Trong đó he là hàm làm "mọi việc" và trả về mảng kết quả.
—
96 đã là gì. Tôi chạy code trên vài lần, trong đó có lần cho kết quả 0.
Cái bảng dữ liệu ở sheet kho như thế hình như vẫn sao sao ấy, không biết các kho vật tư như thế nào, riêng kho cảng thì nhập lô hàng nào, khi xuất thì thanh lý, cho nên làm tới đâu tính tới đó.
Trường hợp nhập CSDL thế này thì 2-3 năm kiểm tra lại, lấy số tồn của thời gian trước ngày TỪ NGÀY thì phải quét hết những năm trước luôn, khi dữ liệu xuất ra thì chắc chắn sẽ có mã hàng có TỒN CUỐI là 0. Với CSDL như vậy sẽ không được hay lắm thì phải.
Nếu lọc kiểu này thì phải thêm 1 vòng lặp nữa xét nếu mã nào có tồn cuối là 0 thì loại ra.
Đôi lời góp ý.
Không phải tồn cuối bằng 0 thì loại ra, mà là đầu kỳ bằng 0, cả nhập cả xuất đều bằng 0 thì mới loại ra.
Nếu đầu kỳ = 0, nhập 100, xuất 100, tồn cuối = 0 mà loại ra thì sai.
Sửa lại dùm em cái này anh Anh Tuân, copy mà không sửa lại:
Sửa lại chỗ màu đỏ anh nhé! Thanks.
Lâu lâu mình làm nhà phê bình cái coi!
1) 2 biến đều nhận giá trị là DATE, nhưng có 1 biến LONG, 1 biến DATE
2) Dict chạy toàn bộ mảng nguồn, đồng thời add toàn bộ như thế có bị xem là hao phí hay không, trong khi người ta chỉ chặn ĐẾN NGÀY, giả sử người ta chỉ lấy từ 1/1/2005 đến 31/12/2005 thì nó vẫn Add luôn cả những năm sau à?
3) Sau khi xuất ra kết quả, không có dòng Total thì vẫn chưa đạt yêu cầu!
Người ta thường nói:
Cười người hôm trước, hôm sau người cười
Vậy tại sao mình "không cười người hôm trước, nếu không cười thì hôm sau còn đâu cơ hội mà cười" kakaka.
Câu 1: Cố tình? Tại sao?
Câu 2: ý nói là thay vì duyệt cột đó có điều kiện, đằng này lại add hết mã hàng kể cả mã hàng không cần thiết vào Dict. Nếu Sư phụ đã từng xem danh mục phụ tùng xe gắn máy của một đại lý thì Sư phụ sẽ ngất đi, 36000 dòng! Khiếp!
Câu 3: Vậy thì bổ sung thêm đi chứ nhỉ?
———————————————-
Lâu lâu mới bắt giò được Sư phụ của mình cũng khá vui, chứ mình bị Sư phụ trảm hoài chán chết đi được!
OK, mỗi người có một lý do, một thuật toán để làm, vậy câu 1 tại sao cố tình đặt biến này, biến kia vậy? Hỏi để học kiểu biến này. Có gì đặc biệt nên cố tình làm thế sao Sư phụ? Nhầm lẫn thì bình thường, còn cố tình thì rất không hiểu tại sao!
Ý của anh Quang Hải là biến gì vậy ạ? Còn biến LONG thay bằng biến DATE cũng vậy thôi à, vì tất cả cũng chỉ là dạng NUMBER, nhưng khai như thế nó thấy kỳ và chổi chổi thế thôi.
Mình có đặc điểm là viết code cực nhanh và cực ẩu luôn, khai báo biến thì tá lả và chẳng bao giờ có chú thích trong code vì chẳng có biết chú thích. Bài này mình thử rồi hôm qua, thuật toán 99% giống bài 112. Thôi thì mạn phép anh PTM cho mình mược code đó thi luôn nha.
Nhưng mà xem thời gian của bài 102 thì chẳng còn ý chí thi thố gì nữa cả. Hỏng biết là tà thuật kiểu gì trong code nữa. Cảm giác nghi ngờ nhiều lắm. Vì code kiểu gì cũng không thể nào được như thế. Khiếp.
Bài của Lê Duy Thương rất nhanh, dùng Pivot thì lọc lẹ nhất, mình cho rằng bạn hiền của mình biết cách dùng "Thủ Thuật" chứ không phải gọi là "Ăn Gian".
Không rõ ý kiến chủ topic sao?
Còn theo như ở đây, tôi nghĩ các bạn lên post lên đây luôn, mọi người tiện so sánh, đánh giá, và góp ý hơn, chứ sau đó thì có khi không khí đã nguội thì ít người góp ý hơn,
Mọi người cho ý kiến, tôi sẽ viết code theo thuần VBA (không sử dụng các tools sẵn của excel như sort, pivot, filter …vv) và gửi lên đây luôn? nên không?
Chuyện góp ý là chuyện bình thường. Nhưng tôi thấy bạn "đeo bám" những cái tủn mủn quá.
Thế nếu không khai là Long mà khai là Variant thì có thấy kỳ không? Vì thực ra ngày tháng là kiểu Variant.
Nói trắng ra là ngày tháng được lưu ở dạng số (numeric) nên chuyện khai báo biến là Long chả có gì là ngồ ngộ cả. Thậm chí khai báo là Double cũng chả có gì là ngồ ngộ. Vì thực chất ngày giờ được lưu ở dạng đó. Chỉ có điều là ở đây ta chỉ làm việc với ngày tháng, không có thời gian, tức làm việc với số nguyên nên không cần tới Double mà chỉ cần Long. Nhu cầu chỉ cần tới numeric kiểu Long (4 bai) thì dùng Long là hợp lý. Đâu có cần, trong trường hợp chỉ có ngày mà không có giờ, tới kiểu Date – Variant – Double (8 bai)?
Đồng ý với VietMini: đây chỉ là 1 sub quá nhỏ, là 1 phần trong bài toán lớn, nếu quá xét nét (phải xét đến từng cột Dữ liệu chuẩn) thì có xét cả năm, cần phải nhớ đầy chỉ là 1 sub và lại đang cần test tốc độ thời gian nữa.
Với dữ liệu nhiều Bài này chưa hẳn Dictionary đã là lựa chọn tốt đâu Hoàng Trọng Nghĩa ah,
Độc lập cũng là giải pháp hay, nhưng mọi người cứ đưa lên, đọc code người khác hiểu, và sáng tạo lại (dĩ nhiên có ghi chú rõ là trích …) thì cũng là đáng quý, và dù sao cuộc thi cũng kết quả là chia sẻ, và cũng không có chi phải bí mật (hàng ngày vẫn giúp thành viên bài bày ra ngay còn được) – giờ open cùng được bàn thảo phải hay biết bao
Tuy thế Giải pháp 1, up lên đây cũng là sử dụng Dictionary,
Tốc độ có cải thiện nhiều lần: thử trên laptop của tôi khoảng 330-340 mls cho chạy lần đầu, chạy từ lần 2 trở đi chỉ khoảng 230-240 mls
chú ý: Phương châm là nghiên cứu tốc độ là chính, nên coi như dữ liệu Nhập là đã chuẩn hóa (vì sub này chỉ là 1 phần code thử nghiệm mà thui)
Mong nhận được đóng góp
Private Sub LapSo()
''Code lap so th nxt
''Su Dung Dictionary
''nguoi Lap: vodoi2x
''email: vodoi909090@yahoo.com
Application.ScreenUpdating = False
Dim DicH, arrRes(), soDM()
Dim Day1 As Long, Day2 As Long, i As Long, k As Long, p As Long, c1 As Long, c2 As Long
Static DicDM, Ngay(), MaSoHH(), SoLG(), LoaiPhieu(), ThanhTien()
''Nhap du lieu ngay tinh toan
Day1 = Range("NGAY1").Value2
Day2 = Range("NGAY2").Value2
''Neu la lan chay dau tien / hoac khi sheet KHO co sua chua thay doi, thi
'' nap cac du lieu tinh toan Ngay, MaSoHH, …., ThanhTien
If Not Run1K Then
With Range("KHO").Resize(Range("KHO").Rows.Count – 1, 1).Offset(, 1)
Ngay = .Value2
MaSoHH = .Offset(, 5).Value2
SoLG = .Offset(, 6).Value2
LoaiPhieu = .Offset(, 8).Value2
ThanhTien = .Offset(, 9).Value2
End With
Run1K = True ''khang dinh da chay 1 lan doc du lieu tu Sheet KHO
End If
''Neu la lan chay dau tien / hoac khi sheet DM VLSPHH co sua chua thay doi, thi
'' nap cac du lieu tinh toan cho SoDM, va DicDM dung luu gia tri Key
If Run1D Then
p = DicDM.Count
Else
soDM = Range("DMVLSPHH").Offset(1).Resize(Range("DMVLSPHH").Rows.Count – 1, 3).Value2
p = UBound(soDM)
''Khoi tao Dictionary DicDM luu giu Key la MaHH va Item la gia tri Ten, DVT trong sheet DM VLSPHH
Set DicDM = CreateObject("Scripting.Dictionary")
For i = 1 To p
DicDM(soDM(i, 1)) = Array(soDM(i, 2), soDM(i, 3))
Next i
Run1D = True
End If
ReDim arrRes(1 To p + 10, 1 To 12) ''Mang chua ket qua gom 12 cot
Set DicH = CreateObject("Scripting.Dictionary") '' khoi tao Dictionary DicH dung de giu vi tri cua 1 MaHH trong mang arrRes
k = 0
For i = 1 To UBound(Ngay) ''Duyet tung dong chung tu cua Kho de xet ngay
If Ngay(i, 1) <= Day2 Then
If Ngay(i, 1) < Day1 Then ''ton dau ky
c1 = 5: c2 = 6
If LoaiPhieu(i, 1) Like "X" Then
SoLG(i, 1) = -SoLG(i, 1)
ThanhTien(i, 1) = -ThanhTien(i, 1)
End If
Else ''trong ky
If LoaiPhieu(i, 1) Like "N" Then
c1 = 7: c2 = 8
Else: c1 = 9: c2 = 10: End If
End If
If DicH.exists(MaSoHH(i, 1)) Then ''Truong hop CO MaHH trong Dictionary DicH, nen ta lay vi tri va gan gia tri vao arrRes
p = DicH.Item(MaSoHH(i, 1))
arrRes(p, c1) = arrRes(p, c1) + SoLG(i, 1)
arrRes(p, c2) = arrRes(p, c2) + ThanhTien(i, 1)
Else ''Truong hop CHUA CO MaHH trong Dictionary DicH, nen ta cong vao, va gan gia tri vao arrRes
k = k + 1
DicH.Add MaSoHH(i, 1), k
arrRes(k, 2) = MaSoHH(i, 1)
arrRes(k, c1) = SoLG(i, 1)
arrRes(k, c2) = ThanhTien(i, 1)
End If
End If
Next i
''tinh toan TON CUOI KY & tinh Tong GrandTotal cua cac cot Thanh tien: TonDK, NHAP, XUAT, & TON CUOI
''bang cach duyet cac dong cua arrRes
p = k + 1
For i = 1 To k
arrRes(i, 1) = i
If DicDM.exists(arrRes(i, 2)) Then
arrRes(i, 3) = DicDM.Item(arrRes(i, 2))(0)
arrRes(i, 4) = DicDM.Item(arrRes(i, 2))(1)
End If
arrRes(i, 11) = arrRes(i, 5) + arrRes(i, 7) – arrRes(i, 9)
arrRes(i, 12) = arrRes(i, 6) + arrRes(i, 8) – arrRes(i, 10)
arrRes(p, 6) = arrRes(p, 6) + arrRes(i, 6)
arrRes(p, 8) = arrRes(p, 8) + arrRes(i, 8)
arrRes(p, 10) = arrRes(p, 10) + arrRes(i, 10)
arrRes(p, 12) = arrRes(p, 12) + arrRes(i, 12)
Next i
''Xuat ket qua ra Sheet
With Range("KetQuaNXT").Offset(1)
.Resize(13, 12).ClearContents
If k Then .Resize(p, 12) = arrRes
End With
End Sub
cuối cùng cũng đã Upload file lên trực tiếp GPE
Các bạn nên down load file về
vì Sub có tối ưu lần chạy thứ 2 code sẽ không load dữ liệu lại từ KHO, và sheet "DM VLSPHH" nữa
Tuy thế, nếu có thay đổi Dữ liệu ở 2 sheet trên thì sẽ đọc lại (thông qua sự kiện worksheet change) –> khi đó tốc độ thời gian lại như lần 1 – Thông qua 2 biến public chung Run1K và Run1D
vậy, các bạn thử chạy và báo lại tốc độ xem sao và mong nhận được góp ý, xin cám ơn
Và bạn nên chú ý: .Value2 thay vì chỉ .Value khi lấy dữ liệu, cũng như chỉ nhập những dữ liệu cần vào thay vì load kiểu gán mảng 2 chiều rộng (chứa các dữ liệu không cần, vì KHO có nhiều dòng, dẫn đến giảm tốc độ),
Cũng như cố gắng khai báo khai báo tường minh các biến, ví dụ như biến mảng thêm () sau tên biến
Cứ thử test sẽ thấy các điều đó
Như trên nói với thuần VBA thì sử dụng object Dictionary chưa hẳn là giải pháp tốt nhất
Qua thử nghiệm thấy rằng với trường hợp file dữ liệu ở topic này thì dùng Collection cho kết quả NHANH hơn hẳn,:
chú ý qua thử nghiệm:
– nếu số dòng dữ liệu KHO khoảng dưới 5000 –> thì nên dùng Dictionary
– còn nếu lớn hơn nữa thì nên dùng Collection,
Với giải pháp Collection
Tốc độ đã thử nghiêm và cải thiện code nhiều lần: thử trên laptop của tôi khoảng 220-240 mls cho chạy lần đầu, chạy từ lần 2 trở đi chỉ khoảng 100-125mls
Cũng tương tự trên, cần chú ý:
Private Sub LapSo()
''Code lap so th nxt
''Su Dung Collections
''nguoi Lap: vodoi2x
''email: vodoi909090@yahoo.com
''13.02.2014
Application.ScreenUpdating = False
''Khai bao cac bien can thiet
Dim Day1 As Long, Day2 As Long, i As Long, k As Long, p As Long, c1 As Long, c2 As Long
Static Ngay(), MaSoHH(), SoLG(), LoaiPhieu(), ThanhTien(), ColDM As Collection
Dim soDM(), arrRes(), ColHH As Collection
''Nhap du lieu cho Day1, Day2 la 2 ngay dau va cuoi cua Ky tinh toan
Day1 = Range("NGAY1").Value2
Day2 = Range("NGAY2").Value2
''Neu la lan chay dau tien / hoac khi sheet KHO co sua chua thay doi, thi
'' nap cac du lieu tinh toan Ngay, MaSoHH, …., ThanhTien
If Not Run1K Then
With Range("KHO").Resize(Range("KHO").Rows.Count – 1, 1).Offset(, 1)
Ngay = .Value2
MaSoHH = .Offset(, 5).Value2
SoLG = .Offset(, 6).Value2
LoaiPhieu = .Offset(, 8).Value2
ThanhTien = .Offset(, 9).Value2
End With
Run1K = True ''khang dinh da chay 1 lan doc du lieu tu Sheet KHO
End If
''Neu la lan chay dau tien / hoac khi sheet DM VLSPHH co sua chua thay doi, thi
'' nap cac du lieu tinh toan cho SoDM, va ColDM dung dinh vi vi tri theo Key
If Run1D Then
p = ColDM.Count
Else
''nap cac du lieu tinh toan cho SoDM – tuong ung
soDM = Range("DMVLSPHH").Resize(Range("DMVLSPHH").Rows.Count – 1, 3).Offset(1).Value2
p = UBound(soDM)
''Khoi tao collection ColDM luu giu Key la MaHH va Item la gia tri Ten, DVT trong sheet DM VLSPHH
Set ColDM = New Collection
On Error Resume Next
For i = 1 To p
ColDM.Add Item:=Array(soDM(i, 2), soDM(i, 3)), Key:=soDM(i, 1)
Next i
On Error GoTo 0
Run1D = True ''khang dinh da chay 1 lan doc du lieu tu Sheet DM VLSPHH
End If
ReDim arrRes(1 To p + 10, 1 To 12) ''Mang chua ket qua gom 12 cot
Set ColHH = New Collection '' khoi tao collection dung de giu vi tri cua 1 MaHH trong mang arrRes
k = 0
For i = 1 To UBound(Ngay) ''Duyet tung dong chung tu cua Kho de xet ngay
If Ngay(i, 1) <= Day2 Then ''chi xet cac ngay nho hon ngay cuoi ky Day2
If Ngay(i, 1) < Day1 Then ''ton dau ky
c1 = 5: c2 = 6
If LoaiPhieu(i, 1) Like "X" Then
SoLG(i, 1) = -SoLG(i, 1)
ThanhTien(i, 1) = -ThanhTien(i, 1)
End If
Else ''trong ky
If LoaiPhieu(i, 1) Like "N" Then
c1 = 7: c2 = 8
Else: c1 = 9: c2 = 10: End If
End If
On Error Resume Next
p = ColHH.Item(MaSoHH(i, 1))
If Err.Number <> 0 Then ''Truong hop CHUA CO MaHH trong collecttion colHH, nen ta cong vao, va gan gia tri vao arrRes
On Error GoTo 0
k = k + 1
ColHH.Add Item:=k, Key:=MaSoHH(i, 1)
arrRes(k, 2) = MaSoHH(i, 1) ''gan gia tri cot 1 cot 2 mang arrRes (la TT va Maso)
arrRes(k, c1) = SoLG(i, 1)
arrRes(k, c2) = ThanhTien(i, 1)
Else ''case Err.Number <> 0 ''Truong hop DA CO MaHH trong collecttion,
On Error GoTo 0
arrRes(p, c1) = arrRes(p, c1) + SoLG(i, 1)
arrRes(p, c2) = arrRes(p, c2) + ThanhTien(i, 1)
End If ''Err.Number <> 0
End If ''Ngay(i, 1) <= Day2
Next i ''FOR i
''tinh toan TON CUOI KY & tinh Tong GrandTotal cua cac cot Thanh tien: TonDK, NHAP, XUAT, & TON CUOI
''bang cach duyet cac dong cua arrRes
p = k + 1
For i = 1 To k
arrRes(i, 1) = i
On Error Resume Next
arrRes(i, 3) = ColDM.Item(arrRes(i, 2))(0) ''gan gia tri cot 3 cot 4 mang arrRes (la TenHH va Donvi) duoc lay tu colDM
arrRes(i, 4) = ColDM.Item(arrRes(i, 2))(1)
On Error GoTo 0
arrRes(i, 11) = arrRes(i, 5) + arrRes(i, 7) – arrRes(i, 9) ''Tinh ton cuoi ky cot 11 cot 12 cua Ket qua arrRes
arrRes(i, 12) = arrRes(i, 6) + arrRes(i, 8) – arrRes(i, 10)
arrRes(p, 6) = arrRes(p, 6) + arrRes(i, 6) ''Tinh Tong GrandTotal cua cac cot Thanh tien: TonDK, NHAP, XUAT, & TON CUOI trong arrRes
arrRes(p, 8) = arrRes(p, 8) + arrRes(i, 8)
arrRes(p, 10) = arrRes(p, 10) + arrRes(i, 10)
arrRes(p, 12) = arrRes(p, 12) + arrRes(i, 12)
Next i
''Xuat ket qua ra Sheet
With Range("KetQuaNXT").Offset(1)
.Resize(13, 12).ClearContents
If k Then .Resize(p, 12) = arrRes
End With
End Sub
Mong nhận được đóng góp, xin cảm ơn
———–
Tái viết:
Với Collection
Vì quá trính sửa name nên mã BE1 không có tên HH và Đơn vị tính, do đoạn code này bị thiếu
thay dòng này
soDM = Range("DMVLSPHH").Resize(Range("DMVLSPHH").Rows.Count – 1, 3).Value2
thành
soDM = Range("DMVLSPHH").Resize(Range("DMVLSPHH").Rows.Count – 1, 3).Offset(1).Value2
Thiếu Offset(1), code trên cũng đã cập nhập
Hoặc các bạn down file mới đã cập nhập …._New
Xin lỗi mọi người, về kết luận chủ quan, Lúc trước hoa mắt hay sao ý (hoặc thử nghiệm các thời điểm khác nhau, dẫn đến kết luận sai)
Vừa thử nghiệm lại thì giải pháp Collection LUÔN LUÔN NHANH hơn hẳn Dictionnary
với mọi số dòng số liệu KHO
Mọi người có thể thử test trong file gửi kèm,
Tại Sheet chạy chương trình:
+ Sô dòng xét của KHO có thể thay đổi ở O2 (name KHO đã đặt thành name động theo O2)
+ Chạy 2 giải pháp Collection ivs Dictionnary qua các nút bấm – thời gian ghi nhận lần lượt tại cột O và P
+ các giải pháp đều đã loại bỏ trường hợp không load dữ liệu nhập lần 2 – nói cách khác luôn đọc lại dữ liệu nhập (từ 2 sheet KHO, DM VLSPHH) khi chạy chương trình (chạy lần 1 hay lần 2 , 3…. đều load lại dữ liệu nhập)
Mọi người thử nghiệm xem có thấy gì khác biệt 2 phương pháp, ứng với số dòng xét ở KHO khác nhau
Tái viết:
Với Collection
Vì quá trính sửa name nên mã BE1 không có tên HH và Đơn vị tính, do đoạn code này bị thiếu
thay dòng này
soDM = Range("DMVLSPHH").Resize(Range("DMVLSPHH").Rows.Count – 1, 3).Value2
thành
soDM = Range("DMVLSPHH").Resize(Range("DMVLSPHH").Rows.Count – 1, 3).Offset(1).Value2
Thiếu Offset(1)
Hoặc các bạn down file mới đã cập nhập _New
có cập nhập lại code cho [URL='https://www.giaiphapexcel.com/forum/showthread.php?89723-THI-T%E1%BA%A1o-s%E1%BB%95-TH-NXT-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng&p=560702#post560702'%5Dbài 141 và [URL='https://www.giaiphapexcel.com/forum/showthread.php?89723-THI-T%E1%BA%A1o-s%E1%BB%95-TH-NXT-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng&p=560718#post560718'%5Dbài 143 (đã thêm phần tái viết tại các bài đó)
Với Giải pháp Collection
Vì quá trính sửa name nên mã BE1 không có tên HH và Đơn vị tính, do đoạn code này bị thiếu
thay dòng này trong sub lapso
thành
Thiếu Offset(1)
Hoặc các bạn download file mới đã cập nhập _New tại 2 bài đó
—> lỗi này không ảnh hưởng đến kết quả số và cũng thời gian chạy , chỉ là thiếu dòng của bảng DMVTSPHH ,
—
Có nhiều trường hợp bạn không dùng tới 2 mảng keys, items mà.
Collection là class mà bạn. Bạn dùng class chán chê rồi mà.
Chỉ cần Set collection = Nothing thôi.
Nếu bạn Add key đã có thì sẽ có lỗi. Vì bạn có thể truy cập tới Item hoặc bằng chỉ số 1, 2, 3, tức vd. Coll(3), Coll.Item(3) hoặc bằng key, tức vd. Coll("hichic"), Coll.Item("hichic"), vậy nếu có 2 key như nhau thì làm sao biết phải trả về Item nào?
Kiểu truy cập bằng index hoặc bằng "tên" – key thì bạn dùng chán rồi. Vd. Sheets(1), sheets("Sheet1") …
Khi bạn Add key đã tồn tại thì sẽ có lỗi
Vậy thì cũng có thể kiểm tra cái trên để biết có key đó chưa (Exists)
Nhưng với collection khi dùng Add thì key là không bắt buộc.
Tôi không phủ nhận là có nhiều khi cần keys, items. Tôi chỉ nói là có nhiều khi không cần.
Thì có nghĩa là trong phương thức Add thì chỉ tham số đầu Item là bắt buộc còn 3 tham số khác là Optional.
Nếu nhập Key thì sau đó có thể truy cập tới Item bằng key (ngoài cách bằng index). Nếu không nhập Key thì mất khả năng này, tức chỉ truy cập tới Item bằng index thôi.
Tất nhiên nếu ta muốn dùng collection để lọc duy nhất thì ta sẽ nhập key. Tôi chỉ nhấn mạnh là key không bắt buộc. Nghĩa là "có thể, được phép nhưng không bắt buộc".
Đã khắc phục tại [URL="https://www.giaiphapexcel.com/forum/showthread.php?89723-THI-T%E1%BA%A1o-s%E1%BB%95-TH-NXT-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng&p=560743#post560743"%5Dbài 144
Cám ơn HLMT, Test lại đúng là thấy có sự sai khác, xem xét lại thì nguyên nhân chính là đây
Do muốn chạy lần 2 nhanh nên đã khai báo các biến tải dữ liệu (trong đó có SoLG() và ThanhTien()) từ kho là Static để lần 2 chạy thì không cần đọc lại dữ liệu, dẫn đến đoạn lệnh sau
If LoaiPhieu(i, 1) Like "X" Then
SoLG(i, 1) = -SoLG(i, 1)
ThanhTien(i, 1) = -ThanhTien(i, 1)
End If
sẽ bị đổi dấu (âm dương) liên tục qua các lần chạy kế (2, 3,…) dẫn đến kết quả sai khác
Tôi đã chỉnh và cập nhập toàn bộ lại code mới ( …_New1) cho – thêm 2 biến tạm tmpSoLg và tmpTien – thời gian tính chắc có tăng chút.
đã upload lên đây 3 files
+ giải pháp collection
+ giải pháp Dictionary
+ Collection vs Dictionary — cái này không sai vì luôn load lại dữ liệu, tuy nhiên tôi cập nhập lại hợp lý hơn: thay khai báo static thành DIM cho nó giải phóng bộ nhớ sau mỗi lần chạy
Vậy các bạn download và test nhé
dưới đây chỉ show ra code lap so của trường hợp collection
Private Sub LapSo()
''Code lap so th nxt
''Su Dung Collections
''nguoi Lap: vodoi2x
''email: vodoi909090@yahoo.com
''14.02.2014
Application.ScreenUpdating = False
''Khai bao cac bien can thiet
Dim Day1 As Long, Day2 As Long, i As Long, k As Long, p As Long, c1 As Long, c2 As Long
Static Ngay(), MaSoHH(), SoLG(), LoaiPhieu(), ThanhTien(), ColDM As Collection
Dim soDM(), arrRes(), ColHH As Collection
Dim tmpSolg As Double, tmpTien As Double
''Nhap du lieu cho Day1, Day2 la 2 ngay dau va cuoi cua Ky tinh toan
Day1 = Range("NGAY1").Value2
Day2 = Range("NGAY2").Value2
''Neu la lan chay dau tien / hoac khi sheet KHO co sua chua thay doi, thi
'' nap cac du lieu tinh toan Ngay, MaSoHH, …., ThanhTien
If Not Run1K Then
With Range("KHO").Resize(Range("KHO").Rows.Count – 1, 1).Offset(, 1)
Ngay = .Value2
MaSoHH = .Offset(, 5).Value2
SoLG = .Offset(, 6).Value2
LoaiPhieu = .Offset(, 8).Value2
ThanhTien = .Offset(, 9).Value2
End With
Run1K = True ''khang dinh da chay 1 lan doc du lieu tu Sheet KHO
End If
''Neu la lan chay dau tien / hoac khi sheet DM VLSPHH co sua chua thay doi, thi
'' nap cac du lieu tinh toan cho SoDM, va ColDM dung dinh vi vi tri theo Key
If Run1D Then
p = ColDM.Count
Else
''nap cac du lieu tinh toan cho SoDM – tuong ung
soDM = Range("DMVLSPHH").Resize(Range("DMVLSPHH").Rows.Count – 1, 3).Offset(1).Value2
p = UBound(soDM)
''Khoi tao collection ColDM luu giu Key la MaHH va Item la gia tri Ten, DVT trong sheet DM VLSPHH
Set ColDM = New Collection
On Error Resume Next
For i = 1 To p
ColDM.Add Item:=Array(soDM(i, 2), soDM(i, 3)), Key:=soDM(i, 1)
Next i
On Error GoTo 0
Run1D = True ''khang dinh da chay 1 lan doc du lieu tu Sheet DM VLSPHH
End If
ReDim arrRes(1 To p + 10, 1 To 12) ''Mang chua ket qua gom 12 cot
Set ColHH = New Collection '' khoi tao collection dung de giu vi tri cua 1 MaHH trong mang arrRes
k = 0
For i = 1 To UBound(Ngay) ''Duyet tung dong chung tu cua Kho de xet ngay
If Ngay(i, 1) <= Day2 Then ''chi xet cac ngay nho hon ngay cuoi ky Day2
tmpSolg = SoLG(i, 1)
tmpTien = ThanhTien(i, 1)
If Ngay(i, 1) < Day1 Then ''ton dau ky
c1 = 5: c2 = 6
If LoaiPhieu(i, 1) Like "X" Then
tmpSolg = -tmpSolg
tmpTien = -tmpTien
End If
Else ''trong ky
If LoaiPhieu(i, 1) Like "N" Then
c1 = 7: c2 = 8
Else: c1 = 9: c2 = 10: End If
End If
On Error Resume Next
p = ColHH.Item(MaSoHH(i, 1))
If Err.Number <> 0 Then ''Truong hop CHUA CO MaHH trong collecttion colHH, nen ta cong vao, va gan gia tri vao arrRes
On Error GoTo 0
k = k + 1
ColHH.Add Item:=k, Key:=MaSoHH(i, 1)
arrRes(k, 2) = MaSoHH(i, 1) ''gan gia tri cot 1 cot 2 mang arrRes (la TT va Maso)
arrRes(k, c1) = tmpSolg ''SoLG(i, 1)
arrRes(k, c2) = tmpTien ''ThanhTien(i, 1)
Else ''case Err.Number <> 0 ''Truong hop DA CO MaHH trong collecttion,
On Error GoTo 0
arrRes(p, c1) = arrRes(p, c1) + tmpSolg ''SoLG(i, 1)
arrRes(p, c2) = arrRes(p, c2) + tmpTien ''ThanhTien(i, 1)
End If ''Err.Number <> 0
End If ''Ngay(i, 1) <= Day2
Next i ''FOR i
''tinh toan TON CUOI KY & tinh Tong GrandTotal cua cac cot Thanh tien: TonDK, NHAP, XUAT, & TON CUOI
''bang cach duyet cac dong cua arrRes
p = k + 1
For i = 1 To k
arrRes(i, 1) = i
On Error Resume Next
arrRes(i, 3) = ColDM.Item(arrRes(i, 2))(0) ''gan gia tri cot 3 cot 4 mang arrRes (la TenHH va Donvi) duoc lay tu colDM
arrRes(i, 4) = ColDM.Item(arrRes(i, 2))(1)
On Error GoTo 0
arrRes(i, 11) = arrRes(i, 5) + arrRes(i, 7) – arrRes(i, 9) ''Tinh ton cuoi ky cot 11 cot 12 cua Ket qua arrRes
arrRes(i, 12) = arrRes(i, 6) + arrRes(i, 8) – arrRes(i, 10)
arrRes(p, 6) = arrRes(p, 6) + arrRes(i, 6) ''Tinh Tong GrandTotal cua cac cot Thanh tien: TonDK, NHAP, XUAT, & TON CUOI trong arrRes
arrRes(p, 8) = arrRes(p, 8) + arrRes(i, 8)
arrRes(p, 10) = arrRes(p, 10) + arrRes(i, 10)
arrRes(p, 12) = arrRes(p, 12) + arrRes(i, 12)
Next i
''Xuat ket qua ra Sheet
With Range("KetQuaNXT").Offset(1)
.Resize(13, 12).ClearContents
If k Then .Resize(p, 12) = arrRes
End With
End Sub
P/S: vì dung lượng các file khá lớn, nên tôi sẽ gỡ bỏ files (code chưa chính xác) ở các bài viết trước nhé, các bạn cần thì cập nhập theo file mới nhất, xin cảm ơn
Cũng nhân đây nhờ chủ topic NDT, test code cho 2 trường hợp Collection và Dictionary ở các files trên, thử xem cùng môi trường và cách test chuyên nghiệp – so sánh xem các giải pháp thế nào, xin cám ơn.
Và mọi người cứ xem xét, chắc vẫn còn có thể cải thiện giảm thêm thời gian chạy nữa – nhất là các lần chạy kế (lần chạy 2, 3,…) – ví như 1 cáchnếu chúng ta ghi kết quả trung gian xuống phần tạm phụ nào đó của sheet
Đúng thế nhắc đến dữ liệu dạng cơ sở dữ liệu như bài này, thì SQL vẫn là đa năng và uyển chuyển nhất, tiếc là nếu cứ xét tốc độ SQL áp vào Excel là ngoại tác vụ nên có thể kém hơn chút, nhưng cũng nên xem xét thì sẽ có nhiều cái hay để bàn
Là do chỗ này cứ lấn cấn việc đặt name KHO là có gồm dòng tiêu đề hay không có dòng tiêu đề đây, dẫn đến chỉnh đi chỉnh lại xót luôn (vì lo việc tổng độ có sao chăng , khi ta offset hay không nên offset)
Hiện name KHO là bao hàm cả dòng tiêu đề (như chủ topic đặt từ đầu)
nên sửa thành như sau cho đúng đủ số dòng dữ liệu
Đúng là bài toán này dùng ADO là gọn và uyển chuyển nhất, tuy nhiên tốc độ so với những cách khác ở trên thì rất hạn chế. Cách ADO so với cách của anh vodoi2x thì ADO sẽ cho thời gian chậm hơn gấp 20 lần. Topic này đưa ra nhằm tìm cách giải quyết với thời gian nhanh nhất. Xét thấy ADO không có được ưu điểm về tốc độ cho bài toán này nên em đành theo dõi và học hỏi thêm từ những cách khác.
Offset resize, không làm giảm tốc độ đáng kể đâu ah,
đúng là giờ nhanh hơn rùi, nhờ chỉ sử dụng 1 Dictionary – và qua đó thấy vai trò của .Value2 cũng như Không đọc dữ liệu dư sẽ tăng tốc độ đáng kể trong bài toán topic này
Tuy vậy, PTM xem bổ sung việc lỗi xảy ra khi – Mã hàng không tồn tại trong "bảng danh mục" (sheet DM VLSPHH) nhưng lại có trong KHO
Vì dụ giờ ta có mã hàng mới là HH099 xuất hiện trong kho –> khi đó chương trình có lỗi –
VỚi đúng cơ sở DL này thì thuật toán này tốt đã giảm đi 1 DIC,
Tôi sẽ chuyển bài này của PTM sang sử dụng collection –> xem tốc độ thế nào có khi lại hay hơn phiên bản collection của tôi,
Tôi đã thử chuyển Thời gian tính giảm đi khoảng 10% khi dùng collection so với dictionary – Tuy nhiên thời gian vẫn dài hơn (chậm) so với collection của vodoi2x ở [URL="https://www.giaiphapexcel.com/forum/showthread.php?89723-THI-T%E1%BA%A1o-s%E1%BB%95-TH-NXT-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng&p=560851#post560851"%5Dbài 156
Code chuyển đây, và cũng đã sửa lỗi
code chuyển sang collection từ code gốc PTM [URL="https://www.giaiphapexcel.com/forum/showthread.php?89723-THI-T%E1%BA%A1o-s%E1%BB%95-TH-NXT-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng&p=560956#post560956"%5Dbài 162
Sub LapSo()
''Code goc from PTM0412 sd Dictionary
''vodoi2x chinh sua , sua loi va chuyen sang collection 15.02.2014
Application.ScreenUpdating = False
Dim ListArr(), sArrID(), TmpArr(), RArr(), sArrQty(), sArrAmt(), sArrDocType(), sArrDate()
Dim ColDM As Collection, EndR As Long, ListCt As Long
Dim DataCt As Long, ListEndR As Long, Date1 As Long, Date2 As Long
Dim i As Long, j As Long, k As Long, Check As Double
ListEndR = Sheet1..End(xlUp).Row
''Lay danh muc vao mang
ListArr = Sheet1.Range("A4:C" & ListEndR).Value2
ListCt = UBound(ListArr, 1)
Set ColDM = New Collection
''Nap mang danh muc vao Collection
On Error Resume Next
For i = 1 To ListCt
ColDM.Add Item:=i, Key:=ListArr(i, 1)
Next
On Error GoTo 0
''Xác dinh dong cuoi cua data va nap vao mang
With Sheet20
EndR = .Cells(4, 1).End(xlDown).Row
sArrID = .Range("G4:G" & EndR).Value2
sArrQty = .Range("H4:H" & EndR).Value2
sArrAmt = .Range("K4:K" & EndR).Value2
sArrDocType = .Range("J4:J" & EndR).Value2
sArrDate = .Range("B4:B" & EndR).Value2
End With
DataCt = EndR – 3
'' gan gia tri cho bien
ReDim TmpArr(1 To ListCt + 10, 1 To 7) ''10 so du phong Ma hang khong co trong danh muc
Date1 = Sheet3.
Date2 = Sheet3.
Dim uB As Long
uB = ListCt
''Duyet mang Data
For i = 1 To DataCt
If sArrDate(i, 1) <= Date2 Then
' 'Xac dinh dong chua ma HH trong danh muc, gan vao cot 1 cua KQ tam
On Error Resume Next
j = ColDM.Item(sArrID(i, 1))
If Err.Number <> 0 Then
On Error GoTo 0
uB = uB + 1
j = uB
ColDM.Add Item:=j, Key:=sArrID(i, 1)
Else
On Error GoTo 0
End If
TmpArr(j, 1) = sArrID(i, 1) ''j
''Neu ngay < ngay bat dau, tinh 2 cot ton dau
If sArrDate(i, 1) < Date1 Then
If sArrDocType(i, 1) = "N" Then
''Cong nhap
TmpArr(j, 2) = TmpArr(j, 2) + sArrQty(i, 1)
TmpArr(j, 3) = TmpArr(j, 3) + sArrAmt(i, 1)
Else
''Tru xuat
TmpArr(j, 2) = TmpArr(j, 2) – sArrQty(i, 1)
TmpArr(j, 3) = TmpArr(j, 3) – sArrAmt(i, 1)
End If
''Neu ngay trong khoang bao cao
Else ''If sArrDate(i, 1) <= Date2 Then
''Neu loai chung tu là N, tinh 2 cot Nhap
If sArrDocType(i, 1) = "N" Then
TmpArr(j, 4) = TmpArr(j, 4) + sArrQty(i, 1)
TmpArr(j, 5) = TmpArr(j, 5) + sArrAmt(i, 1)
''Neu loai chung tu la X, tinh 2 cot xuat
Else
TmpArr(j, 6) = TmpArr(j, 6) + sArrQty(i, 1)
TmpArr(j, 7) = TmpArr(j, 7) + sArrAmt(i, 1)
End If
End If
End If
''Ket thuc vong lap, Mang KQ tam co 12 dong
Next
''Khai bao Mang KQua
ReDim RArr(1 To uB, 1 To 12)
k = 0
''Duyet mang KQ tam
For i = 1 To uB
''Kiem tra dong co du lieu
Check = TmpArr(i, 2) + TmpArr(i, 3) + TmpArr(i, 4) + _
TmpArr(i, 5) + TmpArr(i, 6) + TmpArr(i, 7)
''Neu co dulieu, them vao mang KQua
If Check > 0 Then
k = k + 1
''4 cot thong so Hang hoa
RArr(k, 1) = k
If i <= ListCt Then
RArr(k, 2) = ListArr(i, 1)
RArr(k, 3) = ListArr(i, 2)
RArr(k, 4) = ListArr(i, 3)
Else
RArr(k, 2) = TmpArr(i, 1)
End If
''6 cot Ton, nhap, xuat
For j = 5 To 10
RArr(k, j) = TmpArr(i, j – 3)
Next
''2 cot Ton cuoi
RArr(k, 11) = RArr(k, 5) + RArr(k, 7) – RArr(k, 9)
RArr(k, 12) = RArr(k, 6) + RArr(k, 8) – RArr(k, 10)
End If
Next
'' Gan ket qua xuong sau khi xoa
Sheet26..Resize(12, 12).ClearContents
Sheet26..Resize(k, 12) = RArr
''With Sheet26..Offset(k)
'' Union(.Offset(, 5), .Offset(, 7), .Offset(, 9), .Offset(, 11)).Formula = "=SUM(RC:RC)"
''End With
Set ColDM = Nothing
Application.ScreenUpdating = True
End Sub
(có thể chưa thật hiểu thuật toán gốc – nên có thể việc chuyển sang collection chưa hoàn hảo nên chưa phát huy được hết mặt mạnh code gốc, —> nên mọi người cứ thử kiểm tra và check xem sao)
Thử với Collection!
Thay vì dùng Dictionary, tôi dùng Collection để thay thế (bài tôi gửi Anh Tuân tôi dùng Dictionary).
Cái vụ màu đỏ em đã sử dụng từ rất lâu rồi ạ và thuật toán Mảng trong Mảng cũng thú vị và rất nhanh đấy Sư phụ ạ. Riêng cái Collection (có thể nó có ở đâu đó trên diễn đàn mà tôi chưa thấy) thì mới biết từ bài của Vodoi2x (trước đây học lóm của Thầy ndu96081631 chiêu Dictionary). Cám ơn Vodoi2x nhé!
Nếu code khai báo biến với Static hoặc Public thì người ta cũng ăn gian ở các lần sau như cậu thôi, bởi lần đầu code cậu tạo Pivot, lần sau cậu đã có nó và thực hiện lệnh copy (cái này cậu ăn gian hơn tí hihihi)!
———————————————————————
Test với Dictionary và Collection thì trên máy tính của tôi Dict vẫn nhanh hơn Coll với vòng lặp 300.000 lần, nhưng nếu hơn nữa thì thằng Coll chạy ăn đứt thằng Dict!
——————————————————————
Đúng như tôi nghĩ, Collection đã được admin levanduyet giới thiệu tại đây:
[URL='https://www.giaiphapexcel.com/forum/showthread.php?35487-Filter-and-get-the-unique-values-L%E1%BB%8Dc-v%C3%A0-l%E1%BA%A5y-c%C3%A1c-gi%C3%A1-tr%E1%BB%8B-kh%C3%B4ng-tr%C3%B9ng&p=235231#post235231']https://www.giaiphapexcel.com/forum/showthread.php?35487-Filter-and-get-the-unique-values-Lọc-và-lấy-các-giá-trị-không-trùng&p=235231#post235231
Tôi ngồi ngẫm nghĩ và nghiệm ra như thế này, với biến mảng động, thay vì ta khai báo với Static trong thủ tục:
Thì ta nên thực hiện biến này với khai báo là Public:
Một lý do hết sức đơn giản là, khi dữ liệu mà mảng ArrData được gán thay đổi, cụ thể trong bài này là sheet KHO vì lý do gì đó nhập thêm hay bớt ra (thông thường cơ sở dữ liệu người ta thường nhập trên Form) thì ta chỉ việc dùng thủ tục này để giải phóng biến, thay vì dùng biến Boolean để check (dư ra biến Boolean này):
Mặc dù bài mà Anh Nguyễn Duy Tuân đưa ra tương đối dễ, nhưng học hỏi và suy luận được rất nhiều thứ!
Tổng kết cuộc thi viết code đạt tốc độ nhanh nhất sổ tổng hợp nhập xuất tồn
Tổng kết cuộc thi viết code đạt tốc độ nhanh nhất bài lập sổ tổng hợp nhập xuất tồn trong VBA.
Cuộc thi phát động vào sáng ngày 10-02-2014, hạn cuối gửi bài 12hAM ngày 15/02/2014.
Qua 6 ngày vừa qua có thể thấy có rất nhiều thành viên quan tâm và được thể hiện qua lượng người xem > 3000. Được các thành viên có kinh nghiệm tốt về VBA đã nhiệt tình tham gia trao đổi, gửi bài như: SA_DQ, HYen17, ChanhTQ@, Ba Tê, Hoàng Trọng Nghĩa, Lê Duy Thương, Hai Lúa Miền Tây, ptm0412, dhn46, Nguyễn Duy Tuân, Vodoi2x.
Việc đánh giá các bài gửi bằng phương pháp như sau
1. Tất cả phải chạy trên cùng một máy tính
2. Tắt tất cả các ứng dụng đang chạy, các chương trình thường trú cũng tắt đi nếu không liên quan đến Windows để giảm những tác động đến Windows và Excel.
3. Một bài thi phải được test theo quy trình như sau
b1. Tắt Excel (nếu đang mở)->Mở Excel –>đảm bảo môi trường "sạch"
b2. Mở file Excel cần đo thời gian. Hãy đợi một lúc đảm bảo Excel đã thực hiện các công việc của nó xong. Hãy nhấn CTRL+ALT+DEL để mở "Task Manager", trong tab "Processes" đảm bảo dòng có EXCEL.EXE, CPU và Memory đang ở con số ổn định (không thay đổi liên tục).
Sửa lại thủ tục "DoThoiGian" để tự tính trung bình 3 lần chạy như sau:
b3. Nhấn nút "Thực Hiện" tại sheet "THNXT" và ghi nhận thời gian thực hiện.
Đến code của bài dự thi khác lại lập lại từ b1.
Kết quả do tôi test như sau.
Cấu hình phần cứng và phần mềm của máy tính test
Microsoft Excel 2010 Professional Plus 32-bit.
Kết quả thu được như sau:
Với kết quả trên chúng ta thấy ngay anh Vodoi2x là người có code chạy nhanh nhất với ví dụ dùng Collection, tốc độ đạt 250 mili giây. Xin cảm ơn và chúc mừng anh.
Đánh giá chung các bài của các tác giả:
Tất cả các bài thi tốc độ đều < 1000 mili giây vì vậy đều có thể được coi tốc độ nhanh.
Các tác giả Ba Tê, Hoàng Trọng Nghĩa, dhn46 dùng Array và Dictionary để nạp danh sách duy nhất. Tốc độ khá nhanh, chêch lệch nhau không nhiều.
Bác HYen17 thì viết VBA kết hợp với hàm Excel là SumIf chạy cũng rất nhanh, tuy nhiên các thành viên chờ bác sửa lại code về mã danh mục thì không thấy bác viết tiếp. Hy vọng bác bổ sung tiếp trong topic này.
Anh Lê Duy Thương dùng Pivot cũng rất tốt. Tuy nhiên để đánh giá ký chút ta phải tính cả lúc tạo Pivot. Nếu trong thực tế sử dụng ta chỉ phải tạo Pivot nếu chưa từng tạo nó còn lần thứ 2 trở đi không phải tạo thì Pivot có lẽ là tốc độ nhanh nhất. Nếu nếu file Excel lưu Pivot các bạn nên chú ý tới dung lượng của file, tốc độ mở file Excel vì có thể sẽ nặng và chậm.
Anh Vodoi2x đã đưa cả 2 cách Dictionary và Collection kết hợp với Array. Ví dụ Dictionary tốc độ cũng không bằng Collection. Xem qua thì hình như code trong ví dụ Collection có giải thuật khác? Cá nhân em đánh giá với yêu cầu ví dụ này thì nếu chỉ là Collection thì nó không phải yếu tố làm cho code chạy nhanh? Các yếu tố quyết định làm cho code của anh Vodoi2x chạy nhanh nhất chính là chuyển dùng Value2 thay cho Value, chuyển Range.Value2 sang array, array đóng vai trò là nguồn dữ liệu, được dùng để phân tích và tính toán trong vòng lặp.
Bài của tôi – Nguyễn Duy Tuân đã gửi trang đầu đạt tốc độ thấp nhất (hơn 2000 mili giây). Đạt giải khuyến khích ////// . Tuy nhiên sau khi lấy kinh nghiệm bài anh Vodoi2x chuyển Range sang Array làm nguồn, Value->Value2, giữ nguyên thuật toán tốc độ đạt 299 mili giây (kém 50 mili giây so với bài Vodoi2x). Điều đặc biệt trong code của tôi chỉ dùng Array (không dùng Dictionary, Collection). Tôi đang nghi ngờ rằng, code của anh Vodoi2x nhanh hơn của tôi là do thuật toán hoán đổi mảng chứ không phải do dùng Collection?
Nhiều người đã thí nghiệm Collection nhanh hơn Dictionary nên tôi lấy Collection so sánh với Array với bài test: nạp danh sách, kiểm tra mã tồn tài và nạp tiếp.
Chạy thủ tục "DoThoiGianColl_Array" thì thấy Collection: 667, Array: 107. Vậy Array nhanh hơn Collection rất nhiều. Với bài toán mà yêu cầu nạp danh sách duy nhất thì ta nên dùng Array thuần túy là được rồi. Việc kiểm tra Item có tồn tại trong danh sách hay không ta tự viết tốc độ sẽ nhanh hơn ở vấn đề như sau. Nếu danh sách Mã hàng hóa trong sổ KHO sắp xếp tăng dần khi đó các mã nạp trong mảng có danh sách duy nhất cũng tăng dần. Vậy khi kiểm tra một mã theo thứ tự trong sổ KHO, hàm ItemExists tìm từ dưới lên trên nó sẽ thấy ngay bới 1 đến 2 vòng. Nếu theo kiểm tra ngầm định các hàm Dictionary.Exists() nếu tìm từ trên xuống dưới, khi số mã hàng nhiều việc tìm kiếm sẽ lâu hơn.
Trên là bài test cũng như những đánh giá của riêng cá nhân tôi nên có thể chưa phải đã tuyệt đối chính xác. Các thành viên có thể trao đổi làm rõ thêm tại topic này. Thêm nữa là các tác giả đã gửi bài bằng file hoặc code tại topic này có thể gửi lại code "LapSo" và các hàm, thủ tục của mình lên đây kèm theo những comment thật chi tiết và rõ ràng để các thành viên có điều kiện học tập.
Thông qua topic viết VBA tốc độ tối ưu này rõ ràng chúng ta được học lẫn nhau bởi các phương pháp đa dạng, tăng kiến thức VBA. Các thành viên GPE có nguồn thư viện để học tập, vận dụng cho bài toán thực tế. Các thành viên tham gia trao đổi và gửi bài phần lới đều có kinh nghiệm, kiến thức tốt về VBA, không ngại việc thắng thua, không dị ứng với từ "THI" mà theo đúng với tinh thần của topic này là giao lưu học hỏi lẫn nhau, các anh đúng là các người thầy thực sự của rất nhiều thành viên GPE về kiến thức, tinh thần học học và chia sẻ.
Xem sơ code của dhn thì thấy tồn đầu chỉ tính cộng nhập mà không trừ xuất. Như vậy, nếu trước ngày bắt đầu không phải 1/8 mà là 1/9, thì số dư đầu bao gồm cả nhập và xuất trước ngày FDate, nhưng toàn là cộng chứ không trừ. Dẫn đến tồn đầu sai.
Thứ hai, DicData lấy trong dữ liệu và phải kiểm tra sự tồn tại 65.000 lần. Nên biết rằng 1 lần kiểm tra sự tồn tại tức là 1 thao tác phải tính thời gian. Nhân lên 65000 lần sẽ ra 1 con số đáng kể. Code của tôi chỉ 1 Dic lấy từ danh mục, và khi duyệt Data. chỉ truy xuất chứ không kiểm tra nữa.
Sau đó, để loại trừ các mặt hàng không tồn cũng không nhập xuất, tôi phải kiểm tra, nhưng lần này chỉ kiểm tra bằng vòng lặp 12 vòng. Giả sử không phải 12, cũng chắc chắn là ít hơn 65.000 mặt hàng.
Đối với việc quản lý kho hàng thì thông thường người ta đã bắt lỗi ngay khi nhập xuất 1 mặt hàng không có trong danh mục. Do đó tôi viết code dựa vào cơ sở không có việc chưa có mặt hàng đã mua bán. Vả lại, nếu có trường hợp này xảy ra thì sẽ chỉ có mã mà không có tên và đơn vị tính tương ứng, vì cấu trúc Data không có.
Mảng trong mảng không phải là thuật toán mà chỉ là thủ thuật (phương tiện) để thực hiện thuật toán mà thôi. Ngoài ra, nó có thể thú vị, nhưng không nhanh. Mới cách đây mấy ngày Nghĩa nói chậm, hôm nay lại nói nhanh là sao?
Gởi Tuân,
Tuân test hộ code tôi đưa lên lần 2 ở bài [URL='https://www.giaiphapexcel.com/forum/showthread.php?89723-THI-T%E1%BA%A1o-s%E1%BB%95-TH-NXT-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng&p=560956#post560956'%5D#262 xem có phải trung bình 270 không.
Về Pivot table, tôi không đồng ý về việc tăng dung lượng. Nó chỉ lưu trữ dưới dạng số, không hề có công thức và cũng không tính toán lại thường xuyên. Tôi có xem bài của Duy Thương, refresh (tức là tính toán lại) cũng khoảng 400ms, cộng với code copy ăn gian 50 ms, cũng thuộc loại có hạng. Ăn gian như vậy không đúng, vì không phải cứ ngày bắt đầu đó, ngày kết thúc đó mà tính mãi. Phải thay đổi xem báo cáo tháng này, báo cáo tháng kia, báo cáo quý, báo cáo năm, chứ không chỉ xem mãi 1 báo cáo, hoặc có 1 báo cáo tính đi tính lại mãi. Do đó phải tính thêm thời gian refresh.
Chỉ có điều Thương làm chưa đến nơi đến chốn, vì chưa lường trước 1 số việc:
– Giả sử để tính đầu kỳ có cả cộng nhập và trừ xuất, thì đầu kỳ sai
– Giả sử trong kỳ chỉ có nhập không có xuất, hoặc có xuất không nhập, hoặc không có cả 2, thì code copy sẽ copy sai.
Bài của anh ptm0412 có cải tiến theo kinh nghiệm của vodoi2x tốc độ đạt 317 mili giây. Kết quả vậy là rất nhanh. Cách viết code của anh anh ptm0412 khá giống với em.
Đã test code của bạn ở trên. Tốc độ đạt 667, nhanh hơn code cũ là ~200 mili giây. Có lẽ cần tiếp tục xử lý mảng nữa để đạt tốc độ nhanh hơn.
Ý kiến anh Nghĩa rất đúng. Khi đưa code này vào thực tế thì cần kiểm tra hợp thức hóa của mảng nếu không sẽ lỗi. Range.Value(2) nếu từ 2 ô trử lên sẽ là mảng 2D, nếu chỉ 1 ô thì nó không phải là mảng. Hướng giải quyết như anh Nghĩa đưa ra hoặc có thể cải tiến CSDL như sau:
Sổ KHO, Ngay sau dòng tiêu đề ta đưa dòng giá trị trống. Cách này rất cần thiết nếu sử dụng ADO:
Kiểu ngày tháng, số là 0; Kiểu văn bản là ';
Từ dòng thứ 2 mới là dòng dữ liệu của doanh nghiệp.
Vậy trong code ta vẫn làm bình thường để kiểm tra có dữ liệu hay không ta dùng
If not IsArray(Mảng dữ liệu) then
'Không có dữ liệu
'Làm những việc không dữ liệu
'Thoát…
End If
Tôi làm csdl khá nhiều cả MySQL cả Excel với các bài toán thực tế về kế toán, kho, A-Tools. Những sổ sách phải dùng VBA thuần tuý có nhiều trường hợp phải dùng SQL. Vậy nên CSDL tôi luôn phải làm dòng đầu tiên trống với giá trị giả định theo kiểu dữ liệu của trường trong bảng. Lưu ý là khi bảng dữ liệu trống hoàn toàn hoặc có vài dòng dữ liệu đầu tiên nhưng dữ liệu không xác định kiểu rõ ràng dẫn đến ADO hiểu sai trường dữ liệu dẫn đến lỗi. Các table của loại csdl khác được khai báo kiểu dữ liệu rõ ràng nên không bị lỗi. Còn bảng tính Excel kiểu dữ liệu phụ thuộc vào giá trị nhập vào ở 8 dòng đầu tiên. Vậy nếu không có hoặc có cột nào đó chưa nhập dữ liệu thì ADO hiểu sai cấu trúc là bình thường. Ví thế từ lâu tôi làm trên Excel luôn làm dòng giả định để ADO xác định kiểu giá trị đúng.
Vấn đề về số dư đầu. Có nhiều người nhập số dư đầu trong danh mục tôi cho là không chuẩn vì các lý do sau:
– Một mã hàng hoá, vật tư có thể tồn ở 3 kho, 3 bộ phận. Vậy một dòng danh mục của mã này sẽ nhập thế nào? Mở thêm cột không phải giải pháp tổng thế.
– Tồn đầu được xác định bởi thời điểm. Trong cả kỳ làm việc sẽ có nhiều lần chốt tồn đầu. Vậy danh mục ghi thế nào?
Vậy nên danh mục hãy chỉ để lưu thông tin chi tiết về đối tượng mà thôi.
Tôi không làm như thế. Khi xác định DataEndRow thì kiểm tra EndRow bằng bao nhiêu, nếu = 3 (là dòng tiêu đề) tức là dữ liệu rỗng. Nếu bằng 4 tức là chỉ có 1 dòng dữ liệu. (trong trường hợp này sẽ dùng xlUp thay vì xlDown hoặc tùy biến). Tại sao phải gán năm bảy Array rồi mới kiểm tra năm bảy array đó?
Ngoài ra,với cấu trúc dữ liệu chuẩn, luôn luôn có ít nhất 1 cột không được phép rỗng (ngày chứng từ, số chứng từ, mã hàng, ID dòng, …), Cột nào rỗng nghĩa là cột đó được phép rỗng. Ta dùng cột chuẩn đó để xác định và không cần kiểm tra ô rỗng, không cần thêm 1 dòng dữ liệu rỗng gì cả.
1. Mỗi Bill là 1 mã hàng, thì có ít nhất 1 dòng nhập và/hoặc 1 dòng xuất (đang nói về nhập xuất tồn), số dòng dữ liệu luôn lớn hơn số dòng mã.
2. Nếu danh mục là 1 triệu mã hàng kèm với số dư đầu kỳ (mà thường là thế, tại sao thì tôi nói sau ở mục 4), thì nếu không lấy danh mục làm chuẩn, sẽ bỏ sót những mã có dư đầu kỳ mà không có nhập xuất trong kỳ (mặt hàng chết). Việc này tôi đã nói ở bài trên
3. Thông thường đối với dữ liệu lớn và qua nhiều năm, định kỳ người ta đánh dấu trong danh mục những mã hàng không còn sử dụng. Nên khi nạp danh mục sẽ kiểm tra loại bớt.
4. Nếu quả thực mã hàng 1 triệu dòng (không phải không có), hiếm khi người ta sử dụng Excel. Vì nếu 1 triệu dòng mã sẽ có trên 1 triệu dòng nhập xuất, Excel không chứa hết. Nếu sử dụng Excel người ta cũng tách dữ liệu ra từng năm, mỗi năm có số dư đầu kỳ, và mỗi khi tạo dữ liệu cho năm mới cũng loại bỏ bớt những mã không còn dùng ra khỏi danh mục.
5.Với thí dụ của Nghĩa mỗi bill là 1 mã (không trùng bao giờ), thì người ta sẽ không tạo danh mục làm gì. Sinh ra 1 mã chỉ xài 1 lần thì không tạo danh mục.
Anh Nghĩa test code so sánh array và collection của mình ở bài trên chưa? Tình huống khá giống thực tế của topic này.
Nhà đang mất điện nên chưa test được :(. Bài test của a Nghĩa với giả thiết không tìm được giá trị trong mảng vì giá trị của nó luôn mới. Vậy hàm tìm kiến ItemExists luông phải chạy đủ số vòng lặp. Nên chậm. Nếu anh nghĩa tìm giá trị "Nghĩa " & i-1 có thể sẽ nhanh. Hàm ItemExists phát huy tốc độ khi phải tìm giá trị thực sự tồn tại teong danh sách tìm, nếu dữ liệu nguồn được sắp xếp thì càng nhanh.
Sao lại edit bài thêm hẳn 1 ý như thế? Thêm ý thì phải viết bài mới chứ? Chỉ khi sửa chính tả, sửa sai con số, … thì mới edit bài.
RArray của tôi làm sao mà có dòng trống được chứ? Chỉ có dòng không dùng đến mà thôi. Còn nếu DMArray lấy từ dữ liệu nhập xuất hàng ngày nó ngắn hơn thật, nhưng gắn vào listbox để làm gì? Gắn vào listbox hoặc combobox là để chọn mã hàng khi nhập liệu chứ dữ liệu nhập xong xuôi gắn vào làm chi.
Về đại lý xe máy thì làm sao bằng siêu thị bán lẻ được. Vấn đề là mã càng nhiều thì giao dịch càng nhiều. Có giao dịch mới tạo mã. Không ai tạo mã ra để đó không dùng cả. Cái thí dụ về xe máy của Nghĩa, tức là như thế: các hãng xe cung cấp cho 10.000 mã, lập tức điền vào danh mục 10.000 dòng. Thực tế là kế toán không làm chuyện thừa đó, họ chỉ thêm mã khi có lô hàng thực sự nhập về và chỉ thêm mã cho mặt hàng đã nhập nhưng chưa có mã mà thôi.
Qua năm mới, bỏ bớt danh mục mặt hàng lỗi mốt, không bán được, không muốn kinh doanh, …
Nghĩa vẫn chưa đọc kỹ câu hỏi của tôi về tồn đầu. Tuân thì biết vấn đề tồn đầu nên có lẽ cũng lấy từ danh mục.
Dữ liệu đầu năm chỉ mới có năm ba dòng thì đúng, nhưng những mặt hàng tồn chưa kịp bán không hiện lên báo cáo sao?
Tôi cũng bổ sung thêm 1 câu bị bỏ sót là kiểm tra nếu EndRow = 4 tức là dữ liệu 1 dòng, thì xử lý khác cho khỏi lỗi Array, chứ không add năm bảy array rồi kiểm tra năm bảy array đó.
Gởi Tuân,
Ý tôi là không phải nhất thiết lưu trữ tồn đầu trong danh mục, mà lưu trữ trong 1 bảng tồn đầu, bảng này và bảng danh mục có quan hệ 1-1. Nhiều kho thì sẽ có nhiều cột tồn hoặc nhiều bảng tồn như thế. Nhưng đó là dữ liệu lớn và phải xử lý bằng ADO hoặc SQL, không phải trên Excel. Trên Excel thì thêm 1 cột hay vài cột cũng không thành vấn đề.
Mỗi lần tính toán đều có tồn đầu riêng của lần đó, tất nhiên, nhưng vẫn có cái tồn đầu năm tài chính mang sang từ năm trước, và đã kiểm kê cuối năm, so khớp và điều chỉnh. Những cái tồn đầu tháng 2, đầu tháng 8, tồn đầu 15 tháng 4 vẫn phải tính và tính từ đâu? Chả lẽ tính từ lúc mới thành lập công ty tới nay dù cho 5 năm, 10 năm, dữ liệu 1 triệu, chục triệu dòng?
Nghĩa trả lời cho tôi câu hỏi mà tôi nhắc lại 3 lần rồi:
Nghĩa đang lấy danh mục hàng duy nhất từ bảng dữ liệu, chứ không lấy từ bảng danh mục (và không lấy từ danh mục tồn). vậy:
Dữ liệu đầu năm chỉ mới có năm ba dòng thì đúng, nhưng những mặt hàng tồn chưa kịp bán không hiện lên báo cáo sao?
Còn tôi sẽ lấy số tồn trong danh mục luôn. Tuân có thể phản đối, vì Tuân sẽ lưu số tồn ở chỗ khác, còn Nghĩa thì không biết khái niệm có tồn đầu khác zero.
Một câu mà tôi cũng muốn nhắc lại: Kế toán không làm công việc thừa là nhập sẵn 10.000 dòng mã hàng do nhà cung cấp giao cho kiểu giao catalogue. Copy thì copy lúc nào chẳng được, đâu phải 10.000 dòng thì copy, còn 5 dòng thì gõ tay cho sai?
Cái đó không phải lý thuyết suông, mà là thực tế kế toán không làm chuyện thừa.
Thực ra giả thuyết của sư phụ cũng rất đơn giản
Thì chỉ cần sửa lại 1 chut là xong
thầy thử test file xem có còn sai chỗ nào không giúp em.–=0
thông thường em vẫn dùng pivot table để lấy kết quả sau đó copy vào mẫu báo cáo là xong
sếp chỉ cần xem kết quả khi in ra va ký duyệt.
Không biết có phải lê duy thương nghiện pivot table hay không mà khi gặp những dữ liệu lớn thường nghĩ ngay đến pivot table.sau đó mới đến công cụ khác.
Khi đọc đề bài tôi đã có vài chỗ không hiểu nhưng do mọi người đang thi tôi không muốn hỏi vào vì không muốn mọi người mất tập trung.
Tôi không biết người ta ghi sổ sách như thế nào nhưng có vài điểm tôi không hiểu được.
Trích bài #22
1. Tồn đầu. Vẫn biết là Tuân đã cho khái niệm: Mọi Nhập (N) trước ngày 1 cộng với nhau, mọi Xuất (X) cũng cộng với nhau, và hiệu N – X sẽ là tồn đầu.
Theo tôi có vẻ phi thực tế. Vì nếu thế, theo vd. ta thấy ngày 31-07-2005 trong kho không có mặt hàng HH001. Tươnbg tự với các mặt hàng khác. Tức ở thời điểm ngày 31-07-2005 thì kho trống rỗng. Thực tế thì làm gì có kho nào như thế.
Theo cái lôgíc của tôi thì: Nếu tôi làm báo cáo cho khoảng NGAY1 – NGAY2 thì cũng có nghĩa là tôi phải biết được ở thời điểm (NGAY1 – 1) thì trong kho mỗi mặt hàng có bao nhiêu. Giả sử có k mặt hàng trong kho với số lượng là n1, n2, …, nk thì theo tôi ta sẽ phải tạo vùng dữ liệu kho mà k dòng đầu có ở cột F (tồn đầu) các giá trị n1, n2, …, nk, tiếp theo là những dòng nhập – tồn trong khoảng NGAY1 – NGAY2.
Nói cách khác ta coi lượng hàng hóa trong kho ở ngày (NGAY1 – 1) là lượng đầu kỳ cho khoảng báo cáo (NGAY1 – NGAY2), tức ta coi n1, n2, …, nk là lượng hàng hóa mà ta "nhập" từ "kho cũ" sang "kho mới". Và mỗi dòng trong k dòng kể trên có trong cột J ký tự N. Làm gì có chuyện vừa xuất vừa nhập? Ngày 31-07-2005 có bao nhiêu thì ta Nhập (N) vào "kho mới" (trong tưởng tượng thôi) cho báo cáo mới. Thế thôi.
2. Tôi đọc thấy những câu như "mã phát sinh", hay đọc thấy là phải kiểm tra với từng dòng xem nó có thỏa NGAY1 <= ngày <= NGAY2 hay không. Tôi đọc mà không hiểu. Vì theo tôi đã là sổ sách thì có lẽ những mục ghi trong đó là theo thứ tự thời gian. Không có chuyện dòng 100 ứng với ngày 31-07-2005. Cũng không có chuyện ở dòng 100 có ngày 03-09-2005, dòng 102 có ngày 09-09-2005, còn dòng 101 có ngày 01-01-2007 được. Nói cách khác thì nếu tôi hiểu thì các dòng Nhập – Xuất được ghi theo thứ tư thời gian (Làm gì có chuyện thủ kho ngày 08-09-2005 đi làm và ở chỗ làm ghi vào sổ: Ngày 01-01-2007 nhập Honda 5 xe …). Và nếu thế thì chỉ cần đi từ dòng đầu cho tới ngày >= NGAY1 thì tính những dòng < NGAY1 ta có tồn đầu. Đồng thời có "dòng đầu" của kỳ báo cáo. Đi từ dòng cuối cùng đi lên loại tất cả các dòng trống (nếu có) và các dòng có ngày > NGAY2 ta sẽ có được "dòng cuối". Lúc này thì ta có thể không theo qui tắc
Tức ta sẽ không để ý tới cột B nữa mà Nhập – Xuất ta sẽ xác định dựa vào cột J (N – X)
3. Theo tôi sổ sách được ghi theo trình tự thời gian, và theo trình tự các mã xuất hiện và mất đi. Ví dụ ngày xyz có hàng Nhập với mã chưa có thì lập tức mã đó được ghi vào sổ. Không có chuyện có mã trong sheet KHO mà lại không có trong sheet Danh Mục. Nếu sổ sách nghiêm chỉnh thì khi mặt hàng nào đó không còn thì mã tương ứng sẽ bị xóa. Khi đó thì mỗi mã trong sheet KHO sẽ có trong sheet Danh Muc, và ngược lại. Có đk này thì không phải xét từng mả một trong vòng lặp 65000. Vì lúc đó ta "nhắm mắt" mà cho Danh Mục vào Collection, đít thon, mảng.
Vodoi2x đã chuyển giúp code từ Dic sang collection. Tốc độ đã tăng và đã giữ nguyên thuật toán. Xin cám ơn vodoi2x.
Tôi cũng thử tự mình chuyển sang collection và test thì thấy như sau:
[URL="https://s1329.photobucket.com/user/ptm041261/media/Excel01/compare-collecttion_zps64bd6572.jpg.html"%5D
Sub LapSo()
Application.ScreenUpdating = False
Dim ListArr(), sArrID(), TmpArr(), RArr(), sArrQty(), sArrAmt(), sArrDocType()
Dim sArrDate(), ColDM As Collection
Dim EndR As Long, ListCt As Long
Dim DataCt As Long, ListEndR As Long, Date1 As Long, Date2 As Long
Dim i As Long, j As Long, k As Long, Check As Double
ListEndR = Sheet1..End(xlUp).Row
''Lay danh muc vao mang
ListArr = Sheet1.Range("A4:C" & ListEndR).Value2
ListCt = UBound(ListArr, 1)
Set ColDM = New Collection
''Nap mang danh muc vao Collection
For i = 1 To ListCt
ColDM.Add Item:=i, Key:=ListArr(i, 1)
Next
''Xác dinh dong cuoi cua data va nap vao mang
With Sheet20
EndR = .Cells(65536, 1).End(xlUp).Row
sArrID = .Range("G4:G" & EndR).Value2
sArrQty = .Range("H4:H" & EndR).Value2
sArrAmt = .Range("K4:K" & EndR).Value2
sArrDocType = .Range("J4:J" & EndR).Value2
sArrDate = .Range("B4:B" & EndR).Value2
End With
DataCt = EndR – 3
'' gan gia tri cho bien
ReDim TmpArr(1 To ListCt, 1 To 7)
Date1 = Sheet3.
Date2 = Sheet3.
''Duyet mang Data
For i = 1 To DataCt
j = ColDM.Item(sArrID(i, 1))
''Neu ngay < ngay bat dau, tinh 2 cot ton dau
If sArrDate(i, 1) < Date1 Then
If sArrDocType(i, 1) = "N" Then
''Cong nhap
TmpArr(j, 2) = TmpArr(j, 2) + sArrQty(i, 1)
TmpArr(j, 3) = TmpArr(j, 3) + sArrAmt(i, 1)
Else
''Tru xuat
TmpArr(j, 2) = TmpArr(j, 2) – sArrQty(i, 1)
TmpArr(j, 3) = TmpArr(j, 3) – sArrAmt(i, 1)
End If
''Neu ngay trong khoang bao cao
ElseIf sArrDate(i, 1) <= Date2 Then
''Neu loai chung tu là N, tinh 2 cot Nhap
If sArrDocType(i, 1) = "N" Then
TmpArr(j, 4) = TmpArr(j, 4) + sArrQty(i, 1)
TmpArr(j, 5) = TmpArr(j, 5) + sArrAmt(i, 1)
''Neu loai chung tu la X, tinh 2 cot xuat
Else
TmpArr(j, 6) = TmpArr(j, 6) + sArrQty(i, 1)
TmpArr(j, 7) = TmpArr(j, 7) + sArrAmt(i, 1)
End If
End If
''Ket thuc vong lap, Mang KQ tam co 12 dong
Next
''Khai bao Mang KQua
ReDim RArr(1 To ListCt, 1 To 12)
k = 0
''Duyet mang KQ tam
For i = 1 To ListCt
''Kiem tra dong co du lieu
Check = TmpArr(i, 2) + TmpArr(i, 3) + TmpArr(i, 4) + _
TmpArr(i, 5) + TmpArr(i, 6) + TmpArr(i, 7)
''Neu co dulieu, them vao mang KQua
If Check > 0 Then
k = k + 1
''4 cot thong so Hang hoa
RArr(k, 1) = k
If i <= ListCt Then
RArr(k, 2) = ListArr(i, 1)
RArr(k, 3) = ListArr(i, 2)
RArr(k, 4) = ListArr(i, 3)
Else
RArr(k, 2) = TmpArr(i, 1)
End If
''6 cot Ton, nhap, xuat
For j = 5 To 10
RArr(k, j) = TmpArr(i, j – 3)
Next
''2 cot Ton cuoi
RArr(k, 11) = RArr(k, 5) + RArr(k, 7) – RArr(k, 9)
RArr(k, 12) = RArr(k, 6) + RArr(k, 8) – RArr(k, 10)
End If
Next
'' Gan ket qua xuong sau khi xoa
Sheet26..Resize(12, 12).ClearContents
Sheet26..Resize(k, 12) = RArr
Set ColDM = Nothing
Application.ScreenUpdating = True
End Sub
Tôi phân tích thế này:
1. Collection nhanh hơn Dic trong bài toán này.
2. Cùng là Dic, nhưng của tôi nhanh hơn của vodoi2x vì tôi dùng 1 Dic, Vodoi dùng 2 Dic
3. Cùng là Collection, nhưng code Vodoi2x nhanh hơn, vì tôi dùng mảng tạm, thêm 1 vòng lặp kiểm tra mới đưa ra kết quả. Lý do thì như đã nói, tôi viết code theo thói quen nhận định rằng có thể có mặt hàng không nhập xuất nhưng có tồn đầu
4. Code tôi tự chuyển sang Collection nhanh hơn code vodoi2x chuyển giúp vì vodoi2x có kiểm tra dữ liệu nếu có mặt hàng không có trong danh mục thì add vào, còn tôi thì không kiểm tra. Lý do thì cũng đã nói: Dữ liệu phải được kiểm tra từ lúc nhập, nếu không có mã hàng thì không cho nhập xuất.
Nói thêm:
Do đầu bài không cho sửa Data, trong khi Data không được sort theo thứ tự ngày tháng (do giả lập bằng cách copy và sửa chút đỉnh), nên tôi viết theo thuật toán thế này.
Nếu Dữ liệu thực, nhập hàng ngày theo thứ tự thời gian đúng chuẩn dữ liệu hơn nữa, và có đầu kỳ <> 0 chung với danh mục (hoặc 1 bảng đầu kỳ riêng), tôi sẽ viết kiểu khác:
– Tạo 2 name động cho dữ liệu: 1 cho dữ liệu trước ngày bắt đầu, và 1 cho dữ liệu trong khoảng báo cáo.
– Tạo 2 mảng tương ứng 2 name trên dùng làm mảng nguồn.
– Nếu số dư đầu kỳ chung bảng với danh mục, dùng 1 Dic, nếu số dư đầu kỳ khác bảng, dùng 2 Dic.
– nếu danh mục ít, không có Dic nào được nạp từ dữ liệu, Dic chỉ nạp 1 lần và chỉ dùng để truy xuất.
– nếu danh mục dài, 1 Dic chứa số dư và 1 Dic lấy từ dữ liệu, chấp nhận test If Exist
Nghĩa nên nhớ rằng Tuân không cho sort. Và nên đọc để biết rằng trong 1 bài nào đó Tuân nói dữ liệu dùng để ra đề đã được giả lập bằng cách copy nên nó thế, chứ không phải ban đầu nó thế. Phải chấp nhận để thi.
Dữ liệu gốc không ai sort tùm lum cả. Đúng tiêu chuẩn là nhập hàng ngày theo thứ tự ngày tháng. Muốn xem kiểu gì thì mở báo cáo đó lên xem: Filter, group, … sẵn thành báo cáo đẹp đẽ luôn.
Còn vụ "thanh lý" thì tôi cũng nói rồi. Sau 1 năm người ta xóa bỏ hoặc đánh dấu những mặt hàng không còn sử dụng ra khỏi danh mục. Đó là dữ liệu "rác". Cũng chính vì thế nên người ta không nhập sẵn 10.000 mã chưa dùng đến vào danh mục, vì không xài tức là rác. Khi được chào hàng hoặc thông báo mã hàng mới, người ta sẽ lưu ở chỗ khác theo dạng catalogue để tham khảo, khi nào thực sự dùng mới add, dùng bao nhiêu, add bấy nhiêu.
Nghĩa còn hiểu sai về FIFO. Dù dùng FIFO hay dùng bất kỳ phương pháp tính giá nào, thì vẫn có những mặt hàng dùng mãi mãi, và cũng có mặt hàng chỉ dùng 1 thời gian rồi không dùng nữa. người ta loại mặt hàng không dùng, chứ không loại mặt hàng cũ nhất.
Anh đang thắc mắc phần tồn đầu nên em giải thích thêm.
– Trong thực tế kiểm kho, số tồn đầu kỳ cụ thể luôn là nhập (LOAI_PHIEU="N"). Nói tồn đầu thì chỉ là tồn đầu và nhập không có xuất – Ý anh nói đúng.
– Với người làm nghiệp vụ kho. Báo cáo nộp định kỳ, ví dụ nộp cuối năm thì tồn đầu sẽ cố định ngày < 1/1/xx, và chắc chắn chỉ có loại nghiệp vụ là nhập không có xuất.
– Thực tế trong quá trình quản lý kho của người làm nghiệp vụ, cần theo dõi diễn biến của hàng hoá vật tư kho theo các khoảng thời gian bất kỳ mà họ muốn. NGAY1-NGAY2 là tuỳ ý. Không cứ NGAY1 phải là 1/1/xx. Vì thế nói tới đầu kỳ là nói "Tồn đầu kỳ" chứ không nói "Nhập đầu kỳ". "Nhập đầu kỳ" thì có thể chỉ cần xét phếu nhập, nhưng "Tồn đầu kỳ" (mà ngày đầu có thể bất kỳ) thì phải tính Nhập – Xuất (với ngày < NGAY1).
Không biết ở các đơn vị mọi người thế nào nhưng ở nhiều đơn vị mà công ty tôi hỗ trợ nghiệp vụ kho cho bộ phận quản lý kho họ đều đồng ý vơí cách làm làm báo cáo theo công thức như vậy.
Về dữ liệu đề ra. Như tôi đã nói, chỉ làm dữ liệu tính toán chứ nó không phải là thật. Cấu trúc bảng KHO được đơn giản hoá để làm báo cáo mà thôi. Dữ liệu được copy paste nhiều lần để cho nó có nhiều, vì thế thứ tự thời gian không theo và số phiếu trùng,…. Mà với đặc điểm của Excel thì thứ tự thời gian hay các cột khác cũng không theo được trình tự sắp xếp. Ví dụ sửa, xoá chứng từ mà người lập trình không muốn lệnh Insert, một lúc nào đó người ta đem Sort lại…..
Có lẽ chúng ta có thể trao đổi tiếp về các vấn đề liên quan đến code còn bàn luận về tính thực tế nghiệp vụ mỗi đơn vị ta tạm dừng lại vì khó để đi đến thống nhất cuối cùng và làm loãng topic.
Anh Nghĩa làm thủ tục test chưa đầy đủ vì để so sánh tốc độ của của Array, Dictionary, Collection phải tính thời gian khi nạp dữ liệu vào nó (code ở bài cụ thể ta cũng phải nạp mà) và cả khi kiểm tra Exists có tồn tại và không tồn tại. Hàm ItemExists sẽ tìm nhanh trong trường hợp đã nêu ở bài trên.
Nếu có thời gian anh Nghĩa dùng Dictionary và Collection thay vào file của em ở bài tổng hợp xem khác nhau không?
Đã có bài tổng hợp kết quả test và các file có mã nguồn của các tác giả. Các thành viên xem bài [URL='https://www.giaiphapexcel.com/forum/showthread.php?89723-THI-T%E1%BA%A1o-s%E1%BB%95-TH-NXT-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng/page18'%5D#175 để download.
Qua đề topic dạng "THI" kiểu này cá nhân mình nhìn nhận chủ quan thì thấy nó bổ ích cho mọi người. Không biết nhiều người khác có thực sự thấy như vậy và hứng thú không? Nếu được thì sắp tới chúng ta lại có thêm một đề tài mới. Bài toán cũng vẫn sẽ là có vẻ đơn giản nhưng làm ta lại học thêm được một số cái mới.
Vâng. Là nói vậy chứ chưa khép mà anh. Em cung mong các thành viên tiếp tục gửi thêm giải pháp để cùng học hỏi.
Giải pháp cập nhập cải tiến sử dụng Dictionary
Ở đây vẫn sử dụng giải pháp 2 dictionary (đảm bảo mọi mặt hàng phát sinh mới ở kho mà không có trong danh mục – dĩ nhiên điều này chỉ có tốt lên, và phòng trường hợp nhập mã sai vv.vvv)
So với giải pháp 2 Dictionary cũ (xem file tại [URL="https://www.giaiphapexcel.com/forum/showthread.php?89723-THI-T%E1%BA%A1o-s%E1%BB%95-TH-NXT-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng&p=561089#post561089"%5Dbài 175) của chính tôi, thì giải pháp mới ở sub code sau đã giảm được khoảng 100mili giây (từ khoảng 330mls xuống 230mls tại lap của tôi), tiến tới chỉ kém giải pháp collection hiện đang có thời gian nhỏ nhất (xem tại [URL="https://www.giaiphapexcel.com/forum/showthread.php?89723-THI-T%E1%BA%A1o-s%E1%BB%95-TH-NXT-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-65-532-d%C3%B2ng&p=561089#post561089"%5Dbài 175) là (chậm hơn) khoảng10% (cụ thể tại lap tôi thì giải pháp Collection là khoảng 208mls, còn giải pháp 2 dictionary cải tiên mới này là khoảng 230mls – theo tôi dự thì trên máy tính test của Nguyễn Duy Tuân chắc khoảng 275mls)
Code của sub lapso với giải pháp cải tiến sử dụng 2 dictionary như sau
Private Sub LapSo()
''Code lap so th nxt
''Su Dung Dictionary
''nguoi Lap: vodoi2x
''email: vodoi909090@yahoo.com
'' ngay cap nhap: 19.02.2014
Application.ScreenUpdating = False
Dim DicH, arrRes(), soDM()
Dim Day1 As Long, Day2 As Long, i As Long, k As Long, p As Long
Dim DicDM, Ngay(), MaSoHH(), SoLG(), LoaiPhieu(), ThanhTien()
''Nhap du lieu ngay tinh toan
Day1 = Range("NGAY1").Value2
Day2 = Range("NGAY2").Value2
''Nhap du lieu tu KHO
With Range("KHO").Resize(Range("KHO").Rows.Count – 1, 1).Offset(1, 1)
Ngay = .Value2
MaSoHH = .Offset(, 5).Value2
SoLG = .Offset(, 6).Value2
LoaiPhieu = .Offset(, 8).Value2
ThanhTien = .Offset(, 9).Value2
End With
''nhap DL tu vung DM VLSPHH
soDM = Range("DMVLSPHH").Resize(Range("DMVLSPHH").Rows.Count – 1, 3).Offset(1).Value2
p = UBound(soDM)
ReDim arrRes(1 To p + 10, 1 To 12) ''Mang chua ket qua gom 12 cot, 10 so ma du phong chua co trong DM VLSPHH
Set DicH = CreateObject("Scripting.Dictionary") '' khoi tao Dictionary DicH dung de giu vi tri cua 1 MaHH trong mang arrRes
k = 0
For i = 1 To UBound(Ngay) ''Duyet tung dong chung tu cua Kho de xet ngay
If Ngay(i, 1) <= Day2 Then
p = DicH.Item(MaSoHH(i, 1))
If p = 0 Then ''Truong hop CHUA CO MaHH trong Dictionary DicH, nen ta cong vao, va gan gia tri vao arrRes
k = k + 1
DicH(MaSoHH(i, 1)) = k
arrRes(k, 2) = MaSoHH(i, 1)
p = k
End If '' If p = 0 Then
If Ngay(i, 1) < Day1 Then ''ton dau ky
If LoaiPhieu(i, 1) Like "N" Then
arrRes(p, 5) = arrRes(p, 5) + SoLG(i, 1)
arrRes(p, 6) = arrRes(p, 6) + ThanhTien(i, 1)
Else
arrRes(p, 5) = arrRes(p, 5) – SoLG(i, 1)
arrRes(p, 6) = arrRes(p, 6) – ThanhTien(i, 1)
End If
Else ''trong ky
If LoaiPhieu(i, 1) Like "N" Then
arrRes(p, 7) = arrRes(p, 7) + SoLG(i, 1)
arrRes(p, 8) = arrRes(p, 8) + ThanhTien(i, 1)
Else
arrRes(p, 9) = arrRes(p, 9) + SoLG(i, 1)
arrRes(p, 10) = arrRes(p, 10) + ThanhTien(i, 1)
End If
End If
End If ''——————————————————– If Ngay(i, 1) <= Day2 Then
Next i
''tao DicDM de luu tru vi tri cua Ten hang hoa & Don vi tinh
Set DicDM = CreateObject("Scripting.Dictionary")
For i = 1 To UBound(soDM)
DicDM(soDM(i, 1)) = i
Next i
''tinh toan TON CUOI KY & tinh Tong GrandTotal cua cac cot Thanh tien: TonDK, NHAP, XUAT, & TON CUOI
''bang cach duyet cac dong cua arrRes
p = k + 1
Dim j As Long
For i = 1 To k
arrRes(i, 1) = i
j = DicDM(arrRes(i, 2))
If j > 0 Then
arrRes(i, 3) = soDM(j, 2)
arrRes(i, 4) = soDM(j, 3)
End If
arrRes(i, 11) = arrRes(i, 5) + arrRes(i, 7) – arrRes(i, 9)
arrRes(i, 12) = arrRes(i, 6) + arrRes(i, 8) – arrRes(i, 10)
arrRes(p, 6) = arrRes(p, 6) + arrRes(i, 6)
arrRes(p, 8) = arrRes(p, 8) + arrRes(i, 8)
arrRes(p, 10) = arrRes(p, 10) + arrRes(i, 10)
Next i
arrRes(p, 12) = arrRes(p, 6) + arrRes(p, 8) – arrRes(p, 10)
''Xuat ket qua ra Sheet
With Sheet26.Range("b12")
.Resize(13, 12).ClearContents
If k Then .Resize(p, 12) = arrRes
End With
End Sub
Vậy các bạn thử tìm ra đâu là thủ thuật cải tiến trong sub này? tìm ra sẽ thấy giải pháp rút ngắn thời gian tính với dictionary, thật đơn giản, và sẽ phát hiện ra cả hạn chế cuả thủ thuật này (?)
Với trình độ cùn của tôi thì chỉ cần mấy trang đầu là đủ học hỏi nhiều lắm rồi. Chỗ còn lại quá đặc thù cho nên tôi chả học được gì cả.
Muốn học xa hơn thì nới rộng đề tài, hoặc lập một kỳ thi khác để giải những bài toán như thế này:
[URL='https://www.giaiphapexcel.com/forum/showthread.php?89942-b%E1%BA%A3ng-t%E1%BB%95ng-h%E1%BB%A3p-nh%E1%BA%ADp-xu%E1%BA%A5t-t%E1%BB%93n'%5Dbảng tổng hợp nhập xuất tồn
Đề bài: viết code sao cho chỉ cần chỉnh sửa 1 vài chỗ nhất định nào đó sẽ giải quyết được hết những loại bài toán như trên. Điểm được chấm theo: ví dụ số dòng cần sửa,…
Những chỗ hardcode như có thể châm chước bằng cách tính "Replace All" như 2 dòng (1 dòng là code cũ, 1 dòng là code mới)
Những chô cần chỉnh sửa phải được ghi chú. Nếu không ghi ra thì coi như chạy sai.
Code nào không có ghi chú những chỗ cần chỉnh sửa thì coi như không cần chỉnh sửa.
Thử với 1 triệu dòng, có khi đây là giải pháp tốt hơn! Khả năng chậm là do thời gian kết nối.
HLMT có chừa khoảng trắng cuối dòng mà VetMini? Vã lại, khi có thói quen trước hay sau, thì người ta sẽ không quên quá trình mình thực hiện được đâu.
Chưa thử nhưng hình như nó có gì đó khộng ổn.
Cải tiến lại chút khi chọn khoảng thời gian, chỉ lấy những mặt hàng trong khoảng thời gian thoả điều kiện, tốc độ tuỳ thuộc vào dữ liệu trong khoảng thời gian đó nhiều hay ít. Tuy nhiên tốc độ cũng chưa được ưng ý. Nhờ các bạn cải tiến thêm.
Tôi đã biết vấn đề này, tuy nhiên theo yêu cầu đề tài cũng như "tăng tốc" khi chạy code nên tôi đã không dùng nó. Còn nếu như dùng nó thì tốc độ cũng sẽ bị giảm đi đó.
Ở đây nó sẽ tự sắp xếp theo thứ tự tăng dần, theo bạn thì phải sắp xếp như thế nào mới hợp lý.
–> Ta có thể tạo thêm 1 sub kết nối dữ liệu bên ngoài, khi cần kết nối lại ta có thể kết nối lại. Dùng cách này sẽ bỏ qua kết nối khi mỗi lần chạy code lần sau.
Như em đã nhiều lần đưa ra ý kiến ở trên, với việc tổng hợp = ADO trên cùng file thì tốc độ sẽ thua, nhưng nếu file data từ 1 file khác thì chưa chắc đã qua ADO.
Name theo file anh Tuân gửi đó anh2572
Em cũng sử dụng O2010 đây mà anh.
SQL là ngôn ngữ để làm việc trên CSDL Liên Hệ. Từ bản thân, CSDL LH vẫn có vấn đề về tốc độ.
Khi dữ liệu được xếp chuẩn theo hàng cột để có thể sử dụng lý thuyết CSDL LH thì có những cái lợi sau đây:
– Có thể thiết lập từ điển dữ liệu (data dictionary), tiếng tổng quát hơn gọi là hạ tầng dữ liệu (metadata)
– Có thể tách biệt rõ 3 tầng (layers) truy xuất (data access), trình bày (presentation), và quy trình vận hành (business model)
Nếu không chú trọng các lợi điểm trên thì sử dụng bảng tính theo kiểu CSDL LH chỉ tổ chạy chậm mà thôi. Khi ấy ADO chỉ có thể coi như một thủ thuật dùng để đọc/viết file mà không cần mở.
Kết luận như thế là hơi sớm.
Muốn biết có gọn hay không thì phải export cái sheet data sang Access và dùng câu lệnh chạy thử. Kế đó chỉnh sửa cho đến lúc gọn ghẽ dễ trông nhất. Đôi khi phải hy sinh ngắn gọn để cho nó chạy nhanh. @HTMT: Access có công cụ đo hiệu quả câu lệnh không nhỉ?
Theo em cũng nên tính cả thời gian kết nối. Nếu file data là 1 file khác file chứa code thì tốc độ kết nối cũng như truy vấn sẽ nhanh hơn so với kết nối và truy vấn với data cùng 1 file?
Nhờ anh chị em test dùm code sau xem tốc độ có được cải thiện thêm chút nào không? Chưa vừa ý lắm.
Theo kỹ thuật tổng hợp thì bộ máy SQL sẽ dủng bảng index (đại khái gọi vậy, chứ là btree hay bảng băm, bảng gì thì là chuyện nội bộ của nó) để lọc GROUP. Và vì vậy kết quả sẽ ra theo thứ tự mã tăng dần (đây là hậu quả của kỹ thuật, chứ tiêu chuẩn không bắt buộc).
Khi copy recordset trở lại vào sheet báo cáo thì cũng theo thứ tự này. Như vậy mẫu báo cáo sẽ có khả băng bị xáo trộn.
Điểm rắc rối là theo chỗ tôi biết thì Access không có hàm đặt số thứ tự cho record.
(@HLMT: xin lỗi tôi chỉ giải thích phần trên rườm rà cho những người mới học SQL)
Nếu đề tài bắt phải giữ thứ tự của mẫu thì bắt buộc phải dựa vào cột A của bảng kết quả để sắp xếp. Cột này chứa số thứ tự của mã hàng.
1. Tồn đầu kỳ tính thiếu: Dòng lệnh filter row chỉ tính nhập không tính xuất. Kết quả may là DateFrom là 1/8 chứ nếu 1/9 hay lớn hơn là sai. Từ ngày chuyển số tồn vào 31/7 đến ngày DateFrom chả lẽ chỉ nhập mà không xuất
2577
2. Nhiều query quá: 3 query sau gộp thành một
2576
3. Có thể gộp 4 dòng lệnh replace thành một
4. Đặt tên column dỏm: Query HH_Xuat mà tên cột là nhập
2575
Sửa xong nhưng lòi ra cái sai chết người (hên là đúng do dữ liệu ẹ quá)
1. Cái sai chết người:
– HHTonDK chỉ join 2 query nhập và xuất, không join bảng danh mục, nên không thấy những mặt hàng không nhập xuất
– 2 query nhập đầu kỳ và xuất trong kỳ lại join kiểu left outer, thì chỉ những mặt hàng có nhập có xuất và có nhập không xuất mới được lấy, mặt hàng không nhập có xuất không được lấy => đầu kỳ sai
2. Dữ liệu ẹ:
– suốt 1 năm rưỡi mà chỉ thấy nhập xuất ở 4 tháng, trong đó 3 tháng chỉ có mỗi tháng 1 ngày. Không test theo kiểu nối đuôi được (là test 1 khoảng thời gian bất kỳ, lấy cuối kỳ, test tiếp khoảng thời gian liền kề đối chiếu đầu kỳ này và cuối kỳ trước
– Suốt từ 1/8/2005 đến 31/7/2006 chỉ mua bán 4 mặt hàng, nguyên tháng 8/ 2006 thêm được 2 mặt hàng nữa. Đây chính là lý do dù có left join, right join, full join, inner join kiểu gì trước ngày 1 tháng 8/2006 cũng không sai kiểu thiếu mặt hàng.
Tỷ tỷ không biết rành VBA đâu, hỏi mấy lão cao thủ cao nhơn á. Có điều trong khi test thấy chậm lắm, đừng đo cho xấu hổ
Dữ liệu chuẩn đây nhé, 1 triệu dòng ngày tháng đầy đủ đã sắp xếp, mặt hàng 13 ngàn mua bán đều đặn
[URL='www.giaiphapexcel.com/diendan/threads/t%E1%BA%A1o-s%E1%BB%95-th-nxt-v%E1%BB%9Bi-t%E1%BB%91c-%C4%91%E1%BB%99-nhanh-nh%E1%BA%A5t-d%E1%BB%AF-li%E1%BB%87u-h%C6%A1n-1-tri%C3%AA%CC%A3u-d%C3%B2ng.90062/']Dữ liệu mẫu 1 triệu dòng