ADO: Kết nối, truy vấn Excel Database vào các control ở Userform.

Liên hệ QC

Hai Lúa Miền Tây

❆❆❆❆❆❆❆❆
Thành viên BQT
Administrator
Tham gia
18/3/08
Bài viết
8,281
Được thích
15,780
Giới tính
Nam
Nghề nghiệp
Làm ruộng.
Tiếp chiêu bài ADO: Kết nối Excel Database cho công việc kế toán của Thầy Mỹ (Ptm0412) và Kết nối giữa các file Excel bằng ADODC. của anh Sealand em viết bài này về việc kết nối và truy vấn với file excel dữ liệu khác trên userform, mong các anh chị hướng dẫn thêm.

Chuẩn bị:

References.jpg

Tạo 1 Userform và các control như hình:

form.jpg

Code trong module:

Kết nối với dữ liệu nguồn là file Excel:

Mã:
Public cnn As New ADODB.Connection
Sub Moketnoi()
Dim i, r As Integer
Set cnn = New ADODB.Connection
  With cnn
    .ConnectionString = "Provider= Microsoft.Jet.OLEDB.4.0; data source=" & ThisWorkbook.Path & _
                        "\Database.xls;Extended Properties=Excel 8.0;"
    .CursorLocation = adUseClient
    .Open
  End With

    
End Sub

Code trong form:

Mã:
Dim rst As ADODB.Recordset

Private Sub FillcbOrder()
On Error Resume Next
Dim lsSQL As String
Dim lrs As New ADODB.Recordset
        lsSQL = "select distinct [ORDER] " & _
                        "From [Dulieu$] " & _
                       "ORDER BY [ORDER]"
       lrs.Open lsSQL, cnn, adOpenStatic, adLockReadOnly
       cbOrder.Clear
       Do Until lrs.EOF
       cbOrder.AddItem lrs![Order]
       lrs.MoveNext
       Loop
      Set lrs = Nothing

End Sub




Private Sub cmdExcel_Click()
On Error GoTo ErrHandle
    Dim iNumCols, i As Integer
    Dim lsSQL As String
    Dim lrs As New ADODB.Recordset
       lsSQL = "SELECT * FROM [Dulieu$] where [ORDER] like '" & cbOrder.Text & "' order by id"
       lrs.Open lsSQL, cnn, 1, 3
    If lrs.EOF Then
      MsgBox "Could not find your data, Please try again", vbCritical
    Else
    Cells.ClearContents
    iNumCols = lrs.Fields.Count
    For i = 1 To iNumCols
        With Sheet1
            .Cells(3, i).Value = lrs.Fields(i - 1).Name
            .Cells(3, i).Font.Bold = True
            .Cells(3, i).Font.ColorIndex = 5
            With .Cells(3, i).Interior
                .ColorIndex = 34
            End With
        End With
    Next
    Range("A4").CopyFromRecordset lrs
    
    End If
    Set lrs = Nothing
    Exit Sub
ErrHandle:
    MsgBox Err.Description

End Sub


Private Sub cmdFillList_Click()
On Error GoTo ErrHandle
    Dim iNumCols, i As Integer
    Dim lsSQL As String
    Dim lrs As New ADODB.Recordset
       lsSQL = "SELECT * FROM [Dulieu$] where [ORDER] like '" & cbOrder.Text & "' order by id"
       lrs.Open lsSQL, cnn, 1, 3
    If lrs.EOF Then
      MsgBox "Could not find your data, Please try again", vbCritical
    Else
        
    Set msgInfo.DataSource = lrs
    
    End If
    Set lrs = Nothing
    Exit Sub
ErrHandle:
    MsgBox Err.Description

End Sub


Private Sub UserForm_Initialize()
Moketnoi
On Error Resume Next
FillcbOrder
Dim rs As New ADODB.Recordset
Set rs = Nothing
rs.Open "Select * from [Dulieu$] order by id", cnn, 1, 3

Set msgInfo.DataSource = rs

End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
cnn.Close
Set cnn = Nothing

End Sub
 

File đính kèm

  • ADO.rar
    157.9 KB · Đọc: 1,174
Vấn đề sống còn của 1 Data Excel file là tốc độ. Với 1 file có dữ liệu nhỏ thì không sao, nhưng khi số lượng các bản ghi tăng lên thì công thức tuy là thế mạnh của Excel nhưng là 1 gánh quá sức của Hệ thống.Do vậy, trước đây mình còn làm kế toán, kho hàng trên file Exc thì một điều luôn thúc ép là tìm kiếm phương pháp ly khai công thức đơn thuần kết nối các bảng tính. ADO là 1 hướng giải quyết hoàn toàn khả thi.Nhưng có 1 điều mình chưa Test được là tốc độ sử lý dữ liệu trên Recordset và trên Array thì cái nào hơn. Cách trao đổi dữ liệu từ Recordset sang Array có thể sử dụng GetRow, nhưng ngược lại thì chưa thấy cách nào hay cả.
Vậy Domfootwear trong quá trình viết bài cũng nên lưu ý giùm.Thanks
 
Cũng dữ liệu trên ta truy vấn lọc dữ liệu theo ngày = DTPicker, các bạn thử nhé.
 
Vấn đề sống còn của 1 Data Excel file là tốc độ. Với 1 file có dữ liệu nhỏ thì không sao, nhưng khi số lượng các bản ghi tăng lên thì công thức tuy là thế mạnh của Excel nhưng là 1 gánh quá sức của Hệ thống.Do vậy, trước đây mình còn làm kế toán, kho hàng trên file Exc thì một điều luôn thúc ép là tìm kiếm phương pháp ly khai công thức đơn thuần kết nối các bảng tính. ADO là 1 hướng giải quyết hoàn toàn khả thi.Nhưng có 1 điều mình chưa Test được là tốc độ sử lý dữ liệu trên Recordset và trên Array thì cái nào hơn. Cách trao đổi dữ liệu từ Recordset sang Array có thể sử dụng GetRow, nhưng ngược lại thì chưa thấy cách nào hay cả.
Vậy Domfootwear trong quá trình viết bài cũng nên lưu ý giùm.Thanks

Tốc độ trên rec sẽ cao hơn Arr anh Việt à, cũng dữ liệu trên anh thử so sánh như sau:

1./ Recordset:

Mã:
Private Sub cmdExcel_Click()
On Error GoTo ErrHandle
    Dim iNumCols, i As Integer
    Dim lsSQL As String
    Dim lrs As New ADODB.Recordset
       lsSQL = "SELECT * FROM [Dulieu$] where [ORDER] like '" & cbOrder.Text & "' order by id"
       lrs.Open lsSQL, cnn, 1, 3
    If lrs.EOF Then
      MsgBox "Could not find your data, Please try again", vbCritical
    Else
    Cells.ClearContents
    iNumCols = lrs.Fields.Count
    For i = 1 To iNumCols
        With Sheet1
            .Cells(3, i).Value = lrs.Fields(i - 1).Name
            .Cells(3, i).Font.Bold = True
            .Cells(3, i).Font.ColorIndex = 5
            With .Cells(3, i).Interior
                .ColorIndex = 34
            End With
        End With
    Next
    Range("A4").CopyFromRecordset lrs
    End If
    Set lrs = Nothing
    Exit Sub
ErrHandle:
    MsgBox Err.Description

End Sub

2./ Arr

Mã:
Private Sub cmdExByGetRow_Click()
    Dim i As Integer
    Dim lsSQL As String
    Dim lrs As New ADODB.Recordset
    Dim arr, r, c
       lsSQL = "SELECT * FROM [Dulieu$] where [ORDER] like '" & cbOrder.Text & "' order by id"
       lrs.Open lsSQL, cnn, 1, 3
       
       arr = lrs.GetRows(-1, 1)
       
       For c = LBound(arr, 1) To UBound(arr, 1)
          Cells(3, c + 1).Value = lrs.Fields(c).Name
       Next

       For r = LBound(arr, 2) To UBound(arr, 2)
          For c = LBound(arr, 1) To UBound(arr, 1)
             Cells(r + 4, c + 1).Value = arr(c, r)
          Next c
       Next r

End Sub
 
Lần chỉnh sửa cuối:
Tốc độ trên rec sẽ cao hơn Arr anh Việt à, cũng dữ liệu trên anh thử so sánh như sau:

2./ Arr

Mã:
...
       For r = LBound(arr, 2) To UBound(arr, 2)
          For c = LBound(arr, 1) To UBound(arr, 1)
             Cells(r + 4, c + 1).Value = arr(c, r)
          Next c
       Next r

...
Đoạn này hình như là kg cần for next mà.
Gán thẳng vào.
PHP:
Cells(4,1).resize(UBound(arr, 2),UBound(arr, 1))=Arr
 
Tốc độ trên rec sẽ cao hơn Arr anh Việt à, cũng dữ liệu trên anh thử so sánh như sau:

2./ Arr

Mã:
Private Sub cmdExByGetRow_Click()
    Dim iNumCols, i As Integer
    Dim lsSQL As String
    Dim lrs As New ADODB.Recordset
    Dim arr, r, c
       lsSQL = "SELECT * FROM [Dulieu$] where [ORDER] like '" & cbOrder.Text & "' order by id"
       lrs.Open lsSQL, cnn, 1, 3
       
       arr = lrs.GetRows(-1, 1)
       
       For c = LBound(arr, 1) To UBound(arr, 1)
          Cells(3, c + 1).Value = lrs.Fields(c).Name
       Next

       For r = LBound(arr, 2) To UBound(arr, 2)
          For c = LBound(arr, 1) To UBound(arr, 1)
             Cells(r + 4, c + 1).Value = arr(c, r)
          Next c
       Next r

End Sub
Làm vầy đâu phải là Array đâu trời ---> Có đụng tới CELLS thì xem như... THUA
 
Em đang mong Thầy và các anh chị hướng dẫn để học hỏi thêm. Theo Thầy thì nên chỉnh lại như thế nào cho nó tối ưu.
Thì như ThuNghi làm ở trên đó, 1 khi bạn đã có Array thì cứ việc gán toàn bộ Array này xuống vùng dữ liệu mà không cần bất cứ vòng lập nào!
Code ở trên vì phải "xoay 90 độ" cho Array nên nếu cần 1 vòng lập thì ta sẽ chạy For.. Next gán các giá trị từ Array1 nguồn sang Array2, xong việc sẽ gán Array2 xuống Range (chứ đừng gán từng phần tử của Array vào từng cell như thế)
 
Lần chỉnh sửa cuối:
Thì như ThuNghi làm ở trên đó, 1 khi bạn đã có Array thì cứ việc gán toàn bộ Array này xuống vùng dữ liệu mà không cần bất cứ vòng lập nào!
Code ở trên vì phải "xoay 90 độ" cho Array nên nếu cần 1 vòng lập thì ta sẽ chạy For.. Next gán các giá trị từ Array1 nguồn sang Array2, xong việc sẽ gán Array2 xuống Range (chứ đừng gán từng phần tử của Array vào từng cell như thế)
Cho em hỏi thêm là 2 cách trên cách nào tối ưu hơn, ưu và khuyết của từng cách ?
 
Cho em hỏi thêm là 2 cách trên cách nào tối ưu hơn, ưu và khuyết của từng cách ?
Để công bằng khi so sánh 2 phương pháp, ta clear dữ liệu khi form load nhé:
Mã:
Private Sub UserForm_Initialize()
  Moketnoi
  On Error Resume Next
  FillcbOrder
  Dim rs As New ADODB.Recordset
  Set rs = Nothing
  rs.Open "Select * from [Dulieu$] order by id", cnn, 1, 3
  Set msgInfo.DataSource = rs
  [COLOR=#ff0000][B]Sheet1.UsedRange.Clear[/B][/COLOR]
End Sub
Code của bạn ta đặt thêm bộ đếm thời gian:
PHP:
Private Sub cmdExcel_Click()
  On Error GoTo ErrHandle
  Dim iNumCols, i As Integer, TG As Double
  Dim lsSQL As String
  Dim lrs As New ADODB.Recordset
  TG = Timer
  lsSQL = "SELECT * FROM [Dulieu$] where [ORDER] like '" & cbOrder.Text & "' order by id"
  lrs.Open lsSQL, cnn, 1, 3
  If lrs.EOF Then
    MsgBox "Could not find your data, Please try again", vbCritical
  Else
    Cells.ClearContents
    iNumCols = lrs.Fields.Count
    For i = 1 To iNumCols
      With Sheet1
        .Cells(3, i).Value = lrs.Fields(i - 1).Name
        .Cells(3, i).Font.Bold = True
        .Cells(3, i).Font.ColorIndex = 5
        .Cells(3, i).Interior.ColorIndex = 34
      End With
    Next
    Range("A4").CopyFromRecordset lrs
    MsgBox Format(Timer - TG, "0.000000")
  End If
  Set lrs = Nothing: Exit Sub
ErrHandle:
    MsgBox Err.Description
End Sub
Trên UserForm, tạo thêm 1 nút, đặt tên là cmdExcel2, chèn code dùng Array vào:
PHP:
Private Sub cmdExcel2_Click()
  Dim lsSQL As String, TG As Double
  Dim lrs As New ADODB.Recordset
  Dim sArray, Arr, r As Long, c As Long
  On Error GoTo ErrHandle
  TG = Timer
  lsSQL = "SELECT * FROM [Dulieu$] where [ORDER] like '" & cbOrder.Text & "' order by id"
  lrs.Open lsSQL, cnn, 1, 3
  If lrs.EOF Then
    MsgBox "Could not find your data, Please try again", vbCritical
  Else
    sArray = lrs.GetRows(-1, 1)
    ReDim Arr(UBound(sArray, 2) + 1, UBound(sArray, 1))
    For c = LBound(sArray, 1) To UBound(sArray, 1)
      Arr(0, c) = CStr(lrs.Fields(c).Name)
    Next
    For r = LBound(sArray, 2) To UBound(sArray, 2)
      For c = LBound(sArray, 1) To UBound(sArray, 1)
        Arr(r + 1, c) = sArray(c, r)
      Next c
    Next r
    With Range("A3").Resize(UBound(Arr, 1) + 1, UBound(Arr, 2) + 1)
      .Value = Arr
      .Resize(1).Font.Bold = True
      .Resize(1).Font.ColorIndex = 5
    End With
    MsgBox Format(Timer - TG, "0.000000")
  End If
  Set lrs = Nothing: Exit Sub
ErrHandle:
    MsgBox Err.Description
End Sub
Chạy thử và so sánh xem thì biết liền chứ gì
 
Quả thật tốc độ khi sử dụng Arr rất đáng nể, tuy code có dài và khó hiểu hơn khi dùng Rec nhưng thay vào đó mình được cái tốc độ, vậy cũng đáng.
 
Lần chỉnh sửa cuối:
Quả thật tốc độ khi sử dụng Arr rất đáng nể, tuy code có dài và khó hiểu hơn khi dùng Rec nhưng thay vào đó mình được cái tốc độ, vậy cũng đáng.
Thật ra cũng không khó hiểu gì lắm
- Vòng lập đầu tiên dùng lấy tiêu đề cột là y chang như khi dùng recordset
- 2 vòng lập tiếp theo chuyển dòng thành cột, cột thành dòng từ Array 1 sang Array 2 (xoay array 90 độ)
Chỉ vậy thôi
 
Thật ra cũng không khó hiểu gì lắm
- Vòng lập đầu tiên dùng lấy tiêu đề cột là y chang như khi dùng recordset
- 2 vòng lập tiếp theo chuyển dòng thành cột, cột thành dòng từ Array 1 sang Array 2 (xoay array 90 độ)
Chỉ vậy thôi
Vậy còn đối với fill dữ liệu vào MSHFlexGrid = cách này thì như thế nào hả Thầy?
Trước kia em dùng vòng lặp để đưa dữ liệu vào MSHFlexGrid, sau này tìm đến Rec tốc độ được cải thiện đáng kể, những tưởng Rec là vô địch, hôm nay mới thấy được tốc độ khi sử dụng Arr hơn cả Rec.
Mong Thầy hướng dẫn thêm.
 
Vậy còn đối với fill dữ liệu vào MSHFlexGrid = cách này thì như thế nào hả Thầy?
Trước kia em dùng vòng lặp để đưa dữ liệu vào MSHFlexGrid, sau này tìm đến Rec tốc độ được cải thiện đáng kể, những tưởng Rec là vô địch, hôm nay mới thấy được tốc độ khi sử dụng Arr hơn cả Rec.
Mong Thầy hướng dẫn thêm.
Trước khi trả lời câu này, xin hỏi lại bạn: Vì sao lại dùng MSHFlexGrid mà không dùng SpreadSheet?
(vì tôi chưa dùng MSHFlexGrid bao giờ nên không biết nó có cho phép gán Array vào Range như Excel không?)
 
Mà sao khi lấy
PHP:
sArray = lrs.GetRows(-1, 1)
Mà lại không dùng được nhỉ.
PHP:
WorksheetFunction.Transpose(sArray)
Còn phần của Dom Private Sub FillcbOrder() sao không dùng Arr mà phải dùng EOF
PHP:
' '       cbOrder.Clear
''       Do Until lrs.EOF
''       cbOrder.AddItem lrs![Order]
''       lrs.MoveNext
''       Loop
Dùng thế này thấy OK.
PHP:
sArray = lrs.GetRows
    With cbOrder
      .List = WorksheetFunction.Transpose(sArray)
    End With
    Erase sArray
 
Mà sao khi lấy
PHP:
sArray = lrs.GetRows(-1, 1)
Mà lại không dùng được nhỉ.
PHP:
WorksheetFunction.Transpose(sArray)
Theo kinh nghiệm của tôi thì dùng Transpose không chắc ăn đâu, sẽ bị lỗi khi dữ liệu nhiều (nhiều bao nhiêu sẽ bị lỗi thì tôi không biết nhưng chắc chắn là sẽ có lỗi)
Vì thế, thà rằng cứ For... Next trên Array để xoay dữ liệu sang Array khác
 
Mà sao khi lấy
PHP:
sArray = lrs.GetRows(-1, 1)
Mà lại không dùng được nhỉ.
PHP:
WorksheetFunction.Transpose(sArray)
Còn phần của Dom Private Sub FillcbOrder() sao không dùng Arr mà phải dùng EOF
PHP:
' '       cbOrder.Clear
 ''       Do Until lrs.EOF
 ''       cbOrder.AddItem lrs![Order]
 ''       lrs.MoveNext
 ''       Loop
Dùng thế này thấy OK.
PHP:
sArray = lrs.GetRows
     With cbOrder
       .List = WorksheetFunction.Transpose(sArray)
     End With
     Erase sArray

Em dùng vòng lặp cho trường hợp này chắc ăn hơn vì có thể dùng code này cho Access, VB mà không cần tham chiếu đến Excel.

Trước khi trả lời câu này, xin hỏi lại bạn: Vì sao lại dùng MSHFlexGrid mà không dùng SpreadSheet?
(vì tôi chưa dùng MSHFlexGrid bao giờ nên không biết nó có cho phép gán Array vào Range như Excel không?)
Em dùng MSHFlexGrid với lí do là nó đẹp, khi ta trộn cell thì cái dòng trộn đó nó sẽ luôn hiển thị và nằm giữa khoảng cách giữa trên và dưới của MSHFlexGrid cho dù số dòng chứa các dòng đó nhiều hơn số dòng MSHFlexGrid được hiển thị.
 
Em dùng MSHFlexGrid với lí do là nó đẹp, khi ta trộn cell thì cái dòng trộn đó nó sẽ luôn hiển thị và nằm giữa khoảng cách giữa trên và dưới của MSHFlexGrid cho dù số dòng chứa các dòng đó nhiều hơn số dòng MSHFlexGrid được hiển thị.
Vụ này nghe lạ à nha! Cho tôi 1 file có cái thằng MSHFlexGrid này được không (máy tôi chẳng có)
(Ý tôi muốn nhìn xem cái vụ hiển thị khi trộn cell như bạn vừa nói ấy)
 
Vụ này nghe lạ à nha! Cho tôi 1 file có cái thằng MSHFlexGrid này được không (máy tôi chẳng có)
(Ý tôi muốn nhìn xem cái vụ hiển thị khi trộn cell như bạn vừa nói ấy)
Bài #1 em có cái MSHFlexGrid rồi Thầy thêm đoạn sau là trộn được.

Mã:
Private Sub cmdFillList_Click()
On Error GoTo ErrHandle
    Dim i As Integer
    Dim lsSQL As String
    Dim lrs As New ADODB.Recordset
       lsSQL = "SELECT * FROM [Dulieu$] where [ORDER] like '" & cbOrder.Text & "' order by id"
       lrs.Open lsSQL, cnn, 1, 3
    If lrs.EOF Then
      MsgBox "Could not find your data, Please try again", vbCritical
      Set msgInfo.DataSource = lrs
    Else
        
    Set msgInfo.DataSource = lrs
    
[B][COLOR=#0000ff]       With msgInfo
        For i = 2 To 5
            .MergeCol(i) = True
            .Col = i
            .Row = .FixedRows
            .RowSel = .Rows - 1
            .FillStyle = flexFillRepeat
            .CellBackColor = &HC0FFFF
        Next

      End With
[/COLOR][/B]
    
    
    End If
    Set lrs = Nothing
    Exit Sub
ErrHandle:
    MsgBox Err.Description

End Sub

Lưu ý phải chọn thuộc tính MergeCells của nó là 1
 
Còn đây là hình ảnh của nó.

[video=youtube;9LRy2TpbA1U]http://www.youtube.com/watch?v=9LRy2TpbA1U[/video]
 
Web KT
Back
Top Bottom