VFP 的 SPT 起跳 -- 陈纯(BOE数据网络工作室)
细节描述 Visual FoxPro 的 SPT 技术快速入门
说在前面
熟悉 Fox 的朋友都知道,在 VFP 里我们可以使用远程视图 (Remote View) 和 SPT(SQL Pass Through) 技术控制远程异构数据库。这些技术其实是 VFP 对 ODBC 的 API 的封装,所以对于用户来说访问远程数据库就像操作传统的DBF一样简单。关于这两种技术的使用,完全可以洋洋洒洒地写下一本书,鉴于本文主题及篇幅,这里仅枚举 SPT 技术访问远程数据的应用。
SPT 与远程视图
很多人搞不懂有了远程视图这样直观、简单的工具,为什么还需要 SPT 呢?确实 SPT 较远程视图难以掌握,但细细体会你会发现:远程视图其实是对 SPT 的可视化工具!SPT 较远程视图更具威力,远程视图提供的功能只是 SPT 的一个子集。仔细探索两者优劣,我们发现:
SPT 的优势:
1. 一次得到多个Cursor
2. 执行除 Select 以外的其他 SQL 语句,如 Insert、Update、Delete等
3. 执行远程数据库的存储过程
4. 执行远程数据库的特殊函数、命令
5. 事务管理
SPT 的劣势:
1. 没有图形用户界面
2. 必须人工维护连接
3. 得到的Cursor默认是"可读写"Cursor,要使它成为"可更新"Cursor必须经过设定
下面就顺着我们对 SPT 的认识,来浏览一下这种伟大的工具吧!(注意:本文所有例程均使用 SQL Server的NorthWind 数据库演示)
管理连接:
建立连接:(注意:本文所有示例代码若用到连接的,默认采用"建立连接"代码中产生的连接句柄"con")
WAIT ' 连接到 SQL Server 上去 ' NOWAIT NOCLEAR WINDOW
SQLSETPROP(0,"DispLogin" ,3) && 设置环境为:"从不显示 ODBC 登陆对话框"
con=SQLSTRINGCONNECT("driver=SQL Server;Server=BOE;Uid=sa;pwd=;database=northwind")
*假定 SQL Server 服务器名为 BOE, 用户 ID 是sa, 口令是空串
*如果你的 SQL Server 的服务器名, 用户 ID, 口令与上不同,请修改以上代码中的相关部分以符合你系统中的设置
WAIT clear
IF con<=0
MESSAGEBOX(' 连接失败 ',64,' 连接到 SQL Server 上去 ')
ELSE
MESSAGEBOX(' 连接成功 ',64,' 连接到 SQL Server 上去 ')
ENDIF
断开连接:
SQLDISCONNECT(con)
一次得到多个Cursor
我们可以用一次 SPT 传回多个Cursor,如下:
cSQL="SELECT * FROM EMPLOYEES"+CHR(10)+"SELECT * FROM CUSTOMERS"+CHR(10)+"SELECT * FROM PRODUCTS"
?SQLEXEC(con,cSQL,"TEMP")
SQLEXEC( ) 的返回值表示Cursor的数量,这里返回 3 。这三个Cursor分别以: TEMP,TEMP1,TEMP2 命名。
执行除 SQL-Select 以外的 SQL 语句
cSQL="IF EXISTS(SELECT * FROM CUSTOMERS WHERE CUSTOMERID='TEST')"
cSQL=cSQL+" DELETE FROM CUSTOMERS WHERE CUSTOMERID='TEST'"
cSQL=cSQL+" ELSE INSERT CUSTOMERS(CUSTOMERID,COMPANYNAME) VALUES('TEST',' 这是一个测试! ')"
IF SQLEXEC(CON,cSQL)<=0
MESSAGEBOX(' 执行失败 ',64,' 发送语句到 SQL Server 上去 ')
ELSE
MESSAGEBOX(' 执行成功 ',64,' 发送语句到 SQL Server 上去 ')
ENDIF
行文至此,也许有朋友会问:如果 SQL 语句中 CUSTOMERID 是一个变量怎么办呢?有两个常用的解决方案:
拼接字符串
CUSTID='TEST'
cSQL="IF EXISTS(SELECT * FROM CUSTOMERS WHERE CUSTOMERID='"+CUSTID+"')"
cSQL=cSQL+" DELETE FROM CUSTOMERS WHERE CUSTOMERID='"+CUSTID+"'"
cSQL=cSQL+" ELSE INSERT CUSTOMERS(CUSTOMERID,COMPANYNAME) VALUES('"+CUSTID+"',' 这是一个测试! ')"
?SQLEXEC(CON,cSQL)
SPT 标准变量传递法
CUSTID='TEST'
cSQL="IF EXISTS(SELECT * FROM CUSTOMERS WHERE CUSTOMERID=?CUSTID)"
cSQL=cSQL+" DELETE FROM CUSTOMERS WHERE CUSTOMERID=?CUSTID"
cSQL=cSQL+" ELSE INSERT CUSTOMERS(CUSTOMERID,COMPANYNAME) VALUES(?CUSTID,' 这是一个测试! ')"
?SQLEXEC(CON,cSQL)
执行远程数据库的存储过程
存储过程的好处自是不必多言,下面就让我们看看怎样用 SPT 调用远程数据库的存储过程。下面我们演示的是 NorthWind 数据库中的存储过程" CustOrderHist ",它的作用是返回指定客户关于产品的消费数量合计。据我所知,这里有两种书写格式供大家选择:
使用 T-SQL 的写法:
CUSTID='VINET'
?SQLEXEC(CON,'EXEC CustOrderHist ?CUSTID','TEMP1')
使用 ODBC 的写法:
CUSTID='VINET'
?SQLEXEC(CON,'{CALL CustOrderHist(?CUSTID)}','TEMP2')
存储过程常常会需要返回一些变量,通用的方法就是使用输出参数。在演示之前,我们先用 SPT 在 SQL Server 建立一个包含输入、输出参数的存储过程。
cSQL="IF EXISTS(select * from sysobjects where id=object_id('MY_PROC') and OBJECTPROPERTY(id,'IsProcedure')=1)"
cSQL=cSQL+" drop procedure MY_PROC " &&如果存储过程My_proc已经存在,就删除它
?SQLEXEC(con,cSQL)
cSQL="CREATE PROCEDURE MY_PROC @EmployeeID int,@Desc varchar(100) output as /* 只支持寻找直接下属 */"+chr(10)
cSQL=cSQL+" DECLARE @ROW INT"+chr(10)
cSQL=cSQL+" SELECT * FROM EMPLOYEES WHERE REPORTSTO=@EMPLOYEEID"+chr(10)
cSQL=cSQL+" SELECT @ROW=@@ROWCOUNT"+chr(10)
cSQL=cSQL+" IF @ROW>0"+chr(10)
cSQL=cSQL+" SELECT @Desc=' 找到了 '+CAST(@ROW AS VARCHAR(4)) +' 位下属 '"+chr(10)
cSQL=cSQL+" ELSE SELECT @Desc=' 这是一位普通员工 '"
?SQLEXEC(con,cSQL)
使用 T-SQL 的写法:
EMPID=2
DESCRIPTION=""
?SQLEXEC(CON,'EXEC MY_PROC ?EMPID,?@DESCRIPTION','TEMP1')
?DESCRIPTION
使用 ODBC 的写法:
EMPID=2
DESCRIPTION=""
?SQLEXEC(CON,'{CALL MY_PROC(?EMPID,?@DESCRIPTION)}','TEMP2')
?DESCRIPTION
执行远程数据库的特殊函数、命令
如果在 SQL Server 中你有足够的权限,通过 SPT 你可以完全控制 SQL Server ,这里我们演示"怎样取得数据库服务器的时间":
?SQLEXEC(con,"select getdate() as serverdatetime","temp1")
?temp1.serverdatetime
USE IN ("temp1")
事务管理
在一些复杂的应用中,往往会有一项操作影响好几个表的情况。就客户端来说,发送到远程数据库的数据变动可能来源很多:表缓冲的多行记录的变动,行缓冲的单行记录变化,以及前文我们演示的直接用 SQL 语句传递的数据维护,林林总总……怎样把这些更新行为控制在一个事务中要么--一起成功,要么一起回滚。
cSQL="DELETE FROM CUSTOMERS WHERE CUSTOMERID='BLAUS'"+CHR(10)
cSQL=cSQL+"INSERT CUSTOMERS(CUSTOMERID,COMPANYNAME) VALUES('TEST1',' 这是一个测试! ')"
SQLSETPROP(CON,"Transactions" ,2)&& 开始一个事务
IRETURN=SQLEXEC(CON,cSQL)
IF IRETURN=1
SQLCOMMIT(CON)&& 事务交付
ELSE
SQLROLLBACK(CON)&& 事务回滚
ENDIF
SQLSETPROP(CON,"Transactions" ,1)&& 重新回到自动事务处理状态
&&就本例而言,"DELETE FROM CUSTOMERS WHERE CUSTOMERID='BLAUS'"总是不能执行的,SQL Server会返回错误:
&&DELETE statement conflicted with COLUMN REFERENCE constraint 'FK_Orders_Customers'.
&&The conflict occurred in database 'Northwind', table 'Orders', column 'CustomerID'.
&&所以这笔事务总是被回滚的!!
从例程中我们看到,我们开启的事务其实是针对"连接"的,也就是说通过该"连接"的所有数据更新都包含于事务中,直到事务被回滚或交付。
SQLSETPROP(CON,"Transactions" ,2 ), 其实是开启了人工事务处理,也就是说必须由用户明确的给出交付或者回滚指令,事务才会结束。所以笔者以为:完成一笔事务以后,应执行 SQLSETPROP(CON,"Transactions" ,1 ) 将"连接"的事务模式设为默认的"自动",这样可以防止用户陷入未知的事务中去。
设为可更新
到目前为止,我们已经演示了 6 个 SPT 专题了,除了第一个"连接管理"在远程视图中能够实现之外,其余的远程视图都无法实现。下面我们要讨论一下怎样把 SPT 传回的结果集合设为"可更新",总的来说远程视图提供的就是这个功能。
在默认状况下, SPT 从远程数据库得到的Cursor是"可读写"的,即:可以对它进行" Select 、 Update 、 Insert 、 Delete "的操作,但数据的变化不会反映到数据源。"可更新"Cursor的特色就是可以直接将客户端的数据变动,自动生成一系列 SQL 描述更新远程数据库。
实现"可读写"Cursor到"可更新"Cursor必须经历以下五步的设置:
CURSORSETPROP("TABLES", 数据源表名 , 可更新Cursor名 )
此步骤设定的是数据源里(SQL Server)待更新的表名,如果涉及多个表就这样写: CURSORSETPROP("TABLES","T1,T2","MyCursor") 。
CURSORSETPROP("KEYFIELDLIST", 关键字段 , 可更新Cursor名 )
此步骤是设定关键字段的,这个关键字段是可更新Cursor的字段,而不是数据源里的字段。
CURSORSETPROP("UPDATABLEFIELDLIST", 可更新字段列表 , 可更新Cursor名 )
此步骤设定的是在可更新Cursor里哪些字段的变动要被反映到数据源,即哪些字段时可更新的。
CURSORSETPROP("UPDATENAMELIST", 前后段字段对应关系列表 , 可更新Cursor名 )
此步骤设定数据源字段与可更新Cursor字段的对应关系。
CURSORSETPROP("SENDUPDATES",.T., 可更新Cursor名 )
这个步骤是打开 SQL 发送开关,最关键的一步。
为便于大家理解,现将以上五步实例化:
例一:
SQLEXEC(con,"select categoryid as id ,categoryname,description from categories","mycursor")
SELECT mycursor
CURSORSETPROP("Tables","categories","mycursor")
CURSORSETPROP("KeyFieldList","id","mycursor")
CURSORSETPROP("UpdatableFieldList" ,"id,categoryname,description","mycursor")
CURSORSETPROP("UpdateNameList","id categories.categoryid,categoryname categories.categoryname,"+;
"description categories.description","mycursor")
CURSORSETPROP("SendUpdates" ,.t.,"mycursor")
例二:
SQLEXEC(con,"select a.productid,a.productname,a.unitprice,b.categoryid,b.categoryname,c.supplierid,"+;
"c.companyname as suppliername,c.contactname"+;
" from (products a inner join categories b on a.categoryid=b.categoryid)"+;
" inner join suppliers c on a.supplierid=c.supplierid","mycursor")
SELECT mycursor
CURSORSETPROP("Tables","products,categories,suppliers","mycursor")
CURSORSETPROP("KeyFieldList","productid,categoryid,supplierid","mycursor")
CURSORSETPROP("UpdatableFieldList",+;
"productid,productname,unitprice,categoryid,categoryname,supplierid,suppliername,contactname","mycursor")
CURSORSETPROP("UpdateNameList","productid products.productid,productname "+;
"products.productname,unitprice products.unitprice,"+;
"categoryid categories.categoryid,categoryname categories.categoryname,"+;
"supplierid suppliers.supplierid,suppliername suppliers.companyname,contactname suppliers.contactname","mycursor")
CURSORSETPROP("SendUpdates" ,.t.,"mycursor")
行笔匆匆,终于把我认识的 SPT 基本操作写完了,掌握这些,已能编写不错的 C/S 程序。虽然,本文是用 SQL Server 作为远程数据库,但是如果你使用 DB2 、 Oracle ,在 VFP 中也是一样处理。
VFP 的 SPT 起跳 -- 陈纯(BOE数据网络工作室)的更多相关文章
- 转:Android 判断用户2G/3G/4G移动数据网络
Android 判断用户2G/3G/4G移动数据网络 在做 Android App 的时候,为了给用户省流量,为了不激起用户的愤怒,为了更好的用户体验,是需要根据用户当前网络情况来做一些调整的,也可以 ...
- Android 判断用户2G/3G/4G移动数据网络
Android 判断用户2G/3G/4G移动数据网络 在做 Android App 的时候,为了给用户省流量,为了不激起用户的愤怒,为了更好的用户体验,是需要根据用户当前网络情况来做一些调整的,也可以 ...
- Python数据网络采集5--处理Javascript和重定向
Python数据网络采集5--处理Javascript和重定向 到目前为止,我们和网站服务器通信的唯一方式,就是发出HTTP请求获取页面.有些网页,我们不需要单独请求,就可以和网络服务器交互(收发信息 ...
- jq easyui数据网络的分页过程
第一次写技术方面的文章,有点忐忑,总怕自己讲的不对误导别人.但是万事总有个开头,有不足错误之处,请各位读者老爷指出. 言归正传,最近刚进新公司,上头要求我先熟悉熟悉easyui这个组件库.在涉及到da ...
- JavaScript原生Ajax请求纯文本数据
源代码 ajax1.html: <!DOCTYPE html> <html> <head> <meta charset="utf-8"&g ...
- VFP 用 SPT 来发布一条 SELECT 到一个新的 SQL Server 表
为了发布一条 SQL SELECT 语句来创建一个新的 SQL Server 表, SQL Server 数据库的 select into/bulkcopy 选项必须是可用的. 在默认情况下, 对于 ...
- (转)Android 判断用户2G/3G/4G移动数据网络
在做 Android App 的时候,为了给用户省流量,为了不激起用户的愤怒,为了更好的用户体验,是需(要根据用户当前网络情况来做一些调整的,也可以在 App 的设置模块里,让用户自己选择,在 2G ...
- 通行导论-IP数据网络基础(2)
传输控制协议(TCP) 差错控制:TCP使用差错控制提供可靠性,包括检测受到损伤.丢失.失序的报文段 实现方法:1.16位检验和,2.确认机制:采用确认证实收到的报文段,3.重传(设置一个重传超时RT ...
- 通信导论-IP数据网络基础(1)
TCP/IP封装过程: 端口号:服务器一般都是通过知名端口号(1~1023)来识别应用程序,(TCP)21.23.25,(UDP)53.69.161 TCP报文格式: 字节号:TCP把连接中发送的所有 ...
随机推荐
- 结巴分词 java 高性能实现,是 huaban jieba 速度的 2倍
Segment Segment 是基于结巴分词词库实现的更加灵活,高性能的 java 分词实现. 变更日志 创作目的 分词是做 NLP 相关工作,非常基础的一项功能. jieba-analysis 作 ...
- 数据结构与算法 Python语言实现 第一章练习
说明:部分代码参考了Harrytsz的文章:https://blog.csdn.net/Harrytsz/article/details/86645857 巩固 R-1.1 编写一个Python函数 ...
- 洛谷 UVA11021 Tribles
UVA11021 Tribles 题意翻译 题目大意 一开始有kk种生物,这种生物只能活1天,死的时候有p_ipi的概率产生ii只这种生物(也只能活一天),询问m天内所有生物都死的概率(包括m天前死 ...
- Linux初始化Git环境
第一步:设置Git全局用户名和邮箱 git config --global user.name "你的用户名" git config --global user.email &qu ...
- Java入门 - 面向对象 - 02.重写与重载
原文地址:http://www.work100.net/training/java-override-overload.html 更多教程:光束云 - 免费课程 重写与重载 序号 文内章节 视频 1 ...
- [洛谷P4178] Tree (点分治模板)
题目略了吧,就是一棵树上有多少个点对之间的距离 \(\leq k\) \(n \leq 40000\) 算法 首先有一个 \(O(n^2)\) 的做法,枚举每一个点为起点,\(dfs\) 一遍可知其它 ...
- 【杂项】各类文件头结合winhex使用-转载
———常用文件头——— JPEG (jpg),文件头:FFD8FFE1 PNG (png),文件头:89504E47 (0D0A1A0A) GIF (gif),文件头:47494638 ZIP Arc ...
- 【javaScript】获取某年某月的的最后一天(即当月天数) 妙用
javaScript里 面的new Date("xxxx/xx/xx")这个日期的构造方法有一个妙处,当你传入的是"xxxx/xx/0"(0号)的话,得到的日期 ...
- 使用整体模型模板辅助器 Using Whole-Model Templated Helpers 模板辅助器方法 精通ASP.NET MVC 5
怎么会
- 第一章001-003课程介绍、计算机网络概述、Internet概述
计算机网络概述 课程安排: 第一章:概述 第二章:物理层 第三章:数据链路层 第四章:网络层 第五章:运输层 第六章:应用层 第七章:网络安全 第八章:因特网上的音频/视频服务 第九章:无线网络 第十 ...