一. 概述
作为数据管理程序,统计和查询功能是非常重要的。否则,就和电子表格没有区别了。所以,在每个ACCESS程序中都不可能缺少查询的功能。
本文的目的是由浅入深的介绍几种最常用的利用主/子窗体来实现查询的方法,使初学者和有一定VBA基础的人可以更好的使用窗体查询这种手段。
附件中的窗体“常用窗体查询0.MDB”,仅包含3个数据表、1个查询和以这个查询为数据源的报表。是为了大家根据后面学习的内容作练习用的。
我们先看查询中的数据:
书籍 | 书名 | 类别 | 作者 | 出版社 | 单价 | 进书日期 |
22 | CHIP-01-08 | 报刊 | 电子计算机与外部设备期刊社 | 电子计算机与外部设备 | ¥16.80 | 2001-9-28 |
25 | 电脑新时代-6光盘 | 光盘 | UNKNOWN | 电脑新时代 | ¥5.00 | 2002-7-8 |
27 | 学电脑-7光盘 | 光盘 | UNKNOWN | 人民邮电 | ¥5.00 | 2002-7-13 |
28 | CHIP-02-07光盘 | 光盘 | 电子计算机与外部设备杂志社 | 电子计算机与外部设备 | ¥5.00 | 2002-7-13 |
16 | MCSE学习指南 | 书籍 | Syngress Media公司 | 人民邮电 | ¥62.00 | 2001-9-28 |
17 | 局域网原理与架设技术内幕大公开 | 书籍 | 蔡昌均 | 中国青年 | ¥69.00 | 2001-9-28 |
19 | AccessVBA基础 | 书籍 | EvanCallahan | 人民邮电 | ¥39.00 | 2001-9-28 |
20 | 中文版Access2001一册通 | 书籍 | 赵琳等 | 人民邮电 | ¥42.00 | 2001-9-28 |
21 | VISUALBASIC5.0教程 | 书籍 | SteveBrown | 电子工业 | ¥28.00 | 2001-9-28 |
23 | 中文版VisualBasic5.0程序员指南 | 书籍 | microsoft | 中国青年 | ¥70.00 | 2001-9-28 |
24 | Access中文版开发指南 | 书籍 | Alison Balter | 人民邮电 | ¥128.00 | 2002-2-16 |
29 | Access 2002数据库系统开发实例导航 | 书籍 | 桂思强 | 中国铁道 | ¥25.00 | 2003-3-10 |
30 | 中国名陵——集中华古代名陵之大成 | 书籍 | 罗哲文等 | 百花文艺 | ¥14.00 | 2003-5-20 |
31 | 边缘部落——福音谷 | 书籍 | 林茨(撰文摄影) | 河北教育 | ¥27.00 | 2003-5-20 |
为了比较有代表性,这个查询中包含多种字段类型:
数字类型:【书籍编号】、【单价】
字符类型:【书名】、【类别】、【作者】、【出版社】
日期类型:【进书日期】
在多条件查询中,我们会使用除了【书籍编号】之外的其他字段作为查询条件。
其实,在下面介绍的窗体查询方法中,有些代码或思路也可以应用在其它窗体查询方法中,你对各种代码、方法和思路越熟练,就越能充分发挥它们的作用。
这篇文章我是从4月初开始构思,到5月中旬开始动笔,花费两周时间写成,里面包含了我对ACCESS窗体查询所积累的经验,对初学者和有一定VBA基础的人都有针对性的方法。另外,希望其他精通ACCESS的高手提出意见。
二. 制作主/子窗体的步骤
1. 利用向导制作主窗体
现在的主窗体还太小,要已经以下步骤才能变成我们需要的主窗体(如下图):
①把窗体面积放大到足以容纳条件输入字段和子窗体,并调整所有控件的字体;
②把窗体的“记录源”和各控件的“数据来源”都删除(很多初学者很容易犯的错误就是这里),把类别和出版社改为组合框(因为这些字段的可能值比较少,直接选择就好,不必让用户输入);
③重新调整各字段的位置,并增加了两个空文本框和标签放在单价和进书日期后面(因为我打算使用一个范围来作查询条件,而不是一个固定的值,这样比较符合实际);
④单价后面的两个文本框改名为“单价开始”和“单价截止”,进书日期后面的两个文本框改名为“进书日期开始”和“进书日期截止”;
⑤画一个矩形框包住所有查询条件,把矩形框背景设置为常规,背景色为深灰色,特殊样式为凹陷,此时矩形框覆盖了其它控件,要用菜单中“格式”→“置于底层”才能让它们显示出来;
⑥在窗体上用向导建立一个按钮,按钮标题是“查询”,名称是“cmd查询”先不管里面的代码,后面再修改;
⑦窗体属性中“记录选定器”=否,“浏览按钮”=否。
2. 利用向导设计子窗体
①在主窗体中用工具箱中的“子窗体/子报表”对象建立一个子窗体;
②以存书查询为记录源,选择所有字段;
③把子窗体命名为“存书查询子窗体”;
④在主窗体上删除子窗体的标签,并重新调整子窗体的大小;
⑤关闭主窗体的设计视图,单独打开子窗体数据表视图,调整字体和行的大小。
现在,我们得到了如下的窗体:
现在还没有实际的查询功能,我们在后面要根据所使用的方法,修改窗体并增加一些其他的功能。
还有一些小的细节要注意,比如:按TAB键后的获得光标的控件的顺序要在设计视图下,视图菜单的“TAB键次序”来修改;有些文本框获得焦点后是否要打开输入法,比如输入单价和日期的地方要关闭,输入书名和作者的地方要打开,这些要在控件属性中设置。
三. 查询方法1:在查询中加入条件
这种方法对应的实例数据库是:“常用窗体查询1.MDB”。
1. 设计查询
我们打开“存书查询”设计视图如下:
在字段【书名】的准则格内写入:
Like IIf(IsNull([Forms]![存书查询窗体]![书名]),'*','*' &[Forms]![存书查询窗体]![书名] & '*')
加入后的设计视图如下:
把这个式子解释一下:
LIKE:是专门用于查询字符型字段的运算符,一般介绍ACCESS的书中都有它的用法。
ISNULL([Forms]![存书查询窗体]![书名]):是用来判断窗体“存书查询窗体”上面的“书名”这个文本框是否是空的。如果文本框是空的,则:
ISNULL([Forms]![存书查询窗体]![书名])=TRUE(真)
如果文本框不是空的,则:
ISNULL([Forms]![存书查询窗体]![书名])=FALSE(假)
IIF(expr, truepart,falsepart)函数:
expr 必要参数。用来判断真伪的表达式。
truepart 必要参数。如果 expr 为 True,则返回这部分的值或表达式。
falsepart 必要参数。如果 expr 为 False,则返回这部分的值或表达式。
所以当我们在窗体“存书查询窗体”上面的“书名”里写上“ACCESS”,整个式子的结果是:
LIKE *ACCESS*
相当于查询所有书名中包含“ACCESS”的书籍,这样可以实现模糊查询。
Like IIf(IsNull([Forms]![存书查询窗体]![书名]),'*',[Forms]![存书查询窗体]![书名] & '*')
Like IIf(IsNull([Forms]![存书查询窗体]![书名]),'*',[Forms]![存书查询窗体]![书名])
如果把准则改为上面两句中的一句,同样情况下当我们在窗体“存书查询窗体”上面的“书名”里写上“ACCESS”,整个式子的结果是:
LIKE ACCESS*
LIKE ACCESS
相当于查询以“ACCESS”开头的书籍(半模糊查询),或者书名就叫“ACCESS”的书(精确查询)。
具体使用那一种形式的查询要根据你的实际情况来修改。
当我们在窗体“存书查询窗体”上面的“书名”里什么也没有写,整个式子的结果是:
LIKE *
相当于查询所有的有书名的书。
在书写查询准则时,[Forms]![存书查询窗体]![书名]这样的窗体控件名很难写,这时你可以在准则格内点鼠标右键,选择生成器,出现如下图的窗口:
在左边的树型列表框里找到控件所在的窗体,在中间列表框出现这个窗体包含的所有控件,双击控件名,就会出现“[Forms]![存书查询窗体]![书名]”。
我们再把其他几个字段的查询准则写出:
【类别】:在窗体上是组合框,所以它肯定是个精确查询,跟【书名】一样,它是文本字段。查询准则如下:
LikeIIf(IsNull([Forms]![存书查询窗体]![类别]),'*',[Forms]![存书查询窗体]![类别])
【作者】:跟【书名】一样,它是文本字段。我也打算用模糊查询。查询准则如下:
Like IIf(IsNull([Forms]![存书查询窗体]![作者]),'*','*' &[Forms]![存书查询窗体]![作者] & '*')
【出版社】:跟【类别】的情况完全一样。查询准则如下:
LikeIIf(IsNull([Forms]![存书查询窗体]![出版社]),'*',[Forms]![存书查询窗体]![出版社])
【单价】:是一个数字字段,我在这里准备让用户可以查询“1.5元~6.5元”这样的范围。查询准则如下:
BetweenIIf(IsNull([Forms]![存书查询窗体]![单价开始]),0,[Forms]![存书查询窗体]![单价开始]) AndIIf(IsNull([Forms]![存书查询窗体]![单价截止]),5000,[Forms]![存书查询窗体]![单价截止])
BETWEENAND:是查询符合某个范围之内的值所需的条件格式。
“BETWEEN 1 AND 5”相当于“【字段】≥1 AND 【字段】≤5”。
根据前面讲解的IIF的内容,我用一个表格来解释一下各种情况下这个式子的实际结果,便于大家理解:(0和5000是我事先在条件中设定的最小值和最大值)
[单价开始]的值 | [单价截止]的值 | 整个式子的实际结果 |
空 | 空 | BETWEEN 0 AND 5000 |
1.5 | 空 | BETWEEN 1.5 AND 5000 |
空 | 20 | BETWEEN 0 AND 20 |
1.5 | 6.5 | BETWEEN 1.5 AND 6.5 |
【进书日期】:是一个日期型字段,我在这里准备让用户可以查询“#2001-5-1#~#2002-5-1#”这样的范围。查询准则如下:
Between(IIf(IsNull([Forms]![存书查询窗体]![进书日期开始]),#2000-1-1#,[Forms]![存书查询窗体]![进书日期开始]))And(IIf(IsNull([Forms]![存书查询窗体]![进书日期截止]),#2099-12-31#,[Forms]![存书查询窗体]![进书日期截止]))
在这个式子中,#2000-1-1#和#2099-12-31#是我设定的两个默认的开始和截止日期。如果用户不输入,就使用默认值了。这和前面【单价】的情况是一样的。
2. 设计按钮及代码
2.1 查询按钮
查询按钮的代码非常简单,关键部分只有一句:
Private Sub cmd查询_Click()
Me.存书查询子窗体.Requery '这句是关键
End Sub
Requery:是用来重新查询控件的数据源,这样可以刷新子窗体显示的记录。对于记录源是表或查询的控件如窗体、列表框、组合框等经常用Requery方法来刷新显示内容。
2.2 清除按钮
有了查询按钮的代码之后,大家就可以自己测试查询的效果了。不过,每次想换一个查询的条件时要清除上一个条件输入的东西,有时候比较麻烦。我们再来设计一个清除条件的按钮,控件名称是“cmd清除”,放在查询按钮的下面。按钮的代码如下:
Private Sub cmd清除_Click()
'下面这些控件的值要清空
Me.书名 =Null
Me.类别 =Null
Me.作者 =Null
Me.出版社 =Null
Me.单价开始 =Null
Me.单价截止 =Null
Me.进书日期开始= Null
Me.进书日期截止= Null
Me.存书查询子窗体.Requery '清空之后一样要重新查询
End Sub
2.3 打印按钮
有了查询结果,想打印出来,很简单。由于报表的记录源就是“存书查询”,所以在查询里设计的条件会直接在报表里体现出来。
我们只要用按钮向导制作一个预览报表的按钮就可以了。我们把这个按钮放在子窗体下面的右边。控件名称是“cmd预览报表”。
由于完全是采用向导制作的,不需修改代码,所以我就不在这里列出代码了。
2.4 导出EXCEL按钮
如果你觉得ACCESS的报表边框太难看,或者你想在报表上加上一些说明什么的。你可能更愿意把数据导出到EXCEL里进行再加工。
为了初学者容易理解,我打算用一个宏来解决这个问题。宏的设计如下图:
现在,宏里“输出文件”为空,运行时会自动跳出对话框让你选择保存目录和文件名。如果你想保存在数据库所在目录下的“查询结果.xls”里,可以在“输出文件”一栏里填入:
=[currentproject].[path] & "查询结果.xls"
把这个宏存为“导出查询数据宏”。
然后再在窗体里加入按钮。当按钮向导出现后,直接取消。在按钮的属性页里,先把按钮名称改为“cmd导出”,在按钮的“单击事件”里直接用下拉框选择“导出查询数据宏”,如下图。这样就不必编写代码了。
3. 增加统计功能
经常有人需要在主窗体上显示符合查询条件的记录总数,以及子窗体中某一字段的合计。所以,我们也准备在主窗体上增加记录总数和单价的合计。
3.1 子窗体上的设计
①单独打开子窗体的设计模式,在窗体页脚部分拉出一段窗体;
②在上面增加两个文本框:一个是“txt计数”,控件来源=Count(*),另一个是“txt单价合计”,控件来源=Sum([单价]);
③在子窗体的窗体属性中设置:浏览按钮=否,记录集类型=快照(快照的运行速度快,占用内存少,但不能编辑更新,正适合我们的需要);
3.2 主窗体上的设计
①在子窗体下面的地方设计两个文本框“计数”和“合计”,把它们的标签改为“符合条件记录数:”和“单价合计:”;
②“计数”文本框的控件来源=[存书查询子窗体].[Form].[txt计数],“合计”文本框的控件来源=[存书查询子窗体].[Form].[txt单价合计];
③由于主窗体上的这两个文本框是自动计算的,为了区别,设置它们的是否有效=否,是否锁定=是,背景颜色=深灰色。
4. 方法总结
现在,整个窗体就已经完成了。大家可以使用各种条件组合来测试一下窗体的效果。
4.1 本方法优点
在查询中加入条件这种方法比较简单,只要在查询中设计好各字段的条件,那么窗体中的代码就非常少,而且在打印报表和导出数据方面也很容易设计,比较适合初学ACCESS的人使用。
4.2 本方法缺点
①对查询中的数据有限制,所有设计了条件的字段中必须是每条记录都有数据。即使你在窗体中并没有在这个字段对应的文本框中输入条件。
举例来说,假设你在表“tb藏书情况”中,把【书籍编号】=16对应的这本书的作者或单价删掉,在“存书查询窗体”中,即使你不输入任何条件,【书籍编号】=16对应的这本书你也看不见。
②如果在某个查询组合下,子窗体上根本没有符合条件的记录,则主窗体上的“记录数”和“合计”会显示“#错误”。
如果把主窗体上的“记录数”和“合计”的控件来源改为:
=IIf(IsError([存书查询子窗体].[Form].[txt计数]),0,[存书查询子窗体].[Form].[txt计数])
=IIf(IsError([存书查询子窗体].[Form].[txt单价合计]),0,[存书查询子窗体].[Form].[txt单价合计])
则不会出现“#错误”,这是我在下面地址学到的:
http://www.erpbus.net/access/topic.asp?topic_id=8183&forum_id=44
③报表不能直接打开,每次打开报表就会跳出提示框要求输入条件。
四. 查询方法2:用VBA生成窗体筛选条件
这种方法对应的实例数据库是:“常用窗体查询2.MDB”。
主窗体的控件与“常用窗体查询1.MDB”相同,只是按钮中的代码都改了。子窗体则完全相同。
“存书查询”中不需要输入任何条件。
1. 按钮代码设计
1.1 查询按钮
本按钮代码的设计思想是根据主窗体上各个条件输入控件的值,用VBA代码生成一个条件组合的字符串作为子窗体的窗体筛选的条件。
'判断【书名】条件是否有输入的值
If NotIsNull(Me.书名) Then
'有输入
strWhere = strWhere & "([书名] like '*"& Me.书名 & "*') AND "
EndIf
&:是字符串链接运算符,它和“+”不同之处在于“&”两边如果不是字符串表达式,它会自动把表达式的值变成字符串,省了你转换格式了。
"([书名] like'*":英文双引号是VBA用来表示字符串的符号,两个双引号中间是一个字符串。""两个双引号中间什么也没有,表示一个空字符串。
注意:空字符串≠NULL(空值)。所以IsNull("")=False。
'*ACCESS*':英文单引号是SQL语句中用来表示字符串的符号,两个单引号中间是一个字符串。(SQL语句中也可以使用双引号来表示字符串,但在VBA代码生成SQL语句时,为了简化格式,一律用单引号表示SQL语句内的字符串)
[书名]:用[]表明书名是一个字段名、表名或查询名。使用[]的作用:①可以明确中间是一个名字而不是函数;②当字段名、表名或查询名是ACCESS或VBA保留字(如:DATE、NOTE、TYPEOF等)时,一定要用[]来标明;③当字段名、表名或查询名中包含特殊字符(如[停止/截止日期]、[查询-合计]等)时,一定要用[]来标明,否则特殊字符会被当作算术运算符而导致出错。
Me.书名:是“存书查询窗体”上文本框“书名”的值。Me.书名=Me.书名.Value。Me是对代码所在窗体的引用。
如果此时“存书查询窗体”上文本框“书名”的值是"ACCESS",那么此时strWhere的结果就是:
"([书名] like '*ACCESS*') AND "
跟查询方法1中一样,我们也可以改为半模糊查询和精确查询,代码如下:
strWhere = strWhere & "([书名] like '"& Me.书名 & "*') AND "
strWhere = strWhere & "([书名] like '"& Me.书名 & "') AND "
接着看关于【单价】的代码:
'判断【单价】条件是否有输入的值,由于有【单价开始】【单价截止】两个文本框
'所以要分开来考虑
If NotIsNull(Me.单价开始) Then
'【单价开始】有输入
strWhere = strWhere & "([单价] >= "& Me.单价开始 & ") AND "
EndIf
If NotIsNull(Me.单价截止) Then
'【单价截止】有输入
strWhere = strWhere & "([单价] <= "& Me.单价截止 & ") AND "
EndIf
这里用“>=”“<=”来代替“BETWEENAND”,效果是一样的,只是不需要像查询方法1中那样如果不填数字,会分别写入0和5000。
再来看看关于【进书日期】的代码:
'判断【进书日期】条件是否有输入的值,由于有【进书日期开始】【进书日期截止】两个文本框
'所以要分开来考虑
If NotIsNull(Me.进书日期开始) Then
'【进书日期开始】有输入
strWhere = strWhere & "([进书日期] >= #"& Format(Me.进书日期开始, "yyyy-mm-dd") &"#) AND "
EndIf
If NotIsNull(Me.进书日期截止) Then
'【进书日期截止】有输入
strWhere = strWhere & "([进书日期] <= #"& Format(Me.进书日期截止, "yyyy-mm-dd") &"#) AND "
EndIf
使用Format(Me.进书日期开始,"yyyy-mm-dd"),而不是直接用Me.进书日期开始,是因为有时候直接用Me.进书日期开始的话,会因为计算机日期格式设置的不同而出现一些奇怪的问题。ACCESS中的日期条件一直都是比较麻烦的,不管是选择查询、追加查询还是交叉表查询,设置日期字段的条件都是要特别注意的。(像查询方法1中直接在查询准则中引用控件值虽然不会出现类似问题,但交叉表查询中不能使用控件作为日期字段的条件。)
'先在立即窗口显示一下strWhere的值,代码调试完成后可以取消下一句
Debug.Print strWhere
Debug.Print:是专门用于调试的语句,它会把后面表达式的值显示在VB编辑器的立即窗口里。在适当地地方插入Debug.Print可以检查程序运行的是否正确。等全部调试结束后,可以把他们都删掉。
'让子窗体应用窗体查询
Me.存书查询子窗体.Form.Filter = strWhere
Me.存书查询子窗体.Form.FilterOn = True
主窗体或单一窗体设置筛选字符串时用:
Me.Filter="…" 或是 Forms![XX窗体].Filter="…"。
'在子窗体筛选后要运行一下自编子程序CheckSubformCount()
CallCheckSubformCount
CheckSubformCount是一个自编的子程序,CALL是调用子程序的语句,也可以不写CALL直接用CheckSubformCount调用子程序。
1.2 清除按钮
代码里都有说明,只讲以下的一段代码:
For Eachctl In Me.Controls
'根据ctl的控件类型来选择
Select Case ctl.ControlType
Case acTextBox '是文本框,要清空(注意,子窗体下面还有两个锁定的文本框不能赋值)
If ctl.Locked = False Then ctl.Value = Null
Case acComboBox '是组合框,也要清空
ctl.Value = Null
'其它类型的控件不处理
End Select
Next
For Each ctl InMe.Controls:意思是把当前窗体内所有的控件都逐个引用一次(子窗体控件内的控件不算在内)。这种用“ForEach [对象或属性] In[对象集合或属性集合]”的遍历方法在ACCESS编程中属于比较常见的用法,用处也很多。比如最常见的检查窗体是否打开的代码中也有:
Function IsLoaded(strFrmName As String) As Boolean
' 确定一个窗体是否已被装载。
ConstconFormDesign = 0
DimobjForm As Form
IsLoaded= False
For EachobjForm In Forms
If objForm.FormName = strFrmName Then
If objForm.CurrentView <>conFormDesign Then '窗体不是设计模式时
IsLoaded = True
ExitFunction ' 一旦找到了此窗体,就退出本函数。
End If
End If
Next
End Function
1.3 预览报表按钮
预览报表按钮中关键是如下语句:
strWhere= Me.存书查询子窗体.Form.Filter
'在打开报表的同时把子窗体的筛选条件字符串也传递给报表,
'这样地话报表也会显示和子窗体相同的记录。
DoCmd.OpenReport stDocName, acPreview, , strWhere
在我曾经制作的另一个例子:“打印当前记录的例子”里也使用过类似的方法。这个例子可以在 http://www.accxp.com/club/topic.cgi?forum=2&topic=6281下载。
关于DoCmd.OpenReport的详细帮助,可以在VBA帮助里找到。
1.4 导出按钮
导出方法只能把表或者查询的内容导出到EXCEL,但是不能把窗体上的记录集(RECORDSET)导出。所以我们要把子窗体的内容转变为一个查询。
我在查询中增加了一个“查询结果”,其中的设计随便就行,因为每次在使用这个查询之前,我们都会用DAO修改查询的SQL语句。
思路很简单,如果子窗体的筛选条件字符串是:
([类别] Like '书籍') AND ([单价]>=20)
那么,我们把“查询结果”的SQL语句改为:
SELECT * FROM 存书查询 WHERE ([类别] Like '书籍') AND([单价]>=20)
这样的话,“查询结果”的记录与子窗体里的记录就是相同的。
对应的代码如下:
strWhere= Me.存书查询子窗体.Form.Filter
IfstrWhere = "" Then
'没有条件
strSQL = "SELECT * FROM [存书查询]"
Else
'有条件
strSQL = "SELECT * FROM [存书查询] WHERE " &strWhere
EndIf
这一部分用来准备查询的SQL语句。
Set qdf =CurrentDb.QueryDefs("查询结果")
qdf.SQL =strSQL
qdf.Close
Set qdf =Nothing
这一部分用来把做好的SQL语句放在查询中。
DoCmd.OutputTo acOutputQuery, "查询结果", acFormatXLS, , True
这一部分的作用,和查询方法1中的“导出查询数据宏”的作用相同。运行时会自动跳出对话框让你选择保存目录和文件名。如果你想保存在数据库所在目录下的“查询结果.xls”里,可以把这一句改为:
strOutputFileName=CurrentProject.Path &"查询结果.xls"
DoCmd.OutputTo acOutputQuery, "查询结果", acFormatXLS,strOutputFileName , True
如果你想在文件名上保留时间,以便知道是什么时候做的查询,可以改为:
strOutputFileName=CurrentProject.Path & "查询结果-"& _
Format(Date(),"yyyymmdd")& ".xls"
DoCmd.OutputTo acOutputQuery, "查询结果", acFormatXLS,strOutputFileName , True
_:英文下划线表明下一行和本行是同一条语句,在语句比较长时,为了便于阅读,可以用这种方法把每行变短一些。下划线之前要有一个空格作分隔,不然可能会被当作字段名的一部分。
1.5 CheckSubformCount子程序
这段子程序的目的在代码中已经解释了。目前实例数据库中的代码中是用代码设置文本框的“控件来源”。也可以直接用代码设置控件的值(前提条件是在窗体的设计视图中,把两个文本框的控件来源都删掉,使控件变成未绑定)。此外,我再加几句代码,当子窗体无记录时,下面的导出和预览按钮都变成灰色。这样的软件显得更专业一些。代码如下:
IfMe.存书查询子窗体.Form.Recordset.RecordCount > 0 Then
'子窗体的记录数>0
Me.计数 = Me.[存书查询子窗体].[Form].[txt计数]
Me.合计 = Me.[存书查询子窗体].[Form].[txt单价合计]
Me.cmd导出.Enabled = True
Me.cmd预览报表.Enabled = True
Else
'子窗体的记录数=0
Me.计数 = 0
Me.合计 = 0
Me.cmd导出.Enabled = False
Me.cmd预览报表.Enabled = False
EndIf
2. 方法总结
使用VBA来设置子窗体筛选条件的方法解决了方法1中的几个缺点,大家可以逐一测试。
但是编写这样的VBA代码需要对VBA比较熟悉,而且要求对逻辑运算有一定基础,不然在使用逻辑运算符AND/OR/NOT和括号组合时会出错。不太适合初学者使用。
五. 主子窗体交叉表查询实例
这种方法对应的实例数据库是:“常用窗体查询3.MDB”。
交叉表查询通常作为统计之用,所以查询条件少了一些。
一般学习ACCESS的人,都有可能会被ACCESS的向导误导,以为子窗体控件里面一定要放一个子窗体。其实不是,在我的这个例子里,子窗体的对象其实就是交叉查询,如下图。
一直有人问,对于交叉表这样字段数量不固定的,怎样在子窗体中显示。其实,直接在子窗体里显示交叉表本身是最简单的办法。
另一种方法是事先设计一个包含很多未绑定字段文本框的数据表窗体,在显示之前,先用VBA设置窗体的“记录源”和各文本框的“控件来源”,同时还要控制没有字段可以显示的那些列要隐藏,然后再显示出来。但这样设计要求编写比较多的代码,只适合比较熟悉VBA的人使用。(我在这个例子的报表中设计了类似的代码,可以参考。)
1. 按钮设计
1.1 查询按钮
先看一下没有条件的交叉表查询的SQL语句:
TRANSFORM Sum(存书查询.单价) AS 单价之Sum
SELECT 存书查询.类别
FROM 存书查询
GROUP BY 存书查询.类别
PIVOT Format([进书日期],"yyyy/mm");
再看一下设置了条件的交叉表查询的SQL语句:
TRANSFORM Sum(存书查询.单价) AS 单价之Sum
SELECT 存书查询.类别
FROM 存书查询
WHERE (((存书查询.单价)>=5))
GROUP BY 存书查询.类别
PIVOT Format([进书日期],"yyyy/mm");
可见,WHERE是被插在中间的,所以我在生成SQL语句时要用如下代码:
strSQL = "TRANSFORM Sum(存书查询.单价) AS 单价之Sum SELECT 存书查询.类别 FROM存书查询 "
strSQL = strSQL & "WHERE(" &strWhere
strSQL = strSQL & ") GROUP BY 存书查询.类别 PIVOTFormat([进书日期],'yyyy/mm')"
注意,在字符串中间要留一些空格,比如“FROM存书查询”的后面,如果不留,SQL语句中“存书查询”和“WHERE”会连在一起。这样肯定是错的。
'显示交叉表的内容,不能直接刷新
Me.存书查询子窗体.SourceObject = ""
Me.存书查询子窗体.SourceObject = "查询.存书查询_交叉表"
这里也是一些人容易犯错的地方。因为子窗体里是查询对象,用
Me.存书查询子窗体.Requery
这样的语句是不能更新显示内容的。而要用SourceObject才能更新。如果子窗体对象是窗体,而你又用DAO改变了作为子窗体记录源的查询的SQL语句,也要用这种方法来更新显示内容。
'刷新计数和合计显示
Me.计数 =DCount("*", "存书查询_交叉表")
Me.合计 =DSum("[单价]", "存书查询", strWhere)
由于子窗体对象不是窗体了,自然就没有什么窗体页脚来让你设计合计字段。所以用只好用Dcount和Dsum来统计了。
其他按钮的代码都有注释,我就不专门讲了。
2. 报表设计
为了适应交叉表查询列数不固定的情况,我在页面页眉、主体、报表页脚都设计了一些名称有规律的标签、文本框。
'打开查询
Set rst =CurrentDb.OpenRecordset("SELECT * FROM [存书查询_交叉表] WHERE 1=2")
'记录字段总数
intFieldsNum = rst.Fields.Count
可能大家对“WHERE1=2”这个条件比较迷惑,其实这是为了让这个rst没有任何记录,因为我们需要的不是记录,只是各个字段的名字。这样也可以少占用一些内存。
其他代码已经有注释了,我就不在这里解释。
3. 方法总结
①在子窗体中直接使用交叉表作为子窗体源对象是最简单的方法。
②交叉表查询中,日期类型的字段不能引用窗体上的控件作为准则。例如:
Between [FORMS]![存书查询窗体]![进书日期开始] And[FORMS]![存书查询窗体]![进书日期截止]
ACCESS会报告错误,但是可以用确定的日期常数作为条件,例如:
Between #2001-1-1# And #2001-12-31#
所以要用DAO的方法来修改交叉表查询的SQL语句。
③以交叉表制作的报表,由于字段数不确定,所以设计起来太麻烦,实用性不强。最好是把交叉表数据导出到EXCEL里再进行处理。