|
楼主 |
发表于 2018-12-8 00:57
|
显示全部楼层
本帖最后由 ivccav 于 2018-12-8 14:21 编辑
11.查询过程的效率问题
上面的各种技术只是解决了查询和匹配问题,还有输出问题效率问题需要解决。如果查询数据集庞大,比如有百万行数据,就需要注意查询过程中的效率问题,程序设计不好,会严重影响运行效率,后果就是体验效果不佳。造成运行效率低下的原因除了程序代码的问题外,还有两个原因:多余的显示和多余的查询。
11.1多余的显示
一般创建的查询系统是在窗体中设置一个TEXTBOX查询框,然后运用Change事件根据输入值自动查询并显示符合条件的数据子集。通过分析得知,当我们输入的查询关键词很少时,比如一个字符时,肯定会匹配绝多部分数据,但这些数据都不是最终想要的结果,如果我们把这些数据都显示出来,会造成极大地输出效率问题,因为向列表控件(Listbox、Listview等)添加数据并显示出来,是低效的。同时也是一种浪费,因为这么庞大的结果集没法看,只能导出到文件另行处理。多余的显示可以用分页技术解决,减轻输出到显示的压力,即每次只显示一部分结果,如果确有需要,再逐步显示剩余的数据。
先说使用ADO查询的分页技术。
(1)我们可新建一个窗体,并初始化:
Private Sub UserForm_Initialize()
Dim sql$, i&, j&, col&, a()
With Sheet2
col = .Range("A1").CurrentRegion.Columns.Count '列数
ReDim a(col - 1)
For i = 0 To UBound(a)
a(i) = .Columns(i + 1).ColumnWidth * 10 '创建Listview列宽数据
Next
End With
Set cnn = CreateObject("adodb.connection")
Set rs0 = CreateObject("adodb.recordset")
cnn.Open "Provider=Microsoft.Ace.OLEDB.12.0;Extended Properties='Excel 12.0;HDR=Yes';Data Source=" & ThisWorkbook.FullName
sql = "select * from [数据库$A1:D] where 1<>1" '只要标题,不要数据
rs0.Open sql, cnn, 1, 3
With ListView1
.View = lvwReport
.FullRowSelect = True
.Gridlines = True
For i = 0 To rs0.Fields.Count - 1
If i > 0 Then
.ColumnHeaders.Add , , rs0.Fields(i).Name, a(i), lvwColumnCenter
Else
.ColumnHeaders.Add , , rs0.Fields(i).Name, a(i)
End If
Next i
End With
Label2 = "准备就绪"
模糊查询.SetFocus
End Sub
(2)在文本框“模糊查询”的Change事件中创建查询语句,根据用户输入内容动态查询数据。注意,rst是一个公共Recordset对象,用来存储查询后的结果集,然后调用“下一页”子过程显示第一页:
Private Sub 模糊查询_Change()
Dim sql$, temp$, i&, j&, s$
Set rst = CreateObject("adodb.recordset")
temp = 模糊查询.Text
sql = "select * from [数据库$A1:D]"
If temp <> "" Then '模糊查询.Text不为空
For i = 0 To rs0.Fields.Count - 1 '逐个字段,从0开始循环结果集全部列
s = s & " or " & rs0.Fields(i).Name & " like '%" & temp & "%'" '查询字符串
Next i
sql = sql & " where " & Mid(s, 4)
End If
rst.Open sql, cnn, 1, 3
Call 下一页
End Sub
(3)分页代码包括显示上一页和下一页,算法代码如下:
Private Sub 下一页()
Dim i&, j&
If rst.RecordCount = 0 Then Label2.Caption = "共找到 0 条记录": ListView1.ListItems.Clear: Exit Sub
Label2.Caption = "共找到 " & rst.RecordCount & " 条记录"
If rst.EOF Then MsgBox "已显示所有数据": Exit Sub
If rst.BOF Then rst.Move ListView1.ListItems.Count + 1
With ListView1
.ListItems.Clear
Do While Not rst.EOF
i = i + 1
If i > 10 Then Exit Do '每次显示10条
.ListItems.Add , , rst.Fields(0).Value
For j = 1 To rst.Fields.Count - 1
.ListItems(i).SubItems(j) = rst.Fields(j).Value
Next j
rst.MoveNext
Loop
End With
End Sub
Private Sub 上一页()
Dim i&, j&
If rst.RecordCount = 0 Then Label2.Caption = "共找到 0 条记录": ListView1.ListItems.Clear: Exit Sub
Label2.Caption = "共找到 " & rst.RecordCount & " 条记录"
If rst.BOF Then MsgBox "已显示所有数据": Exit Sub
rst.Move -(ListView1.ListItems.Count + 10) '每次倒退10条(显示多少条就倒退多少条)
If rst.BOF Then MsgBox "已显示所有数据": Exit Sub
With ListView1
.ListItems.Clear
Do While Not rst.EOF
i = i + 1
If i > 10 Then Exit Do '每次显示10条
.ListItems.Add , , rst.Fields(0).Value
For j = 1 To rst.Fields.Count - 1
.ListItems(i).SubItems(j) = rst.Fields(j).Value
Next j
rst.MoveNext
Loop
End With
End Sub
使用ADO方法的好处是,Recordset对象会记住数据移动到哪一行,不需要你去控制。但有时候不适合使用ADO技术,因为数据比较乱,或者不规范,这时候就得使用数组的方式。
使用数组的分页技术
(1)同样,创建一个窗体并初始化。这里drr是数据源数组,crr是保存查询结果的数组,都是模块级公共变量,方便不同过程调用。
Private Sub UserForm_Initialize()
Dim i&, a
With Sheet2
drr = .Range("A2").CurrentRegion
ReDim a(UBound(drr, 2) - 1)
For i = 0 To UBound(a)
a(i) = .Columns(i + 1).ColumnWidth * 10
Next
End With
With ListView1
.View = lvwReport
.FullRowSelect = True
.Gridlines = True
For i = 1 To UBound(drr, 2)
If i > 1 Then
.ColumnHeaders.Add , , drr(1, i), a(i - 1), lvwColumnCenter
Else
.ColumnHeaders.Add , , drr(1, i), a(i - 1)
End If
Next i
End With
Label2 = "准备就绪"
模糊查询.SetFocus
End Sub
(2)在文本框“模糊查询”的Change事件中创建查询语句,根据用户输入内容动态查询数据。注意代码中的注释说明。Preserve运算效率比较低,其实可以每次把维数扩展100甚至1000,这样就能减少Preserve的使用次数,同时也不会浪费多少数组空间。当然也可以定义一个跟数据源数组一样大小的数组来保存查询结果,这样就不需要Preserve和转置,效率更高。也可以定义一个跟数据源数组行数一样多的数组,只保存符合条件的数据的行号,这样查询结果的保存会更轻松。待需要输出时根据行号可一步到位地找到数据行。这个代码可自行完成。
Private Sub 模糊查询_Change()
Dim txt$, i&
If IsEmpty(drr) Then Exit Sub
txt = 模糊查询.Text
If Len(txt) = 0 Then Exit Sub
cnt = 0 '记录符合查询条件的数据的条数
pos = 0 '记录每次输出之后crr数组的位置
ReDim crr(1 To 4, 1 To 1) '每次查询都需要重定义crr。
For i = 2 To UBound(drr)
If InStr(drr(i, 1) & "/" & drr(i, 2) & "/" & drr(i, 3) & "/" & drr(i, 4), txt) Then
u = UBound(crr, 2)
For j = 1 To 4
crr(j, u) = drr(i, j)
Next
cnt = cnt + 1
ReDim Preserve crr(1 To 4, 1 To u + 1)
End If
Next
' Preserve效率比较低,其实可以每次把维数扩展100甚至1000,
' 这样就能减少Preserve的使用次数,也不会浪费多少数组空间。
' ReDim crr(1 To 4, 1 To 100)
' For i = 2 To UBound(drr)
' If InStr(drr(i, 1) & "/" & drr(i, 2) & "/" & drr(i, 3) & "/" & drr(i, 4), txt) Then
' cnt = cnt + 1
' If cnt Mod 100 = 0 Then ReDim Preserve crr(1 To 4, 1 To UBound(crr, 2) + 100)
' For j = 1 To 4
' crr(j, cnt) = drr(i, j)
' Next
' End If
' Next
' 当然也可以定义一个跟数据源数组一样大小的数组来保存查询结果,
' 这样就不需要Preserve和转置,效率更高。
' 也可以定义一个跟数据源数组行数一样多的数组,只保存符合条件的
' 数据的行号,这样查询结果的保存会更轻松。待需要输出时根据行号
' 可一步到位地找到数据行。这个代码可自行完成。
crr = transpose(crr)
Call 下一页
End Sub
(3)数组的分页代码如下:
Private Sub 下一页()
Dim i&, j&, k&
If cnt = 0 Then Label2.Caption = "共找到 0 条记录": ListView1.ListItems.Clear: Exit Sub
Label2.Caption = "共找到 " & cnt & " 条记录"
If pos >= cnt Then MsgBox "已显示所有数据": Exit Sub
If pos = 0 Then pos = 1 'Listview中没有显示过数据的情形pos为零
If pos < 0 Then pos = ListView1.ListItems.Count + 1
With ListView1
.ListItems.Clear
For i = pos To cnt
k = k + 1
If k > 10 Then Exit For '每次显示10条
.ListItems.Add , , crr(i, 1)
For j = 1 To 3
.ListItems(k).SubItems(j) = crr(i, j+1)
Next
Next
pos = i
End With
End Sub
Private Sub 上一页()
Dim i&, j&
If cnt = 0 Then Label2.Caption = "共找到 0 条记录": ListView1.ListItems.Clear: Exit Sub
Label2.Caption = "共找到 " & cnt & " 条记录"
If pos <= 0 Then MsgBox "已显示所有数据": Exit Sub
pos = pos - (ListView1.ListItems.Count + 10) '每次倒退10条(显示多少条就要倒退多少条)
If pos <= 0 Then MsgBox "已显示所有数据": Exit Sub
With ListView1
.ListItems.Clear
For i = pos To cnt
k = k + 1
If k > 10 Then Exit For '每次显示10条
.ListItems.Add , , crr(i, 1)
For j = 1 To 3
.ListItems(k).SubItems(j) = crr(i, j+1)
Next
Next
pos = i
End With
End Sub
|
评分
-
1
查看全部评分
-
|