So sánh 3 cách lấy dữ liệu từ 1 file đang đóng

Liên hệ QC

Maika8008

Thành viên gạo cội
Tham gia
12/6/20
Bài viết
4,763
Được thích
5,719
Donate (Momo)
Donate
Giới tính
Nam
Với Excel chúng ta thường có nhu cầu lọc lựa, lấy dữ liệu từ 1 file đang trạng thái đóng đã biết trước đường dẫn và địa chỉ tham chiếu vùng dữ liệu, nhưng lại không muốn tự tay mở nó ra để chép 1 cách thủ công qua file đang làm. Với VBA, chúng ta có 3 cách để tự động hóa khâu lấy dữ liệu này.

Cách 1: Mở trực tiếp file lên, lấy dữ liệu xong đóng lại:
Rich (BB code):
Sub GetDataByOpenFile()
Dim Wb As Workbook, WbS As Workbook
Dim sFullName$, tmr#

tmr = Timer()
Application.ScreenUpdating = False
sFullName = "D:\GoogleDrive2\CaNhan\VBA\MapVN.xlsx"  'Duong dan file du lieu
Set Wb = ThisWorkbook
Set WbS = Workbooks.Open(sFullName)
WbS.Sheets("VNxy").Range("A1:D100").Copy Wb.Sheets("KQ").Range("A1")
WbS.Close False
Application.ScreenUpdating = True
Msgbox Timer() – tmr  'Thoi gian thuc hien
End Sub
- Ưu điểm của cách 1 là trực quan, ta có thể tạm ngừng lệnh, chạy từng bước để xem kết quả trung gian. Dữ liệu có thế nào chép sang thế ấy hoặc có thể tùy ý chép riêng định dạng, công thức, giá trị…

- Nhược điểm cách 1 là thời gian thực thi khá chậm, mất khoảng 1,4 giây cho việc mở file, chép dữ liệu, đóng file. (Thời gian ở đây là trong điều kiện thử nghiệm cụ thể của tác giả, chỉ để so sánh tốc độ thực hiện các cách với nhau. Thời gian đó sẽ khác đi khi dùng ở máy khác, dùng 1 file nguồn khác…)

Cách 2: Mở file bằng ADODB:
Rich (BB code):
Sub GetDataByADODB()
Dim Rec As Object, rs As Object
Dim sFullName$, iCol&, tmr#

tmr = Timer()
sFullName = "D:\GoogleDrive2\CaNhan\VBA\MapVN.xlsx"   'Duong dan file du lieu
Application.ScreenUpdating = False
Set Rec = CreateObject("ADODB.Connection")
With Rec
        .Provider = "Microsoft.ACE.OLEDB.12.0"
        .ConnectionString = "Data Source=" & sFullName & ";" & _
              "Extended Properties=""Excel 12.0 Xml;HDR=YES"";"
        .Open
End With
        Set rs = Rec.Execute("Select * From [VNxy$A1:D100]")
        Sheets("KQ").Range("A2").CopyFromRecordset rs
        For iCol = 0 To rs.Fields.Count - 1   'Chep tieu de cọt
                Sheets("KQ").Cells(1, iCol + 1).Value = rs.Fields(iCol).Name
        Next
        Set rs = Nothing
        Msgbox Timer() – tmr  'Thoi gian thuc hien
        Application.ScreenUpdating = True
End Sub
- Ưu điểm của cách 2 là thực thi nhanh, chỉ mất cỡ 0,2 giây, và người dùng không nhận biết được file đóng mở.

- Nhược điểm cách 2 là:

+ Trong đa số trường hợp phải mất công khắc phục việc chạy lệnh đối với tiêu đề có dấu tiếng Việt (Có cách khác không bị ảnh hưởng bởi tiêu đề tiếng Việt. Nhưng vấn đề này sẽ được trình bày ở 1 bài khác, có sự so sánh việc sử dụng của 2 cách).

+ Những ai chưa rành cú pháp SQL và lồng các biến vào câu lệnh SQL có thể gặp trở ngại khi điều kiện truy vấn phức tạp.

+ Gặp dữ liệu có dấu phân cách thập phân với các máy đã được dịnh dạng trong Control Panel là dấu phẩy thì sẽ có rắc rối với kết quả chép ra. Thay vì đúng là 144,12 thì kết quả chép ra ở đây là 14412,00.

Để khắc phục nhược điểm này thì phải thêm mã lệnh để chuyển định dạng dấu phẩy thập phân trong Control Panel sang dấu chấm trước khi chạy truy vấn rồi chuyển lại dấu phẩy ngay sau truy vấn.

Khai báo và đặt hàm SetLocalSetting trên đầu Module:
Rich (BB code):
#If VBA7 Then

 Private Declare PtrSafe Function SetLocaleInfo _

 Lib "kernel32" Alias "SetLocaleInfoA" ( _

 ByVal Locale As LongPtr, _

 ByVal LCType As LongPtr, _

 ByVal lpLCData As String) As Boolean

 

 Private Declare PtrSafe Function GetUserDefaultLCID% Lib "kernel32" ()

#Else

 Private Declare Function SetLocaleInfo _

 Lib "kernel32" Alias "SetLocaleInfoA" ( _

 ByVal Locale As Long, _

 ByVal LCType As Long, _

 ByVal lpLCData As String) As Boolean

 

 Private Declare Function GetUserDefaultLCID% Lib "kernel32" ()

#End If

 

 Private Const LOCALE_SDECIMAL = &HE

 

 Private Function SetLocalSetting(LC_CONST As Long, Setting As String) As Boolean

 Call SetLocaleInfo(GetUserDefaultLCID(), LC_CONST, Setting)

 End Function
Với Sub GetDataByADODB() ở trên, thêm lệnh này trước lệnh truy vấn SQL:

Call SetLocalSetting(LOCALE_SDECIMAL, ".")

Và thêm lệnh này ngay sau khi chạy xong truy vấn SQL:

Call SetLocalSetting(LOCALE_SDECIMAL, ",")



Cách 3: Dùng Macro 4. Cách này tôi dùng code từ nguồn:

https://www.giaiphapexcel.com/diendan/threads/dùng-macro-4-để-lấy-dữ-liệu-từ-1-file-đang-đóng.39312/
Rich (BB code):
Sub GetDataByMacro4()
Dim tmr#
tmr = Timer()
  Dim sFile As String, sSheet As String, sAddr As String
  sFile = "D:\GoogleDrive2\CaNhan\VBA\MapVN.xlsx"
  sSheet = "VNxy"
  sAddr = "A1:D100"  
  Sheets("KQ").Range("A1:D100") = GetData(sFile, sSheet, sAddr)
'Kích thuoc phai bang sAddr
  Msgbox Timer() – tmr  'Thoi gian thuc hien
End Sub

Function GetData(sFile As String, sSheet As String, sAddr As String)
      Dim pLink As String, iR As Long, iC As Long, Arr
      If Len(Dir(sFile)) Then
            Arr = Range(sAddr)
            pLink = "'" & Replace(sFile, Dir(sFile), "[" & Dir(sFile) & "]") & sSheet & "'!"
            For iR = 1 To Range(sAddr).Rows.Count
                  For iC = 1 To Range(sAddr).Columns.Count
                        Arr(iR, iC) = ExecuteExcel4Macro(pLink & Range(sAddr). _
Cells(iR, iC).Address(, , 2))
                  Next iC
            Next iR
            GetData = Arr
      End If
End Function
Với cách này, dù dữ liệu nguồn sAddr = "A1:D100" nhưng khi muốn hiển thị kết quả ít hơn thì bạn giảm kích thước vùng kết quả chỗ Sheets("KQ").Range("A1:D100"). Nếu tăng kích thước vùng kết quả thì sẽ bị #N/A ở các ô thừa.

- Thực tình thì không thấy ưu điểm nào của cách này ngoài việc nó lấy đúng số liệu nguồn như cách 1. Còn nhược điểm là quá chậm, phải đến tận 5 giây (gấp hơn 3 lần cách 1) thì mới lấy được dữ liệu cùng cỡ với các cách trên. Dòng tiêu đề sẽ hiển thị là 0 với tiêu đề nào chừa trống.

P/S: Tôi đính kèm file dữ liệu mà tôi đã dùng để test tốc độ thực thi code để các bạn có cùng 1 mẫu thử.
Trong bài có chỗ nào sai, sót thì các bác "gội" rồi mới "cạo" nhẹ nhàng góp ý giúp.
 

File đính kèm

  • MapVN.xlsx
    660.4 KB · Đọc: 57
Theo bài #1 thì chủ thớt muốn so sánh trong Excel không thôi nên tôi nghĩ chỉ dùng CSDL là excel chứ đừng dùng dạng khác (CSV).
Đây là cái data 1.1 triệu dòng, 26 cột Excel (103M) để các bạn test nhé. nhiều quá chạy mệt máy.
:)
Link: https://www.mediafire.com/file/62bdp3w1ml5cpsv/1.1Mil_Records.xlsx/file
Kết quả tốc độ Open là 62 giây còn ADODB là 485 giây (lâu quá) --=0

Tiện thể: database đó có 14 cột hà!
 
Upvote 0
Khoảng 30 giây bạn à. Nhanh thiệt! Như vậy có lẽ Provider Microsoft.ACE.OLEDB.12.0 thiết kế tối ưu cho Access?
Đương nhiên nha vì nó chuyên cho CSDL mà! Nhưng lưu ý, thử lần đầu nó kết nối nó sẽ nhiều thời gian hơn những lần sau.
 
Upvote 0
Code nạp csv như hình đấy bạn, python nạp excel chậm lắm bạn nếu dữ liệu càng lớn vì file excel nó dạng dữ liệu nén, dữ liệu khoảng vài chục ngàn thì nạp excel được còn lớn hơn thì tốt nhất chuyển qua csv , đọc file thì nó cũng phụ thuộc vào cpu và tốc độ ssd bạn ạ nên máy bạn yếu nạp chậm hơn là bình thường bạn ạ
Máy bác đúng là mạnh hơn máy em nhiều. Máy em có corei7 ram 8gb, data của em tận 40 cột. Nên khả năng em nạp python 1tr dòng × 40 cột thời gian mất 9s chắc cũng hợp lý. Có cách nào nạp đuôi xlsx tốc độ nhanh thì tốt, ko lại mất công convert sang csv hơi tốn công.
 
Upvote 0
... Với VBA, chúng ta có 3 cách để tự động hóa khâu lấy dữ liệu này.
Bạn muốn so sánh theo phương diện lý thuyết hay thực hành?
Lý thuyết: thực sự mỗi cách nó làm cái gì?
Thực hành: ở đây (GPE), khi nói thựck hành là người ta muốn nói tốc độ. Tôi không cùng quan niệm nên mạn phép không bàn tới.
 
Upvote 0
Bạn muốn so sánh theo phương diện lý thuyết hay thực hành?
Lý thuyết: thực sự mỗi cách nó làm cái gì?
Thực hành: ở đây (GPE), khi nói thựck hành là người ta muốn nói tốc độ. Tôi không cùng quan niệm nên mạn phép không bàn tới.
Tôi muốn các anh em có kiến thức cơ bản có cái nhìn tổng quan về các phương pháp. Còn với gạo cội thì tôi cần các bác góp ý, bổ sung để mà tôi còn học hỏi.
 
Lần chỉnh sửa cuối:
Upvote 0
Tôi muốn các anh em có kiến thức cơ bản có cái nhìn tổng quan về các phương pháp. Còn với gạo cội thì tôi cần các bác góp ý, bổ sung để mà tôi còn học hỏi.
Cây gạo là một trong những loại cây có thể sống lâu năm và lớn thành đại thụ. Vì vậy mới có thành ngữ gạo cội.
Tôi thích đọc sách cho nên biết nhiều lý thuyết thôi. Gọi đại thụ thì chưa xứng đáng.

Cách 1 dùng chính Excel để mở file, đọc lấy dữ liệu, và đóng lại khi dùng xong. Nó là cách trực tiếp và giản dị nhất.

Cách 3 là cách người ta dùng khi cần lấy dữ liệu ở chính xác một vài chỗ. Nó chính thức là sử dụng liên kết từ file Excel này đến file Excel khác. Tương tự như ở một cell nào đó bạn có công thức =[tên file]'tên sheet'!địa chỉ cell.
Nói cách khác, phương pháp này cũng dùng Excel để đọc file.
Chú: vì mã nguồn bạn đưa ra là người ta biểu diễn cách lấy một mảng dữ liệu, cho nên có ba cái mớ vòng lặp để đọc từng ô và nhét vào mảng.

Cách 2 là cách khá thú vị. ADO là tên viết tắt ActiveX Data Object. Cái tên nói lên rõ rằng nó là một đối tượng thuộc loại ActiveX (gồm COM và OLE). Và đối tượng này chuyên về đọc/xử lý dữ liệu.
Khi đọc file Excel, ADO không dùng Excel mà dùng cách riêng của nó (hỏi mấy cha viết code đối tượng mới biết nó thực sự mở file và đọc hạ tầng cơ sở dữ liệu bằng phương pháp nào). Sau khi kết nói thì ADO coi cái file Excel kia như một CSDL. Để có thể truy vấn và xử lý CSDL dạng Excel, Microsoft đã chọn Access làm cỗ máy dịch và thi hành lệnh SQL. Tất cả mọi lệnh SQL đều phải tuân theo tiêu chuẩn Access Query. Nếu thay file Excel bằng SQL Server thì phải chọn cỗ máy SQL. Và mọi lệnh SQL đều phải theo tiêu chuẩn T-SQL.
Người ta dùng ADO chủ yếu là vì tiện lợi của lệnh SQL, có thể tóm gom tổng kết dữ liệu luôn trước khi đưa ra kết quả. Điển hình là các dạng đối chiếu (join by keys), lọc (where), sắp xếp (sort), nhóm (group).
Về sau này, với Power BI thì ADO mất dần đi lợi thế của SQL. Data Model của Power BI dùng cỗ máy SQL Server (Express) cho nên hiệu quả hơn Access nhiều.
 
Upvote 0
Hãy thứ trên Data.sqlite xem ... thì nó xem Access bé tẹo :D
Bạn nạp dữ liệu đó vô sqlLite3 và test lấy dữ liệu vô Excel bằng thư viện của Delphi xem như thế nào.
Tôi dùng ADO với driver sql3ODBC thì cũng mất tầm 48s cho 1tr records, còn với database Access mất 18s

Screen Shot 2021-09-01 at 22.36.45.png
 
Upvote 0
Cây gạo là một trong những loại cây có thể sống lâu năm và lớn thành đại thụ. Vì vậy mới có thành ngữ gạo cội.
Tôi thích đọc sách cho nên biết nhiều lý thuyết thôi. Gọi đại thụ thì chưa xứng đáng.

Cách 1 dùng chính Excel để mở file, đọc lấy dữ liệu, và đóng lại khi dùng xong. Nó là cách trực tiếp và giản dị nhất.

Cách 3 là cách người ta dùng khi cần lấy dữ liệu ở chính xác một vài chỗ. Nó chính thức là sử dụng liên kết từ file Excel này đến file Excel khác. Tương tự như ở một cell nào đó bạn có công thức =[tên file]'tên sheet'!địa chỉ cell.
Nói cách khác, phương pháp này cũng dùng Excel để đọc file.
Chú: vì mã nguồn bạn đưa ra là người ta biểu diễn cách lấy một mảng dữ liệu, cho nên có ba cái mớ vòng lặp để đọc từng ô và nhét vào mảng.

Cách 2 là cách khá thú vị. ADO là tên viết tắt ActiveX Data Object. Cái tên nói lên rõ rằng nó là một đối tượng thuộc loại ActiveX (gồm COM và OLE). Và đối tượng này chuyên về đọc/xử lý dữ liệu.
Khi đọc file Excel, ADO không dùng Excel mà dùng cách riêng của nó (hỏi mấy cha viết code đối tượng mới biết nó thực sự mở file và đọc hạ tầng cơ sở dữ liệu bằng phương pháp nào). Sau khi kết nói thì ADO coi cái file Excel kia như một CSDL. Để có thể truy vấn và xử lý CSDL dạng Excel, Microsoft đã chọn Access làm cỗ máy dịch và thi hành lệnh SQL. Tất cả mọi lệnh SQL đều phải tuân theo tiêu chuẩn Access Query. Nếu thay file Excel bằng SQL Server thì phải chọn cỗ máy SQL. Và mọi lệnh SQL đều phải theo tiêu chuẩn T-SQL.
Người ta dùng ADO chủ yếu là vì tiện lợi của lệnh SQL, có thể tóm gom tổng kết dữ liệu luôn trước khi đưa ra kết quả. Điển hình là các dạng đối chiếu (join by keys), lọc (where), sắp xếp (sort), nhóm (group).
Về sau này, với Power BI thì ADO mất dần đi lợi thế của SQL. Data Model của Power BI dùng cỗ máy SQL Server (Express) cho nên hiệu quả hơn Access nhiều.
Thực ra tôi cũng định nói thêm về cái "thú vị" của cách 2 nhưng thấy sẽ làm mấy bạn làng nhàng như tôi mất hứng, bởi ngoài VBA lại phải cáng đáng thêm SQL. Thú thực với bác là dù lệnh SQL khá dễ hiểu, dễ dùng nhưng tôi chỉ dùng nó trong 1 số trường hợp đơn giản. Dùng Union cộng với biến ngày tháng là rối như canh hẹ.

Tôi thích dùng mảng vì nó phù hợp với cách suy nghĩ của tôi, gọi là tư duy cho nó sang. Cứ mở cái file lên ưa chép gì thì chép vào mảng rồi xào nấu nó, ưa món gì mà chẳng được. Cách đó chân phương, dễ hiểu, dễ làm.

Còn cách 3 là cách ngoài lề, tôi đưa vào cho rộng đường binh và đã nói VBA thì buộc phải có nó. Nếu tìm hiểu thêm thì chắc cũng có nhiều điều thú vị nhưng thôi, để dành thời gian cho 2 cách quan trọng hơn.

Tôi là người thực hành chứ không phải lý thuyết bởi xuất phát điểm là tôi viết VBA trước khi biết lý thuyết căn bản nữa kia. Do đó rất cảm ơn bác đã cho tôi thấy nhiều điều gọi là khẩu quyết để tìm tòi học hỏi trong lập trình.
 
Upvote 0
Tôi hay dùng ADO để import dữ liệu từ File đang đóng và cảm thấy giải pháp đó là vô địch.
Ngoài ra cũng tiện khi cần lấy kết quả cho mảng để làm việc khác (như nạp vào Combobox).
 
Upvote 0
Tôi hay dùng ADO để import dữ liệu từ File đang đóng và cảm thấy giải pháp đó là vô địch.
Ngoài ra cũng tiện khi cần lấy kết quả cho mảng để làm việc khác (như nạp vào Combobox).
À, bạn cho tôi hỏi làm thế nào để chuyển cái Recordset thành mảng? Tôi gán toàn bộ cho mảng nhưng không được?
 
Upvote 0
À, bạn cho tôi hỏi làm thế nào để chuyển cái Recordset thành mảng? Tôi gán toàn bộ cho mảng nhưng không được?
Ah,
Tôi cũng học từ sư phụ @NDU
Code bài #19, dùng hàm Getdata thôi
 
Upvote 0
Ah,
Tôi cũng học từ sư phụ @NDU
Code bài #19, dùng hàm Getdata thôi
Rồi, tôi đã biết thêm 1 chút: muốn lấy dữ liệu từ Recordset ra mảng thì phải dùng Rec.Getrows, nhưng cái Recordset đó có kích thước ngược lại với Datasource nên bước tiếp phải chép dòng thành cột ra mảng thì mới ra đúng như Datasource được. Thêm nữa (cũng lạ?) đã Sheet3.Range("A2").CopyFromRecordset Rec.Datasource thì sau đó không Getrows được nữa và ngược lại đã Getrows thì CopyFromRecordset không lỗi nhưng không ra kết quả gì cả.
 
Lần chỉnh sửa cuối:
Upvote 0
Rồi, tôi đã biết thêm 1 chút: muốn lấy dữ liệu từ Recordset ra mảng thì phải dùng Rec.Getrows, nhưng cái Recordset đó có kích thước ngược lại với Datasource nên bước tiếp phải chép dòng thành cột ra mảng thì mới ra đúng như Datasource được. Thêm nữa (cũng lạ?) đã Sheet3.Range("A2").CopyFromRecordset Rec.Datasource thì sau đó không Getrows được nữa và ngược lại đã Getrows thì CopyFromRecordset không lỗi nhưng không ra kết quả gì cả.
Tôi chưa hiểu ý của bạn lắm, nhưng tôi nạp kiểu này thì bình thường, như tôi đang dùng hàm GetData:
Mã:
Private Sub ComboBox1_Change()
  With ComboBox1
    .ListFillRange = ""
    .ColumnCount = 2
    .ColumnWidths = "50;100"
    .Width = "200"
    .ColumnHeads = True
    Dim lR As Long, arr
    FileName = "D:\EXCEL\GPEData3.xlsb"
    SheetName = "file 2"
    RangeAddress = "A4:B10"
    arr = GetData(FileName, SheetName, RangeAddress)
    .List() = arr
  End With
End Sub
 
Upvote 0
Tôi chưa hiểu ý của bạn lắm, nhưng tôi nạp kiểu này thì bình thường, như tôi đang dùng hàm GetData:
Mã:
Private Sub ComboBox1_Change()
  With ComboBox1
    .ListFillRange = ""
    .ColumnCount = 2
    .ColumnWidths = "50;100"
    .Width = "200"
    .ColumnHeads = True
    Dim lR As Long, arr
    FileName = "D:\EXCEL\GPEData3.xlsb"
    SheetName = "file 2"
    RangeAddress = "A4:B10"
    arr = GetData(FileName, SheetName, RangeAddress)
    .List() = arr
  End With
End Sub
Tôi đang nói về code trong GetData bạn à. Nó lấy ra mảng bằng cách:
1/ Lấy từ Recordset ra mảng tạm bằng .GetRows.
2/ Dùng 2 vòng lặp để biến cái mảng tạm đấy thành mảng có cấu trúc giống như data nguồn.

Còn đoạn: "Thêm nữa (cũng lạ?) đã Sheet3.Range("A2").CopyFromRecordset Rec.Datasource thì sau đó không Getrows được nữa và ngược lại đã Getrows thì CopyFromRecordset không lỗi nhưng không ra kết quả gì cả." là tôi thử nghiệm vào code ADO của tôi ở bài #1: Đã .GetRows thì không CopyFromRecordset được nữa và ngược lại
 
Upvote 0
Tôi đang nói về code trong GetData bạn à. Nó lấy ra mảng bằng cách:
1/ Lấy từ Recordset ra mảng tạm bằng .GetRows.
2/ Dùng 2 vòng lặp để biến cái mảng tạm đấy thành mảng có cấu trúc giống như data nguồn.
uhm, tôi cũng dân ngoại đạo, thực sự thì ngôn ngữ ADO tôi cũng không hiểu sâu, tôi cứ nạp bằng hàm Getdata là lấy data từ File khác (đang đóng) về.
Snag_149024d9.png
 
Upvote 0
Web KT

Bài viết mới nhất

Back
Top Bottom