一个复合查询方法
类别: 数据库教程
几乎每个完整的应用程序都会需要一个复合查询。建立一个功能强大的复合查询首先必须要能够动态生成查询条件,其次应该能够对查询到的数据进行修改,最后这个复合查询最好能够对一对多的两个表建立条件进行查询。
在VFP里建立查询的方法主要有这么几种:一是使用VFP中自带的SearchClass类;二是建立一个查询;三是建立一个视图,其中包括参数化视图、宏替换Sql语句视图;四是建立一个Grid,将其数据源设置为SQL语句或临时表。
不管哪一种方法,其实质都是使用SQL语句。
这几种方法各有各的优点,也都有缺点。
建立查询的方法最死板,只能建立固定条件的查询,并且不能更新数据,最不能满足要求。
SearchClass类功能强大,但是它只能对一个表建立条件进行查询,并且它的源代码太复杂了,几乎难以进行修改定制;(初学者想必都有过用表单向导建立表单后试图修改txtbtn类、SearchClass类的经历吧!看到源代码后有几个没昏倒?)用将Grid的数据源设置为SQL语句或临时表的方法无法修改/更新数据,刷新数据也比较困难。(这方面的问题在网易虚拟社区VFP版上有过许多讨论,大家可以去看看。)建立视图的方法中,参数化视图也太简单。不管是用表单控件的值作参数还是用给参数两端加上引号的方法都只能对固定的字段进行查询。如果是复合查询,难道要先建立几十个视图吗?最有前途的办法还是用宏替换SQL语句建立视图的办法。视图有着能够对数据进行修改/更新的优点,如果能够动态生成查询条件,那么就是最完美的查询了。
建立宏替换sql语句视图的具体办法是先动态生成一个Sql语句sqlstatement,然后用宏替换的方法使用Create Sql view viewname as &sqlstatement来动态建立视图,最后将数据动态显示在一个Grid控件中。
看到这里,VFP大虾们怕会大喊:Stop!你当我是菜鸟啊!你的办法从理论上虽然行的通,但实际做起来就会碰到查询结果在表格上数据无法刷新的难题。俺早就试过不行了!你想骗稿费啊!
嘿嘿,这个难题偏偏给我解决了!这就是我洋洋得意的写这篇文章的原因!
问题的解决
==========
几个月前(好可怜^0^.....),我参照VFP的Sample中的Solution里的Interactively Bulid a sqlstatement示例建立了一个复合查询,想将它集成到我自己的程序中。由于该示例是用browse窗口来显示查询结果的,而我自己的应用程序使用了顶层表单,结果编译后运行才发现Browse窗口在顶层表单中无法显示。于是我建立了一个有两个表单的表单集,用一个表单makesql动态生成sql语句,在另一个表单form1上用grid来显示查询结果的数据,在给Grid设置数据源的时候,问题来了。首先使用临时表来作为表格的数据源,结果第一次查询正确,更改条件进行第二次查询时碰到了众所周知的"不能更新临时表"的错误;使用sql语句倒是可以,可是在表格显示数据前会莫名其妙的先出现一个Browse窗口,必须关闭它后才会显示表格,由于browse窗口在使用顶层表单的程序中无法显示,结果导致程序无法继续执行,这个办法也不行。
最后只能使用Create sql view viewname as &sqlstatement的办法了,先随便建立一个视图tempview,把表格的Recordsourcetype属性设置为1-别名,Recordsource属性设置为视图别名tempview,在表单makesql上建立sql语句后的代码中使用create sql view temp view as &sqlstatement建立视图Tempview.
执行后发现,第一次查询正确,更改查询条件后再次查询,出现"视图已存在,要改写吗?"的情况,按下"确定"后,出现的表格中没有数据。
避免出现对话框的问题好解决,在建立视图前先用rename view tempview to oldview,然后用Delete view oldview将旧的视图删除就可以了。代码如下:
****************************************************************************
set database to databasename &&databasename是你的数据库名称
&&注意:即使你打开了数据库也必须写这个语句!否则会出现"找不到数据库"的错误。
if used("tempview")
rename view tempview to oldview
delete view oldview
endif
Create sql view tempview as &sqlstatement
=requery()
IF _TALLY = 0
#DEFINE MSG_LOC "没有找到符合条件的纪录!"
#DEFINE TITLE_LOC "没有找到纪录"
=MESSAGEBOX(MSG_LOC,64+0+0,TITLE_LOC)
ELSE
thisform.hide
thisformset.form1.show
Endif
*****************************************************************************
但是这样做了以后,表格上没有数据的问题仍然存在。查找资料后发现,用Create sql view语句编程建立视图的方法,建立视图后要先保存视图定义,再打开视图后视图中才有数据。因此,必须将Creatsql view语句部分代码修改如下:
************************************************
Create sql view tempview as &sqlstatement
use
use tempview
************************************************
满以为这下问题解决了,结果更惨。出现的表格上不但没有数据,连表头、网格都不见了!这个问题
百思不得其解,查找资料也没有结果,最后不了了之,一直困扰了我几个月。
就在昨晚,我上床的时候突然灵光一现:既然表格无法动态加载数据源视图,那么干脆连包含表格的表单也动态生成!只要表单动态生成,那么表单上的表格对象的数据源不就完全重新加载了吗?也用不着刷新什么的了!而且,用这个方法也用不着先建立一个tempview视图,完全在程序中动态生成就可以了。主意一定,马上下床动手。将原来包含表格的表单form1删除,在上述的代码中最后一句
thisformset.form1.show前插入以下代码:
*************************************************************
thisformset.addobject("form1","form")
with thisformset.form1
.caption="查询结果"
.width=600
.height=400
.Autocenter=.t.
.controlbox=.f.
endwith
thisformset.form1.addobject("cmdReturn1","cmdReturn")
with thisformset.form1.cmdReturn1
.top=360
.left=270
endwith
thisformset.form1.addobject("grid1","gird")
with thisformset.form1.grid1
.Recordsourcetype=1
.Recordsource="tempview"
.top=10
.left=20
.height=300
.width=560
endwith
**************************************************************
在程序的最后加入:
*********************************************
Define class cmdReturn as commandbutton
caption="返回"
procdure click
thisform.release
endproc
enddefine
*********************************************
这下总可以了吧?运行程序,结果出现对话框"在事件或方法中不能嵌套类定义!"。我@#$%&*....什
么嘛!教科书、帮助文件中的示例prg都是这么写的啊!
不过好在还有办法,我手工建立一个类总行了吧!
在类库mybut中新建一个按钮类cmdReturn,设置它的cation属性为"返回",click事件代码为thisform.release。在上面的代码前插入set Classlib to mybut additive(注意:如果不加additive参数,将关闭所有之前打开的类库!),然后将最后的类定义语句Define...EndDefine全部删除。
运行,新表单出现!且慢,这个表单上怎么什么东西都没有?:-((打开调试器,在"局部"窗口中察看,发现明明有cmdReturn1、Grid1对象啊!怎么回事?仔细察看他们的每个属性,发现原来它们的visible属性都为false!原来,我们平常看到的帮助中的示例都是prg文件,在这些文件中用addobject()方法向表单添加的对象在显示表单后都是可见的。而在表单的scx文件中使用addobject()方法建立的任何东西,其visible属性都为false!
本质上,用createobject()、addobject()方法建立的对象,其实只是在内存中建立了一个对象变量,必须再用语句使它们实例化。比如,我们常用的mainform.show语句就是如此,没有使用show方法,mainform就只是内存中的一个变量,而不是一个表单不可见的实例!反之,thisform.release语句则从根本
上释放了表单上所有的对象变量,从而在内存中完全清除了表单.
而在prg文件中与scx文件中用addobject()方法建立表单和控件的顺序是不一样的,在prg文件中是先向表单添加控件再显示表单,表单上的控件继承了表单的visible属性,当表单实例化时控件也实例化;而在scx文件使用addobject()方法是先显示表单,然后再向表单添加控件的,因此必须手工设置控件的visible属性为Ture。
当然这样比较麻烦,干脆在mybut类库中手工建立一个Resultform表单类,在该表单类上添加一个命令按钮cmdReturn和一个Grid1,设置命令按钮cmdReturn的caption属性为"返回",Click事件代码为thisform.release,设置Grid1的RecordSourceType属性为1-别名,RecordSource属性为Tempview,这样就
不用在代码中手工输入thisformset.form1.cmdReturn1.visible=.t.语句,省事多了。
最终的程序代码如下:
*******************************************************
set database to databasename
if used("tempview")
rename view tempview to oldview
delete view oldview
endif
Create sql view tempview as &sqlstatement
=requery()
IF _TALLY = 0
#DEFINE MSG_LOC "没有找到符合条件的纪录!"
#DEFINE TITLE_LOC "没有找到纪录"
=MESSAGEBOX(MSG_LOC,64+0+0,TITLE_LOC)
ELSE
set Classlib to mybut additive
thisformset.addobject("form1","Resultform")
thisform.hide
thisformset.form1.show
Endif
********************************************************
运行程序,一切ok!终于实现了动态生成查询条件,动态显示结果。只要再用dbsetprop()语句设置视图为可更新,就能对查询结果进行修改/更新了!这是我见到过的最完美的复合查询了!就这么简单?没错,就这么简单!一个困扰数月的问题,研究的时候峰回路转,最终结果却是如此轻松!这就是编程的艺术吧!
这个问题的解决,虽然走了许多弯路,但是也让我们了解了许多VFP的原理,难道不是很值得吗?!光凭书本的知识,你永远也无法了解这些东西的。有人说:读三年的书,还不如写一个月的程序,不是吗?!其他:
我在本文中省略了开头的动态生成sql语句的部分,具体的做法大家可以研究一下VFP自带的示例应用程序solution中databases目录下的view/queries目录中的Interactively Bulid a sql statement示例,你甚至可以稍作修改就在你自己的程序中使用该表单。
我曾经就其原理写了一篇文章《交互式建立sql复合查询-vfp示例应用程序详解》贴在网易虚拟社区VFP版,有兴趣的朋友可以在精华区查到,读完该篇文章,你应该可以自行修改该程序使之能够对一对多数据库进行查询了。因为该文篇幅很长,又比较枯燥,就不在这儿解说了。
要注意的是:原示例程序用于生成sql语句查询。要改为用于建立sql视图,必须作一些修改:
1、在sql查询中不必限定表别名和数据库名,而建立sql视图却必须这样做。因此需要修改makesql表单的
自定义方法bldsql的代码,将源代码下面的部分:
**************************************************************************
IF !EMPTY(lcOperand)
lcValue2 = THISFORM.ValidateType(THIS.cboField2.Value,lcValue2)
lcWHERE = lcOperand + " " + lcField2 + " " + ;
lcRelation2 + " " + lcValue2
ENDIF
** Create the first part of the WHERE condition
lcWHERE = "WHERE " + lcField1 + " " + lcRelation1 + " " + lcValue1 + " " + lcWHERE
** Create the full SQL command using the base table for the form
lcSQL = "SELECT * FROM " + lcAlias + " " + lcWHERE
****************************************************************************
修改为:
****************************************************************************
If !empty(lcOperand)
lcValue2 = thisform.ValidateType(this.cboField2.value,lcValue2)
lcWhere = lcOperand + " " + lcAlias + "." + lcField2 + " " +;
lcRelation2 + " " +lcValue2
Endif
lcWhere = "Where "+ lcAlias + "." + lcField1 + lcRelation1 + " ";
+ lcValue1 + " " + lcWhere
lcSql = "Select * From " + "DatabaseName!" + lcAlias + " " + lcWhere
****************************************************************************
DatabaseName是你的数据库的名字。以上修改的实质是,给要查询的字段名限定其所在的表别名,给
select form的表别名限定所属的数据库。
2、修改RunSql命令按钮的Click事件代码,将原代码:
*************************************************************************
cMacro = ALLTRIM(THISFORM.edtSQL.Value) + "INTO CURSOR TEMPQUERY"
*************************************************************************
中的 (+ "INTO CURSOR TEMPQUERY")部分删除,将cMacro改为sqlstatement.并将除了下面部分外的全
部代码删除:
*************************
IF USED(lcOldAlias)
SELECT (lcOldAlias)
ENDIF
*************************
,插入上面的最终代码就可以了。
ok!现在所有的任务都完成了。做完所有的这一切,花不了十分钟,你就建立了一个强大的复合查询!
在VFP里建立查询的方法主要有这么几种:一是使用VFP中自带的SearchClass类;二是建立一个查询;三是建立一个视图,其中包括参数化视图、宏替换Sql语句视图;四是建立一个Grid,将其数据源设置为SQL语句或临时表。
不管哪一种方法,其实质都是使用SQL语句。
这几种方法各有各的优点,也都有缺点。
建立查询的方法最死板,只能建立固定条件的查询,并且不能更新数据,最不能满足要求。
SearchClass类功能强大,但是它只能对一个表建立条件进行查询,并且它的源代码太复杂了,几乎难以进行修改定制;(初学者想必都有过用表单向导建立表单后试图修改txtbtn类、SearchClass类的经历吧!看到源代码后有几个没昏倒?)用将Grid的数据源设置为SQL语句或临时表的方法无法修改/更新数据,刷新数据也比较困难。(这方面的问题在网易虚拟社区VFP版上有过许多讨论,大家可以去看看。)建立视图的方法中,参数化视图也太简单。不管是用表单控件的值作参数还是用给参数两端加上引号的方法都只能对固定的字段进行查询。如果是复合查询,难道要先建立几十个视图吗?最有前途的办法还是用宏替换SQL语句建立视图的办法。视图有着能够对数据进行修改/更新的优点,如果能够动态生成查询条件,那么就是最完美的查询了。
建立宏替换sql语句视图的具体办法是先动态生成一个Sql语句sqlstatement,然后用宏替换的方法使用Create Sql view viewname as &sqlstatement来动态建立视图,最后将数据动态显示在一个Grid控件中。
看到这里,VFP大虾们怕会大喊:Stop!你当我是菜鸟啊!你的办法从理论上虽然行的通,但实际做起来就会碰到查询结果在表格上数据无法刷新的难题。俺早就试过不行了!你想骗稿费啊!
嘿嘿,这个难题偏偏给我解决了!这就是我洋洋得意的写这篇文章的原因!
问题的解决
==========
几个月前(好可怜^0^.....),我参照VFP的Sample中的Solution里的Interactively Bulid a sqlstatement示例建立了一个复合查询,想将它集成到我自己的程序中。由于该示例是用browse窗口来显示查询结果的,而我自己的应用程序使用了顶层表单,结果编译后运行才发现Browse窗口在顶层表单中无法显示。于是我建立了一个有两个表单的表单集,用一个表单makesql动态生成sql语句,在另一个表单form1上用grid来显示查询结果的数据,在给Grid设置数据源的时候,问题来了。首先使用临时表来作为表格的数据源,结果第一次查询正确,更改条件进行第二次查询时碰到了众所周知的"不能更新临时表"的错误;使用sql语句倒是可以,可是在表格显示数据前会莫名其妙的先出现一个Browse窗口,必须关闭它后才会显示表格,由于browse窗口在使用顶层表单的程序中无法显示,结果导致程序无法继续执行,这个办法也不行。
最后只能使用Create sql view viewname as &sqlstatement的办法了,先随便建立一个视图tempview,把表格的Recordsourcetype属性设置为1-别名,Recordsource属性设置为视图别名tempview,在表单makesql上建立sql语句后的代码中使用create sql view temp view as &sqlstatement建立视图Tempview.
执行后发现,第一次查询正确,更改查询条件后再次查询,出现"视图已存在,要改写吗?"的情况,按下"确定"后,出现的表格中没有数据。
避免出现对话框的问题好解决,在建立视图前先用rename view tempview to oldview,然后用Delete view oldview将旧的视图删除就可以了。代码如下:
****************************************************************************
set database to databasename &&databasename是你的数据库名称
&&注意:即使你打开了数据库也必须写这个语句!否则会出现"找不到数据库"的错误。
if used("tempview")
rename view tempview to oldview
delete view oldview
endif
Create sql view tempview as &sqlstatement
=requery()
IF _TALLY = 0
#DEFINE MSG_LOC "没有找到符合条件的纪录!"
#DEFINE TITLE_LOC "没有找到纪录"
=MESSAGEBOX(MSG_LOC,64+0+0,TITLE_LOC)
ELSE
thisform.hide
thisformset.form1.show
Endif
*****************************************************************************
但是这样做了以后,表格上没有数据的问题仍然存在。查找资料后发现,用Create sql view语句编程建立视图的方法,建立视图后要先保存视图定义,再打开视图后视图中才有数据。因此,必须将Creatsql view语句部分代码修改如下:
************************************************
Create sql view tempview as &sqlstatement
use
use tempview
************************************************
满以为这下问题解决了,结果更惨。出现的表格上不但没有数据,连表头、网格都不见了!这个问题
百思不得其解,查找资料也没有结果,最后不了了之,一直困扰了我几个月。
就在昨晚,我上床的时候突然灵光一现:既然表格无法动态加载数据源视图,那么干脆连包含表格的表单也动态生成!只要表单动态生成,那么表单上的表格对象的数据源不就完全重新加载了吗?也用不着刷新什么的了!而且,用这个方法也用不着先建立一个tempview视图,完全在程序中动态生成就可以了。主意一定,马上下床动手。将原来包含表格的表单form1删除,在上述的代码中最后一句
thisformset.form1.show前插入以下代码:
*************************************************************
thisformset.addobject("form1","form")
with thisformset.form1
.caption="查询结果"
.width=600
.height=400
.Autocenter=.t.
.controlbox=.f.
endwith
thisformset.form1.addobject("cmdReturn1","cmdReturn")
with thisformset.form1.cmdReturn1
.top=360
.left=270
endwith
thisformset.form1.addobject("grid1","gird")
with thisformset.form1.grid1
.Recordsourcetype=1
.Recordsource="tempview"
.top=10
.left=20
.height=300
.width=560
endwith
**************************************************************
在程序的最后加入:
*********************************************
Define class cmdReturn as commandbutton
caption="返回"
procdure click
thisform.release
endproc
enddefine
*********************************************
这下总可以了吧?运行程序,结果出现对话框"在事件或方法中不能嵌套类定义!"。我@#$%&*....什
么嘛!教科书、帮助文件中的示例prg都是这么写的啊!
不过好在还有办法,我手工建立一个类总行了吧!
在类库mybut中新建一个按钮类cmdReturn,设置它的cation属性为"返回",click事件代码为thisform.release。在上面的代码前插入set Classlib to mybut additive(注意:如果不加additive参数,将关闭所有之前打开的类库!),然后将最后的类定义语句Define...EndDefine全部删除。
运行,新表单出现!且慢,这个表单上怎么什么东西都没有?:-((打开调试器,在"局部"窗口中察看,发现明明有cmdReturn1、Grid1对象啊!怎么回事?仔细察看他们的每个属性,发现原来它们的visible属性都为false!原来,我们平常看到的帮助中的示例都是prg文件,在这些文件中用addobject()方法向表单添加的对象在显示表单后都是可见的。而在表单的scx文件中使用addobject()方法建立的任何东西,其visible属性都为false!
本质上,用createobject()、addobject()方法建立的对象,其实只是在内存中建立了一个对象变量,必须再用语句使它们实例化。比如,我们常用的mainform.show语句就是如此,没有使用show方法,mainform就只是内存中的一个变量,而不是一个表单不可见的实例!反之,thisform.release语句则从根本
上释放了表单上所有的对象变量,从而在内存中完全清除了表单.
而在prg文件中与scx文件中用addobject()方法建立表单和控件的顺序是不一样的,在prg文件中是先向表单添加控件再显示表单,表单上的控件继承了表单的visible属性,当表单实例化时控件也实例化;而在scx文件使用addobject()方法是先显示表单,然后再向表单添加控件的,因此必须手工设置控件的visible属性为Ture。
当然这样比较麻烦,干脆在mybut类库中手工建立一个Resultform表单类,在该表单类上添加一个命令按钮cmdReturn和一个Grid1,设置命令按钮cmdReturn的caption属性为"返回",Click事件代码为thisform.release,设置Grid1的RecordSourceType属性为1-别名,RecordSource属性为Tempview,这样就
不用在代码中手工输入thisformset.form1.cmdReturn1.visible=.t.语句,省事多了。
最终的程序代码如下:
*******************************************************
set database to databasename
if used("tempview")
rename view tempview to oldview
delete view oldview
endif
Create sql view tempview as &sqlstatement
=requery()
IF _TALLY = 0
#DEFINE MSG_LOC "没有找到符合条件的纪录!"
#DEFINE TITLE_LOC "没有找到纪录"
=MESSAGEBOX(MSG_LOC,64+0+0,TITLE_LOC)
ELSE
set Classlib to mybut additive
thisformset.addobject("form1","Resultform")
thisform.hide
thisformset.form1.show
Endif
********************************************************
运行程序,一切ok!终于实现了动态生成查询条件,动态显示结果。只要再用dbsetprop()语句设置视图为可更新,就能对查询结果进行修改/更新了!这是我见到过的最完美的复合查询了!就这么简单?没错,就这么简单!一个困扰数月的问题,研究的时候峰回路转,最终结果却是如此轻松!这就是编程的艺术吧!
这个问题的解决,虽然走了许多弯路,但是也让我们了解了许多VFP的原理,难道不是很值得吗?!光凭书本的知识,你永远也无法了解这些东西的。有人说:读三年的书,还不如写一个月的程序,不是吗?!其他:
我在本文中省略了开头的动态生成sql语句的部分,具体的做法大家可以研究一下VFP自带的示例应用程序solution中databases目录下的view/queries目录中的Interactively Bulid a sql statement示例,你甚至可以稍作修改就在你自己的程序中使用该表单。
我曾经就其原理写了一篇文章《交互式建立sql复合查询-vfp示例应用程序详解》贴在网易虚拟社区VFP版,有兴趣的朋友可以在精华区查到,读完该篇文章,你应该可以自行修改该程序使之能够对一对多数据库进行查询了。因为该文篇幅很长,又比较枯燥,就不在这儿解说了。
要注意的是:原示例程序用于生成sql语句查询。要改为用于建立sql视图,必须作一些修改:
1、在sql查询中不必限定表别名和数据库名,而建立sql视图却必须这样做。因此需要修改makesql表单的
自定义方法bldsql的代码,将源代码下面的部分:
**************************************************************************
IF !EMPTY(lcOperand)
lcValue2 = THISFORM.ValidateType(THIS.cboField2.Value,lcValue2)
lcWHERE = lcOperand + " " + lcField2 + " " + ;
lcRelation2 + " " + lcValue2
ENDIF
** Create the first part of the WHERE condition
lcWHERE = "WHERE " + lcField1 + " " + lcRelation1 + " " + lcValue1 + " " + lcWHERE
** Create the full SQL command using the base table for the form
lcSQL = "SELECT * FROM " + lcAlias + " " + lcWHERE
****************************************************************************
修改为:
****************************************************************************
If !empty(lcOperand)
lcValue2 = thisform.ValidateType(this.cboField2.value,lcValue2)
lcWhere = lcOperand + " " + lcAlias + "." + lcField2 + " " +;
lcRelation2 + " " +lcValue2
Endif
lcWhere = "Where "+ lcAlias + "." + lcField1 + lcRelation1 + " ";
+ lcValue1 + " " + lcWhere
lcSql = "Select * From " + "DatabaseName!" + lcAlias + " " + lcWhere
****************************************************************************
DatabaseName是你的数据库的名字。以上修改的实质是,给要查询的字段名限定其所在的表别名,给
select form的表别名限定所属的数据库。
2、修改RunSql命令按钮的Click事件代码,将原代码:
*************************************************************************
cMacro = ALLTRIM(THISFORM.edtSQL.Value) + "INTO CURSOR TEMPQUERY"
*************************************************************************
中的 (+ "INTO CURSOR TEMPQUERY")部分删除,将cMacro改为sqlstatement.并将除了下面部分外的全
部代码删除:
*************************
IF USED(lcOldAlias)
SELECT (lcOldAlias)
ENDIF
*************************
,插入上面的最终代码就可以了。
ok!现在所有的任务都完成了。做完所有的这一切,花不了十分钟,你就建立了一个强大的复合查询!
-= 资 源 教 程 =-
文 章 搜 索