一个复合查询方法
发表于:2007-06-30来源:作者:点击数:
标签:
几乎每个完整的应用程序都会需要一个复合查询。建立一个功能强大的复合查询首先必须要能够动态 生成查询条件,其次应该能够对查询到的数据进行修改,最后这个复合查询最好能够对一对多的两个表建 立条件进行查询。 在VFP里建立查询的方法主要有这么几种:一是
几乎每个完整的应用程序都会需要一个复合查询。建立一个功能强大的复合查询首先必须要能够动态
生成查询条件,其次应该能够对查询到的数据进行修改,最后这个复合查询最好能够对一对多的两个表建
立条件进行查询。
在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 sql
statement示例建立了一个复合查询,想将它集成到我自己的程序中。由于该示例是用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语句编
程建立视图的方法,建立视图后要先保存视图定义,再打开视图后视图中才有数据。因此,必须将Creat
sql 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!现在所有的任务都完成了。做完所有的这一切,花不了十分钟,你就建立了一个强大的复合查询!
原文转自:http://www.ltesting.net