VFP是一個簡單實用的編程工具,但數據庫一般用其本身的DBF數據庫,對於SQL可能每個人都用不同的看法,各位高手有什麼好的方法、經驗、建議全都拿出來,供大家分享,謝謝!
以下是我平時對SQL的使用點滴,歡迎各位指點!
**************************************************************
*-- vfpsql
*-- parameters :tcDatabase,tcServer,tcUserId,tcPassword
*-- Sql Connect
**************************************************************
FUNCTION VFPSql()
PARAMETERS tcDatabase,tcServer,tcUserId,tcPassword
SET MULTILOCKS ON
LOCAL lcSqlConnectStr
PUBLIC gcSqlConnectstr
IF TYPE("gcSqlConnectstr")<>"C"
gcSqlConnectstr = ""
ENDIF
lcSqlConnectStr =''
IF TYPE("_gnSqlConnectHandle") = "N" AND _gnSqlConnectHandle > 0
disVFPSql()
ENDIF
PUBLIC _gnSqlConnectHandle
_gnSqlConnectHandle =0
IF PARAMETERS()>=3 AND VARTYPE(tcDatabase)='C' AND VARTYPE(tcServer)='C' AND VARTYPE(tcUserId)='C'
IF EMPTY(tcPassword)
tcPassword = ''
ENDIF
lcSqlConnectStr ="driver={Sql Server};Database=&tcDatabase;server=&tcServer;uid=&tcUserId;pwd=&tcPassword"
_gnSqlConnectHandle = SQLSTRINGCONNECT(lcSqlConnectStr)
ELSE
IF gcSqlConnectstr <> ""
lcSqlConnectStr = gcSqlConnectstr
_gnSqlConnectHandle = SQLSTRINGCONNECT(lcSqlConnectStr)
ENDIF
ENDIF
gcSqlConnectstr = lcSqlConnectStr
LOCAL iLoopCount
iLoopCount = 1
DO WHILE _gnSqlConnectHandle <= 0 AND iLoopCount <=3
DO prgDataConnect
iLoopCount = iLoopCount + 1
ENDDO
RETURN _gnSqlConnectHandle>0
*- VFPSQL END
************************************************
*-- disVFPSql()
*-- Sql Disconnect
************************************************
FUNCTION disVFPSql()
IF TYPE("_gnSqlConnectHandle")<>'N' OR _gnSqlConnectHandle<=0
Return
ENDIF
SQLDISCONNECT(_gnSqlConnectHandle)
RELEASE _gnSqlConnectHandle
RETURN
*- DisVFPSql END
***************************************
*-- SqlDo()
*-- PARAMETERS tcSqlString: 查詢語句
*-- tcCurName : 結果集存放表
*- 功能: 執行查詢
**************************************
FUNCTION SqlDo
LPARAMETERS tcSqlString,tcCurName
LOCAL lnReturn
*-- 檢查連接
IF TYPE("_gnSqlConnectHandle")<>"N" or _gnSqlConnectHandle<=0
IF !vfpsql()
*-- 連接失敗,返回 0
RETURN 0
ENDIF
ENDIF
IF TYPE('tcSqlString')<>'C' OR EMPTY(tcSqlString)
RETURN 0
ENDIF
IF EMPTY(tcCurName) OR TYPE("tcCurName") <>'C'
lnReturn = SQLEXEC(_gnSqlConnectHandle,tcSqlString)
ELSE
lnReturn = SQLEXEC(_gnSqlConnectHandle,tcSqlString,tcCurName)
ENDIF
RETURN lnReturn
*- SqlDo END
*-- Opendb
********************************************************
*-- OpenDB
*
*- 功能:打開table Or View
*
*-參數說明 tcCurName : 臨時表名
*- tcSqlTblName: Sql數據表名(默認tcCurName)
* tcFldsList : 字段列表(默認爲全部 *)
**********************************************************
FUNCTION OpenDB
LPARAMETERS tcCurName,tcSqlTblName,tcFldsList
IF VARTYPE(tcCurName)<>"C"
MESSAGEBOX("參數錯誤,不能打開數據表或視圖!",48,"錯誤")
RETURN .F.
ENDIF
IF EMPTY(tcSqlTblName)
tcSqlTblName = tcCurName
ENDIF
IF EMPTY(tcFldsList)
tcFldsList = "*"
ENDIF
*-- 執行查詢
lnReturn = Sqldo("select &tcFldsList From &tcSqlTblName ","&tcCurName.")
*-- 打開開放式表緩衝
IF lnReturn > 0
cursorsetprop("buffering",5, "&tcCurName.")
ENDIF
RETURN lnReturn>0
*-- OpenDB END
********************************************************
*-- CloseDB
*
*- 功能:關閉table Or View
*
*-參數說明 tcCurName : 臨時表名
*-
**********************************************************
FUNCTION CloseDB
LPARAMETERS tcCurName
IF VARTYPE(tcCurName)<>"C"
MESSAGEBOX("參數錯誤,非法數據表或視圖!",48,"錯誤")
RETURN .F.
ENDIF
IF SELECT(tcCurName) > 0
USE IN &tcCurName
ENDIF
RETURN .T.
*-- CloseDB END
**********************************************************************
*- SaveData
*--
*-- 參數說明 tcCurName : 臨時表名
*-- tcSqlTblName: Sql數據表名(默認tcCurName)
*-- tcKeyFld : 關鍵字段名稱(一定要有,否則默認爲ID),
*-- 程序要根據該字段來定位記錄,
*-- 功能:從臨時表保存到 SQL數據庫中,注意,臨時表要啓用緩衝模式
**********************************************************************
FUNCTION savedata
LPARAMETERS tcCurName,tcSqlTblName,tcKeyFld
LOCAL lcFldList,lcFldListTmp,lnNextModifyRec,lcFldValue,lnFldCnt
LOCAL lcSqlString,lnSqlReturn
LOCAL lcOldAlias,lcOldDele,lcOldSafe
LOCAL laFlds(1)
LOCAL i,llNeedAddDH
IF TYPE("tcCurName")<>'C' OR ;
EMPTY(tcCurName) OR ;
SELECT(tcCurName)=0
MESSAGEBOX("表名錯誤!",48,"錯誤提示")
RETURN
ENDIF
lcOldDele = SET("Deleted")
lcOldSafe = SET("Safety")
SET DELETED OFF
SET SAFETY OFF
lcOldAlias = ALIAS()
SELECT (tcCurName)
IF cursorgetprop('buffering')=1
MESSAGEBOX("數據表沒有啓用緩衝模式,程序無法識別!",48,"錯誤提示")
RETURN .F.
ENDIF
IF EMPTY(tcSqlTblName) OR TYPE("tcSqlTblName")<>'C'
tcSqlTblName = tcCurName
ENDIF
IF EMPTY(tcKeyFld)
tcKeyFld = 'Id'
ENDIF
lnFldCnt = AFIELDS(laFlds)
lcFldList = ''
FOR i = 1 TO lnFldCnt
IF !EMPTY(lcFldList)
lcFldList = lcFldList +','
ENDIF
lcFldList = lcFldList + laFlds(i,1)
ENDFOR
lnNextModifyRec = GETNEXTMODIFIED(0)
lnSqlReturn = SQLDO("BEGIN TRANSACTION")
IF lnSqlReturn =-1
RETURN .F.
ENDIF
DO WHILE lnNextModifyRec <>0
GO lnNextModifyRec
lcFldListtmp = ''
lcSqlString = ''
DO case
CASE DELETED() &&刪除
lcSqlString = "Delete From "+tcSqlTblName + IIF(TYPE(tcKeyFld) $ "NI",;
" Where &tcKeyFld. = "+TRANSFORM(EVALUATE(tcCurName+".&tcKeyFld.")),;
" Where &tcKeyFld. = '"+TRANSFORM(EVALUATE(tcCurName+".&tcKeyFld."))+"'")
CASE RECNO()<0 &&新增
lcSqlString = "" &&"insert into "+tcSqlTblName +" ("+lcFldList+") Values ("
FOR i = 1 TO lnFldCnt
lcFldValue = EVALUATE(tcCurName+'.'+laFlds(i,1))
IF EMPTY(lcFldValue)
Loop
ENDIF
IF !EMPTY(lcFldListtmp)
lcFldListtmp = lcFldListtmp +','
ENDIF
lcFldListtmp = lcFldListtmp + laFlds(i,1)
DO case
CASE laFlds(i,2)$'NI'
lcFldValue = TRANSFORM(lcFldValue)
CASE laFlds(i,2)='L'
IF lcFldValue
lcfldValue = '1'
ELSE
lcfldValue = '0'
ENDIF
OTHERWISE
lcFldValue ="'"+ TRANSFORM(lcFldValue)+"'"
ENDCASE
IF !EMPTY(lcSqlString)
lcSqlString =lcSqlString +','
ENDIF
lcSqlString =lcSqlString + lcFldValue
ENDFOR
lcSqlString ="insert into "+tcSqlTblName +" ("+lcFldListtmp+") Values ("+lcSqlString +')'
OTHERWISE
lcSqlString = "Update "+tcSqlTblName +" set "
llNeedAddDH = .F.
FOR i = 1 TO lnFldCnt
IF GETFLDSTATE(i)<>2
LOOP
ENDIF
DO case
CASE laFlds(i,2)$'NI'
lcFldValue = laflds(i,1)+'='+TRANSFORM(EVALUATE(tcCurName+'.'+laFlds(i,1)))
CASE laFlds(i,2)='L'
IF EVALUATE(tcCursorName+'.'+laFlds(i,1))
lcfldValue = laflds(i,1)+'=1'
ELSE
lcfldValue = laflds(i,1)+'=0'
ENDIF
OTHERWISE
lcFldValue =laflds(i,1)+" = '"+ TRANSFORM( EVALUATE(tcCurName+'.'+laFlds(i,1)))+"'"
ENDCASE
IF NOT llNeedAddDH
llNeedAddDH = .T.
Else
lcSqlString =lcSqlString +','
ENDIF
lcSqlString =lcSqlString + lcFldValue
ENDFOR
lcSqlString =lcSqlString + IIF(TYPE(tcKeyFld) $ "NI",;
" Where &tcKeyFld. = "+TRANSFORM(EVALUATE(tcCurName+".&tcKeyFld.")),;
" Where &tcKeyFld. = '"+TRANSFORM(EVALUATE(tcCurName+".&tcKeyFld."))+"'")
ENDCASE
lnSqlReturn = sqlDo(lcSqlString)
IF lnSqlReturn=-1
SQLDO("Rollback")
MESSAGEBOX("保存失敗!"+CHR(13)+CHR(10)+lcSqlString)
_cliptext = lcSqlstring
TABLEREVERT(.T.)
RETURN .F.
ENDIF
lnNextModifyRec = GETNEXTMODIFIED(lnNextModifyRec)
ENDDO
lnSqlReturn = SQLDO(" COMMIT TRANSACTION ")
TABLEUPDATE(.T.)
IF !EMPTY(lcOldAlias) AND ALIAS()<>lcOldAlias
SELECT (lcOldAlias)
ENDIF
IF lcOldDele='ON'
SET DELETED on
ENDIF
IF lcOldSafe = 'ON'
SET SAFETY on
ENDIF
RETURN .T.
*-- 調用SQL設置
PROCEDURE prgdataconnect
LOCAL lcDataPath,lcTextString
LOCAL loForm
_gnSqlConnectHandle = 0
IF _gnSqlConnectHandle <=0
loFormCon = CREATEOBJECT("fmConnect")
loFormCon.Show(1)
ENDIF
**********************************************
*- 定義連接表單
*********************************************
DEFINE CLASS fmConnect as Form
&&-"driver={Sql Server};Database=Hjasset;server=jianqy;uid=sa;pwd=")
Caption = 'SQL服務器連接'
Width = 300
Height = 182
BorderStyle = 2
maxbutton = .F.
minButton = .F.
ShowWindow = 1
Autocenter = .T.
alwaysontop = .T.
desktop = .T.
windowType = 1
Name = 'fmConnect'
ADD OBJECT lblServer as label WITH Caption = '服務器:',;
left = 15,;
Width = 80,;
Top = 20,;
Height= 22,;
Alignment = 1,;
BackStyle = 0,;
Visible = .T.
ADD OBJECT txtServer as textbox WITH left = 95,;
Width = 160,;
Top = 17,;
Height= 25,;
Value = '(Local)',;
Visible = .T.
ADD OBJECT lblDatabase as label WITH Caption = '數據庫:',;
left = 15,;
Width = 80,;
Top = 50,;
Height= 22,;
Alignment = 1,;
BackStyle = 0,;
Visible = .T.
ADD OBJECT txtDatabase as textbox WITH left = 95,;
Width = 160,;
Top = 47,;
Height= 22,;
Value = 'pubs' ,;
Visible = .T.
ADD OBJECT lblUid as label WITH Caption = '用戶名:',;
left = 15,;
Width = 80,;
Top = 80,;
Height= 22,;
Alignment = 1,;
BackStyle = 0,;
Visible = .T.
ADD OBJECT txtUid as textbox WITH left = 95,;
Width = 160,;
Top = 77,;
Height= 22,;
Value = 'sa' ,;
Visible = .T.
ADD OBJECT lblPwd as label WITH Caption = '密 碼:',;
left = 15,;
Width = 80,;
Top = 110,;
Height= 22,;
Alignment = 1,;
BackStyle = 0,;
Visible = .T.
ADD OBJECT txtPwd as textbox WITH left = 95,;
Width = 160,;
Top = 107,;
Height= 22,;
passwordchar= '*',;
Value = '' ,;
Visible = .T.
ADD OBJECT btnConnect as Commandbutton WITH left = 35,;
Width = 100,;
Top = 150,;
Height= 22,;
Value = '' ,;
Visible = .T.,;
Caption = '連 接'
ADD OBJECT btnCancel as Commandbutton WITH left = 165,;
Width = 100,;
Top = 150,;
Height= 22,;
Value = '' ,;
Visible = .T.,;
Caption = '取 消'
ADD OBJECT cntShp1 as Container WITH left = 0,;
Width = 300,;
Top = 142,;
Height= 1,;
Visible = .T.,;
SpecialEffect = 1
PROCEDURE destroy
PROCEDURE load
PROCEDURE init
PROCEDURE txtPwd.Keypress
LPARAMETERS nKeyCode, nShiftAltCtrl
IF nKeyCode = 3 OR nKeyCode = 24
NODEFAULT
ENDIF
PROCEDURE btnConnect.click
LOCAL lcServer,lcDatabase,lcUid,lcPwd,lcSqlStrConnect
lcServer = ALLTRIM(thisform.txtServer.Value)
lcDatabase = ALLTRIM(thisform.txtDatabase.Value)
lcUid = ALLTRIM(thisform.txtUid.Value)
lcPwd = ALLTRIM(thisform.txtPwd.Value)
IF EMPTY(lcServer)
MESSAGEBOX("請輸入SQL服務器的名稱!",48,"系統提示")
thisform.txtServer.setfocus
RETURN
ENDIF
lcSqlStrConnect="driver={Sql Server};"+;
IIF(EMPTY(lcDatabase),'',"Database="+lcDatabase+";")+;
"server="+lcServer+";uid="+lcUid+";pwd="+lcPwd
_gnSqlConnectHandle = SQLSTRINGCONNECT(lcSqlStrConnect)
gcSqlConnectstr = lcSqlStrConnect
ON ERROR
IF _gnSqlConnectHandle > 0
MESSAGEBOX("連接成功!",64,"SQL連接")
thisform.Release
ELSE
MESSAGEBOX("連接失敗!"+CHR(13)+CHR(10)+"請重新輸入連接項",64,"SQL連接")
thisform.txtServer.setfocus
ENDIF
PROCEDURE btnCancel.click
thisform.Release
ENDDEFINE
*-- 以pubs 爲例
&&連接SQL
vfpsql()
&&打開authors
opendb("authors")
&&修改
repl au_lname with 'dfjkdjfkdj' for au_id = '722-51-5454'
&&保存到SQL
savedata("authors","authors","au_id")
****************************************************************
9 樓westbulls(westbulls)回覆於 2005-07-07 10:37:06 得分 30
一、採用ado方式,可能代碼會很多,但是會很靈活,而且可以讓前臺沒有dbc文件了,如果採用遠程視圖方式,就不會寫這麼多代碼了,但是必須要定義DBC(數據庫)用於保存遠程視圖及連接等,前一種方式,樓主已經寫的比較通俗簡明瞭,我對後一種簡單補充一下。
1.建立數據庫,然後建立與後臺數據庫的連接(當然可以用INI文件等先保存定義,然後動態設定)
2.建立遠程視圖表
3.打開表同時設置表的緩衝級別,一般爲行緩衝和表緩衝
4.修改後如果保存=tableupdate(.t.),放棄=tablerevert(.t.)
5.可以通過捕獲返回的錯誤信息,顯示給用戶.如
IF TABLEUPDATE(.T.)
=MESSAGEBOX("更新完畢!",48,"系統信息")
ELSE
=AERROR(ERR)
IF !EMPTY(ERR(2))
=SHOWWARN("錯誤信息:"+ALLTRIM(SUBSTR(UPPER(ERR(2)),AT('[SQL SERVER]',UPPER(ERR(2)),1)+12)))
ENDIF
XX=SHOWCHOICE("是否取消此次操作?")
IF XX=1
=TABLEREVERT(.T.)
ELSE
RETURN
ENDIF
ENDIF
=REQUERY()
二、當然了,現在最好使用的還是CursorAdapter方式,它即有了ado方式的靈活,又有了遠程視圖的簡易,還有了後臺升級方便等優點。具體使用請參考網上的一些說明,在些從簡了。
13 樓cxmcxm(小陳)回覆於 2005-07-09 23:00:39 得分 10
用遠視圖.或vfp中提供的odbc方法,
如果用ado,最好用CursorAdapter.
不用直接用createobject創建ado對象,因爲vfp不能直接支持ado的recordset,只能自增麻煩.
就我的體驗,遠程視圖,cursoradapter中,odbc或ado的遊標已被轉爲vfp的臨時表,我們在vfp操作
轉化來臨時表,用vfp提供的命令對臨時表更新時,vfp內部再更新相應的odbc或ado遊標.因爲多了一層,所以有自增列的表添加記錄時,自增列的值在vfp中看不到,需requery後才能看到.
回覆人: westbulls(westbulls) ( ) 信譽:100 2005-07-17 21:29:00 得分: 0
TO Hewiit(糊塗):
我的SQL的數據庫的記錄數有好幾百萬,在VFP中查詢都經常超時,有沒有什麼好的法子?
其實表結構的設計是否合理是最主要的關鍵,還有索引的運用及查詢條件的設置,都會加快運行速度,總之,要具體問題具體分析纔可以.
另外,發現使用9.0中的VARCHAR時,控件的MAXLENGTH就不可用了,而且如果你第一次存儲的數據長度定了後,第二次錄入到第一次長度後,光標會自動跳走,所以我暫時不打算用VARCHAR了.
-----------------------------------------------
非常感謝!!!!!!!!!!
29 樓trainee(春泥)回覆於 2005-07-23 10:25:15 得分 25
CURSORADAPTER(故名思義:叫CURSOR的裝配器)
CURSORADAPTER和SPT生成的CURSOR是一樣的,
SPT生成的CURSOR也可通過CURSORSETPROP語句變成可更新的CURSOR
SPT生成的CURSOR可以通過CURSORADAPTER中的CURSORATTACH方法裝配到CURSORADAPTER中
CURSORADAPTER中生成的CURSOR也可以通過的CURSORDETACH方法遊離出來。
CURSORADAPTER技術比SPT擴展了,能處理AOD和XML,並增加了些事件,並可以自行改寫在TABLEUPDATE時由系統自動生成的SQL語句。
直到VFP9.0,兩者才增加了SQLROWCOUNT功能,即能返回UPDATE或DELETE語句所影響的行數。
32 樓trainee(春泥)回覆於 2005-07-25 10:49:42 得分 5
TO Hewiit(糊塗):
---------------------------------------------------------------------
爲知道sql中怎麼去鎖定一條記錄或者一個表,像lock(),rlock()這些命令?
---------------------------------------------------------------------
在服務器型數據庫裏是沒有以上命令的,各種數據庫都有相應的鎖的SQL語句
爲了保證併發性,儘可能減少從加瑣到解鎖之間的時間,中間不能有任何交互型的語句。
一般採取緩衝比較技術
比如修改表中的某一記錄,以下的流程
SELECT * FROM MYTABLE WHERE KEY=SOME
--- 進行交互型修改,中間你可以上廁所、吃飯等
開始提交時:
1。先加鎖
BEGIN TRANSACTION
LOCK TABLE MYTABLE
2、比較是否被更改
A、SELECT * FROM MYTABLE WHERE KEY=SOME
IF EOF() OR 被修改的字段<>OLDVALUE(被修改的字段)
原記錄被他人修改了或刪掉了
ROLLBACK
RETURN -1
ENDIF
UPDATE MYTABLE SET 被修改的字段=新值 WHERE KEY=SOME
B、或者直接用一條語句,VFP9.0以上
UPDATE MYTABLE SET 被修改的字段=新值 WHERE KEY=SOME AND 被修改的字段=OLDVALUE(被修改的字段)
IF SQLROWCOUNT=0 原記錄被他人修改了或不存在了
ROLLBACK
RETURN -1
ENDIF
3、COMMITTop
33 樓trainee(春泥)回覆於 2005-07-25 11:36:06 得分 5
其實SPT和遠程試圖、CURSORADAPTAR中的TABLEUPDATE單條記錄,用的就是以上技術,他封裝的是B方法,即
UPDATE MYTABLE SET 被修改的字段=新值 WHERE KEY=SOME AND 被修改的字段=OLDVALUE(被修改的字段)
只是前後不會加上BEGIN、LOCK、COMMIT,由客戶自行決定,其實若是更新一條記錄,服務器若處於AUTOCOMMIT模式,一條SQL語句前後也不必加。
若TABLEUPDATE多條記錄,與單條一樣,一條條生成。
在SPT中,你可以用CURSETPROP("WhereType")來控制自動生成的UPDATE語句中的WHERE子句
1、值爲1(DB_KEY )或者TABLEUPDATE(0, FORCE),只生成 WHERE KEY=SOME,表示無條件更新
2、值爲2(DB_KEYANDUPDATABLE ),生成 WHERE KEY=SOME AND 可更新的字段=OLDVALUE(可更新的字段) AND 。。。。表示只要有一可更新字段被別人更改,就無法更新
3、值爲3(DB_KEYANDMODIFIED ) 默認,生成 WHERE KEY=SOME AND 被修改的字段=OLDVALUE(被修改的字段) AND 。。。 表示只要有一被改字段被別人更改,就無法更新
4、值爲4(DB_KEYANDTIMESTAMP ) 生成 WHERE KEY=SOME AND TIMESTAMP字段=OLDVALUE(TIMESTAMP),適應於某些特定的數據庫,根據時間戳對比。