程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> Delphi中的SQL編程

Delphi中的SQL編程

編輯:Delphi

SQL語言作為關系數據庫管理系統中的一種通用的結構查詢語言,已經被眾多的數據庫管理系統所采用,如ORACLE、Sybase、Informix等數據庫管理系統,它們都支持SQL 語言。Delphi與使用SQL語言的數據庫管理系統兼容,在使用Delphi開發數據庫應用程序時,我們可以使用SQL語言編程,支持SQL編程是Delphi的一個重要特征,這也是體現Delphi作為一個強大的數據庫應用開發工具的一個重要標志。 

17.1 SQL語言簡介 

17.1.1 SQL的歷史 

在70年代初,E.E.Codd首先提出了關系模型。70年代中期,IBM公司在研制 SYSTEM R關系數據庫管理系統中研制了SQL語言,最早的SQL語言(叫SEQUEL2)是在1976 年 11 月的IBM Journal of R&D上公布的。1979年ORACLE公司首先提供商用的SQL,IBM公司在DB2 和SQL/DS數據庫系統中也實現了SQL。

1986年10月,美國ANSI采用SQL作為關系數據庫管理系統的標准語言(ANSI X3. 135-1986),後為國際標准化組織(ISO)采納為國際標准。1989年,美國ANSI采納在ANSI X3.135-1989報告中定義的關系數據庫管理系統的SQL標准語言,稱為ANSI SQL 89, 該標准替代ANSI X3.135-1986版本。該標准為下列組織所采納:

● 國際標准化組織(ISO),為ISO 9075-1989報告“Database Language SQL With Integrity Enhancement”

● 美國聯邦政府,發布在The Federal Information Processing Standard Publication(FIPS PUB)127

目前,所有主要的關系數據庫管理系統支持某些形式的SQL語言,大部分數據庫打算遵守ANSI SQL89標准。 

17.1.2 SQL的優點

SQL廣泛地被采用正說明了它的優點。它使全部用戶,包括應用程序員、DBA管理員和終端用戶受益非淺。

(1) 非過程化語言

SQL是一個非過程化的語言,因為它一次處理一個記錄,對數據提供自動導航。SQL允許用戶在高層的數據結構上工作,而不對單個記錄進行操作,可操作記錄集。所有SQL 語句接受集合作為輸入,返回集合作為輸出。SQL的集合特性允許一條SQL語句的結果作為另一條SQL語句的輸入。

SQL不要求用戶指定對數據的存放方法。這種特性使用戶更易集中精力於要得到的結果。所有SQL語句使用查詢優化器,它是RDBMS的一部分,由它決定對指定數據存取的最快速度的手段。查詢優化器知道存在什麼索引,哪兒使用合適,而用戶從不需要知道表是否有索引,表有什麼類型的索引。

(2) 統一的語言

SQL可用於所有用戶的DB活動模型,包括系統管理員、數據庫管理員、應用程序員、決策支持系統人員及許多其它類型的終端用戶。基本的SQL 命令只需很少時間就能學會,最高級的命令在幾天內便可掌握。

SQL為許多任務提供了命令,包括:

● 查詢數據

● 在表中插入、修改和刪除記錄

● 建立、修改和刪除數據對象

● 控制對數據和數據對象的存取

● 保證數據庫一致性和完整性

以前的數據庫管理系統為上述各類操作提供單獨的語言,而SQL 將全部任務統一在一種語言中。

(3) 是所有關系數據庫的公共語言

由於所有主要的關系數據庫管理系統都支持SQL語言,用戶可將使用SQL的技能從一個RDBMS轉到另一個。所有用SQL編寫的程序都是可以移植的。

17.2 TQuery部件在SQL編程中的運用

在Delphi中是通過TQuery部件來實現對SQL語言支持的,也就是說用Delphi 開發數據庫應用程序時,使用SQL語言操作數據庫中的數據的唯一途徑是經過TQuery部件。 TQuery部件在Delphi中使用SQL語言編程時占居著絕對重要的地位。在使用Delphi 開發的數據庫應用中,可以使用SQL語言訪問下列三個方面的數據庫:

● Paradox或dBASE數據庫中的表

在訪問這些桌面數據庫系統中的數據時,只能使用ANSI標准的SQL語言中的部分SQL 語句,它們主要包括:Select、Insert、Update和Delete語句;即本地SQL語句。有關詳細情況請參見附錄“局部SQL語句的使用”。

● 本地InterBase數據庫服務器中的數據庫

在InterBase數據庫中支持的SQL語句, 在Delphi中都可以使用。有關InterBase中SQL語句的語法和限制,請參看“InterBase的語言參考”。

● 遠程數據庫服務器中的數據庫

當然這要求在Delphi中必須安裝相應的SQL Link。只要是數據庫服務器上的DBMS支持的SQL語句,在Delphi中都可以使用。有關語法及限制請參看相關的數據庫管理 系統的文檔。

值得一提的是,Delphi還支持異構查詢,即可以同時查詢多個數據庫服務器中相同的或不同類型的數據庫表,例如查詢的數據可以是來自ORACLE數據庫中的表和Sybase數據庫中的表或者其它多個數據庫中的表。

17.2.1 TQuery部件的使用

TQuery部件是一個數據集部件,它在Delphi部件選擇板上的數據訪問頁(Data Access)上,它與TTable部件具有很多共同的特性,我們在第十五章“數據訪問部件的應用及編程”中較詳細地進行了介紹。 TQuery 部件在 SQL 編程中占居了十分重要的地位。 它實現了Delphi對SQL語言的支持,在Delphi開發的數據庫應用中,SQL語句是通過TQuery部件傳遞到要訪問的數據庫系統的數據庫引擎中,由數據庫引擎具體執行SQL語句,以實現對數據的操作,而不是傳遞給Delphi中的BDE,由BDE實施具體的SQL動作。

我們已經知道了TTable部件在訪問數據庫時已經具備很強大的功能。TTable部件通過Delphi內置的BDE可以實現對各種數據庫系統的訪問,然而TQuery部件提供了一些 TTable部件不具備的功能,它們是:

●多表聯接查詢

●復雜的嵌套查詢(Select中包含著Select子查詢)

●明確需要按SQL語言進行的操作

因為TTabel部件不能使用SQL語言,而在TQuery部件可以使用SQL語言,因而TQuery部件也就具備了強大的關系查詢能力。當然這也使數據庫應用程序本身變得更復雜了。

在Delphi應用程序中編寫和使用的SQL語句有兩種:即靜態SQL語句、動態SQL 語句。靜態SQL語句是在程序設計階段,將SQL命令文本作為TQuery部件的SQL屬性值設置。而動態SQL語句編程是SQL語句中包含一系列的參數,在程序運行過程中各參數值是可變的,即可以動態地給SQL語句中的參數賦值。

靜態方式是把SQL命令文作為TQuery部件的SQL屬性值進行設置,這樣,當執行應用程序時,Delphi便執行TQuery部件SQL屬性中設置的SQL命令。如果是SQL中的查詢命令,把TQuery部件通過TDataSource部件與數據控制部件相連,查詢的結果將會顯示在與 TQuery部件相連接的數據浏覽部件中。動態SQL語句是指SQL語句中包含一些參數變量,在程序中可以為這些參數賦值,在程序運行過程中,各個參數值是變化的。TQuery部件的SQL 屬性中的SQL語句的編寫也有兩種方法,一種方法是在程序設置階段便將相應的SQL語句寫入到TQuery的SQL屬性中,另一種方法是在Delphi開發的應用程序將SQL語句,包含在Pascal代碼單元中。

在SQL編程中使用TQuery部件的具體方法步驟如下:

①為TQuery部件設置DatabaseName屬性,它可以是用BDE建立的數據庫的別名,或桌面數據庫系統中的目錄名或數據庫服務器中的文件名,如果在應用中使用了TDatabase 部件,那麼TQuery部件的DatabaseName屬性可以是TDatabase部件中定義的一個數據庫別名。詳細情況請參看“TDatabase部件的使用”;

②為TQuery部件設置SQL屬性,TQuery部件的SQL屬性值就是應用程序要執行的SQL 命令文本,設置SQL屬性有兩種方法:

● 在程序設計過程中,我們可以通過對象浏覽器(Object Inspector)編輯SQL屬性在Object Inspector中選擇SQL屬性,這樣會打開String List Editor窗口,在其中我們便可以編寫SQL命令,我還可以打開Visual Query Builder來編寫SQL命令 (只有Delphi的客戶/服務器版本才具有這一工具)。

● 將SQL命令包含在Pascal代碼單元中

在程序運行過程中,首先調用TQuery部件的Close方法關閉當前的TQuery部件,然後調用Clear方法清除SQL屬性,並說明新的SQL命令文本,然後調用Add方法,將新的SQL命令文本加入到SQL屬性中。

③通過調用TQuery部件的Open方法或ExecSQl方法執行 SQL 命令。 Open 方法只執行Select命令,ExecSQL方法還可以執行其它的SQL命令。Open方法和ExecSQL 方法的區別我們在後面的章節裡會進一步地加以討論的。

如果使用動態SQL語句,首先調用prepare方法,給動態SQL語句中的參數賦值,然後再調用Open方法或ExecSQL方法。調用propare 方法並不是必須的,但是對於要多次執行TQuery部件中SQL屬性中的動態SQL語句,調用Prepare可以大大提高TQuery部件執行SQL語句的性能。 

17.2.2 在TQuery部件中編寫簡單的SQL查詢命令 

在這一節裡我們將學習如何使用TQuery部件編寫簡單的SQL查詢命令,並在Delphi 應用程序中實現SQL查詢。

例如,如果我們想查詢出表Customer.DB中客戶的編號和公司名稱,我們按下列步驟來實現:

①在應用窗體中放置一個TQuery部件、一個TDataSource部件一個TDataGrid部件,並將它們連接起來 

②設置窗體TQuery 部件Query1的DatabaseName屬性值為DBDEMOS

③雙擊Object Inspector窗口中Query1的SQL 屬性, Delphi 將顯示 String List Editor窗口。

④在圖17.3中的窗口中輸入SQL語句:

Select CustNo,Company From Custormer;

⑤單擊OK按鈕,關閉String List Editor窗口。

⑥設置Query的Open屬性為True。

17.3 SQL語言編程概述 

在Delphi應用程序中的SQL命令語句是包含在TQuery部件的SQL屬性中,TQuery部件的SQL屬性是TString類型的,也就是說SQL屬性值是一個字符串列表,這個字符串列表非常類似於一個字符串類型的數組,有關TString類型的信息請參看聯機幫助。在前一節裡我們介紹了TQuery部件可以執行兩種SQL語句:

● 靜態SQL語句

● 動態SQL語句 

靜態SQL語句在程序設計時便已固定下來,它不包含任何參數和變量,例如下面的語句便是一條靜態SQL語句: 

Select * From Cusromer Where CustNo = 1234; 

而動態SQL語句,也被稱作參數化的語句,在其中間包含著表示字段名或表名的參數,例如下面的語句是一條動態SQL語句: 

Select * From Customer Where CustNo =: Number;

其中的變量Number便是一個參數變量,它由一個冒號引導,在程序運行過程中,必須要為該參數賦值,該條SQL語句才能正確執行,每次運行應用程序時可以為該參數變量賦予不同的值。

17.3.1 SQL命令文本的編寫

1. 使用String List Editor編寫

我們要為TQuery部件的SQL屬性設置SQL命令文本時,可以在應用窗體中選擇TQuery部件且雙擊Object Inspector窗口中的SQL屬性,這樣便打開了String List Editor 窗口,在該窗口中我們便可以編寫各種SQL命令,如圖17.3所示。

在編寫完適當的SQL語句之後,選擇 OK 按鈕便可以將編輯器中的 SQL 命令文裝入到TQuery部件的SQL屬性中,選擇SAVE按鈕可以將編寫好的SQL命令保存到一個文件中供以後編程時使用。我們在編寫SQL命令文本時還可以選擇Load按鈕從一個 SQL 命令文件中調入SQL命令。在程序運行過程中,要想設置TQuery部件的SQL屬性,必須首先調用Close方法,關閉TQuery部件,然後再調用Clear方法清除SQL屬性中現存的SQL命令語句,最後再調用Add方法為SQL屬性設置新的SQL命令語句。例如:

Query1.Close {關閉Query1)
Query1.SQL.Clear {清除SQL屬性中的SQL命令語句}
Query1.SQL.Add('Select * From Country');
Query1.SQL.Add('Where Name ="ARGENTINA" ');

在為TQuery部件設置SQL屬性時調用Close方法總是很安全的,如果TQuery部件已經被關閉了,調用Close方法時不會產生任何影響。在應用程序中為SQL屬性設置新的SQL 命令語句時,必須要調用Clear方法以清除SQL屬性中現存的SQL命令語句,如果不調用Clear方法,便調用Add方法向SQL屬性中設置SQL命令語句,那麼新設置的SQL命令語句會追加在現存SQL命令語句後面,在程序運行時常常會出現出乎意料的查詢結果甚至程序無法運行下去。

在這裡要特別注意的,一般情況下TQuery部件的SQL屬性只能包含一條完整的SQL語句,它不允許被設置成多條SQL語句。當然有些數據庫服務器也支持在TQuery部件的SQL屬性中設置多條SQL語句,只要數據庫服務器允許這樣,我們在編程時可以為 SQL 屬性設置多條SQL語句。

2. 使用Visual Query Builder編寫

客戶/服務器版本的Delphi還包含一個可視化的查詢構造器Visual Query Builder ,用這個可視化的工具我們只能編寫Select語句。在應用程序窗體中選擇TQuery部件後,單擊鼠標右鍵,彈出一個彈出式菜單,從中選擇Run Visual Query Builder後便會彈出一對話框提示你選擇要訪問的數據庫,選擇想要訪問的數據庫之後選擇OK按鈕,緊接著會出現一個彈出式對話框提示你選擇要查詢的數據庫表,一次可以選擇多個數據庫表,若要選擇多個數據庫表,每選擇一個表之後單擊Add按鈕,接著選擇另一個表,選擇完要查詢的表之後單擊Close按鈕,這樣,可視化的查詢構造器中將會顯示出用戶選擇的數據庫表。  

有關如何使用可視化的查詢構造器Visual Query Builder 請參看聯機幫助信息, 在Visual Query Builder中構造完一個查詢並退出Visual Query Builder時,其中的SQL 命令語句會自動地寫入相應的TQuery部件的SQL屬性。 

17.3.2 SQL程序的執行 

在為TQuery部件設置完SQL屬性的屬性值之後,也即編寫好適當的SQL程序之後,可以有多種方式來執行SQL程序。

在設計過程中,設置完TQuery部件的SQL屬性之後將其Active屬性的值置為True,這樣便可以執行SQL屬性中的SQL程序,如果應用中有與TQuery部件相連的數據浏覽部件( 如TDDGrid TDBEdit等)那麼在這些數據浏覽部件中會顯示SQL程序的執行結果。

在應用程序運行過程中,通過程序調用TQuery部件的Open方法或ExecSQL 方法可以執行其SQL屬性中的SQL程序。Open方法和ExecSQL方法是不一樣的。大家在程序設計過程中一定要注意。Open方法只能用來執行SQL語言的查詢語句(Select命令),並返回一個查詢結果集,而ExecSQL方法還可以用來執行其它常用的SQL語句(如Insert、UPDATE、 DELETE等命令)例如:

Query1.Open (這樣會返回一個查詢結果集)

如果調用Open方法,而沒有查詢結果時,會出錯。此時應該調用ExecSQL 方法來代替Open方法。如:

Query1.ExecSQL (沒有返回結果)

當然在設計應用程序時,程序設計人員是無法確定TQuery部件中的SQL 語句是否會返回一個查詢結果的。對於這種情況應當用Try…Except模塊來設計程序。在 Try 部分調用Open方法,而在Except部分調用ExceSQL方法,這樣才能保證程序的正確運行。

例如:

Try
Query1.Open
Except
Query1.ExecSQL
End

在應用程序中使用TQuery部件時,還可以設置它的UniDirectional屬性為True,這樣會加快檢索數據庫表的速度,但是這樣只能往一個方向移動記錄指針, 在缺省情況下,UniDirectional屬性的值為False。

17.3.3 通過TQuery部件如何獲得活動的數據

我們在前面的章節裡介紹TTable部件時,我們知道通過TTable部件從數據庫中獲得的數據都是活動的,也就是說用戶可以直接通過數據浏覽部件對這些數據進行編輯修改。而通過TQuery部件可以獲得兩種類型的數據:

● “活動”的數據

這種數據就跟通過TTable部件獲得的數據一樣,用戶可以通過數據浏覽部件來編輯修改這些數據,並且當調用Post方法或當焦點離開當前的數據浏覽部件時,用戶對數據的修改自動地被寫回到數據庫中,詳細情況請參看第四章“數據浏覽部件的應用及編程”。

● 非活動的數據(只讀數據)

用戶通過數據浏覽部件是不能修改其中的數據。在缺省情況下,通過TQuery部件獲得的查詢結果數據是只讀數據,要想獲得“活動”的數據,在應用程序中必須要設置TQuery部件的RequestLive屬性值為True,然而並不是在任何情況下(通過設置RequestLive的屬值True)都可以獲得“活動”的數據的,要想獲得“活動”的數據,除了將TQuery部件的RequestLive屬性為True外,BDE要能夠返回“活動”的數據,相應的SQL命令語句還要滿足附錄C中的語法規則和下列的約束條件:

TQuery部件獲得“活動”的查詢結果數據的約束條件:

當查詢Paradox或dBASE數據庫中的表:

● 查詢只能涉及到一個單獨的表

● SQL語句中不能包含ORDER BY命令

● SQL語句中不能含聚集運算符SUM或AVG

● 在Select後的字段列表中不能有計算字段

● 在Select語句WHERE部分只能包含字段值與常量的比較運算,這些比較運算符是: Like,>,<,>=,<=,各比較運算之間可以有並和交運算:AND和OR。

當通過SQL語句查詢數據庫服務器中的數據庫表:

● 查詢只能涉及到一個單獨的表

● SQL語句中不能包含ORDER BY命令

● SQL語句中不能含聚集運算符SUM或AVG運算

另外,如果是查詢Sybase數據庫中的表,那麼被查詢的表中只能有一個索引。

如果在應用程序中要求TQuery部件返回一個“活動”的查詢結果數據集,但是SQL 命令語句不滿足上述約束條件時,對於本地數據庫的SQL查詢,BDE只能返回只讀的數據集。對於數據庫服務器中的SQL查詢,只能返回錯誤的代碼。當TQuery 部件返回一個“活動”的查詢結果數據集時,它的CanModify屬性的值會被設置成True。

表17.1 TQuery部件返回查詢結果數據的類型

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

RequestLive屬性值 CanModify屬性值 查詢結果的類型

────────────────────────────────

False False 只讀數據

True(SQL語句滿足約束條件) True “活動”數據

True(SQL語句不滿足約束條件) False 只讀數據

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

當TQuery部件返回只讀的查詢結果數據集,而用戶又希望修改這只讀的數據集時,一般這樣來處理,在應用程序中另外增加一個 TQuery 部件 Query2( 假設獲得只讀結果的TQuery部件的名字是Query1),在Query2中設置修改語句UpDATE對Query1 中的數據進行修改操作,這樣會實現對只讀數據的修改。

17.4 動態SQL語句的編程

在17.3節中,我們已經介紹了動態SQL語句(又被稱為參數化的SQL語句),在其中包含在程序過程中可以變化的參數,在實際的程序設計中使用得更多的是動態SQL語句,因而在這一節裡我們重點介紹如何給動態SQL語句的參數賦值,以在應用程序中靈活地使用SQL語句。動態SQL語句的編寫、執行等等與17.3節中介紹的SQL語句的編寫、執行是一樣的。

動態SQL語句中的參數,我們可以通過兩種途徑來為它賦值:

1. 利用參數編輯器(Parameter Editor)來為參數賦值

具體方法是:選中TQuery部件,單擊鼠標右鍵,然後從中選擇Define Parameters 便可以打開參數編輯器。 

例如,在TQuery部件的SQL屬性中我們設置如下的SQL語句: 

Setect * From Customer Where CustNO=:Number;

TQuery的DatabaseName屬性為DBDEMOS,其中Number為參數變量。我們便可以為參數Number賦值,在Datetype組合框中選擇該參數的數據類型為整數Integer,在Value編輯框中可以為參數Number賦一個值,也可以單擊Null Value檢查框為參數Number賦一個空值Null。給參數賦值之後,單擊OK按鈕,這樣TQuery部件中的SQL 查詢便准備好了,而且參數值也被賦給了動態SQL語句中相應的參數,此時當把TQuery 部件的Active屬性設置成True時,在與TQuery部件相連的數據浏覽部件中會顯示出查詢結果,通過參數編輯器為參數賦值,這種方式缺乏應有的靈活性,在實際應用中用得較少,在實際應用中程序設計人員希望用更靈活方便的方式為參數賦值,那就是我們接下來要介紹的另一種途徑:

2. 在運行過程中,通過程序為參數賦值

用這種方式為參數賦值有三種方法:

①根據參數在SQL語句中出現的順序,設置TQuery部件的Params屬性值為參數賦值。

②直接根據SQL語句中各參數的名字,調用ParamByName方法來為各參數賦值。

③將TQuery部件的DataSource屬性設置為另一個數據源,這樣將另一個數據源中與當前TQuery部件的SQL語句中的參數名相匹配的字段值賦給其對應的參數。

這三種方法我們將在下面的三小節中具體地介紹

17.4.1 使用Params屬性為參數賦值

TQuery部件具有一個Params屬性,它們在設計時不可用,在程序運行過程中可用,並且是動態建立的,當為TQuery部件編寫動態SQL 語句時, Delphi 會自動地建立一個數組Params,數組Params是以0下標開始的,依次對應動態SQL 語句中的參數, 也就是說動態SQL語句中第一個參數對應Params[0],第二個參數對應params[1],依此類推。

例如:一個TQuery部件Query1,我們為它編寫的動態SQL語句是:

Insert Into Customer(CustNo,Name,Country)

Values(:CustNo,:Name, : Country)

對於上述這條動態SQL語句中的參數,我們可以利用TQuery部件的params 屬性為參數賦值:

Query1.params[0].AsString := "1988";

Query1.params[1].AsString := "Lichtenstein";

Query1.params[2].AsString := "USA";

上述語句將把"1988"賦給參數:Cuse_No,"Lichtenstein"賦給參數:Name,"USA"賦給參數:Country。

17.4.2 使用ParamByName方法為參數賦值

ParamByName是一個函數,用動態SQL語句中的參數作為調用ParamByName函數的參數,這樣便可以為它們賦值,使用這種賦值方法,必須要知道動態SQL語句參數的名字。

例如在17.4.1節中的例子中,也可以用下述方法給參數賦值:

Query1.ParamByName('CustNo').AsString := "1988";

Query1.ParamByName('Name').AsString := "Lichtenstein";

Query1.ParamByName('Country').AsString := "USA";

使用這種方法同樣可以為各參數賦值,而且更加直觀一些。

17.4.3 使用Datasource屬性為參數賦值

上述兩種方法的共同特點是:我們在為各參數賦值時,我們是知道各參數對應的具體參數值的。而在具體的應用程序中,有些參數值常常是無法確定的,例如參數值來自於另一個查詢結果,對於這種情況,Delphi提供了使用Datasource屬性為動態SQL 語句中尚存在沒有賦值的參數時, Delphi 會自動檢查 TQuery 部件的 Datasource 屬性, 如果為Datasource屬性設置了屬性值(該屬性的值是另一個TDatasource部件的名字),Delphi 會把沒有賦值的參數與TDatasource部件中的各字段比較,Delphi 會將相應的字段值賦給與其相匹配的參數,利用這種方法也能實現所謂的連接查詢,我們在學習使用TTable部件時,便會創建主要--明細型數據庫應用,用TQuery部件創建的連接查詢與主要- -明細型應用是相似的。

例如:在如圖17.7所示的應用中,設置了下列部件:

● 一個TTable部件

名字為Cust,它的DatabaseName屬性為DEMOS,TableName屬性為Customer。

● 一個TDatasource部件

名字為Custsource,其Dataset屬性被設置為Cust。

● 一個TQuery部件

名字為ORDERS,其DatabaseName被設置為DEMOS,SQL屬性值為:

Select Orders.CustNo,Orders.OrderNo,Orders.SaleDate FROM Orders

WHERE Orders.CustNo =: CustNo

ORDERS的DataSouce屬性被設置為CustSource

● 一個TDatasource部件

名字為OrderSource,其DataSet屬性被設置為Orders。

● 兩個TDBGrid部件

它們分別連接CustSource和OrderSource。

TQuery部件Orders中的動態SQL語句中的參數:CustNo在程序設計過程中沒有給它賦值,當該應用程序運行時Delphi會自動地到其Datasource屬性中說明的數據源CustSource中查找與參數:CustNo匹配的字段,而CustSource中正好有一個名字為 CustNo 的字段與參數:CustNo匹配,這樣Customer表中的CustNo字段值被賦給了參數 : CustNo , 而當每移動Customer表中的記錄指針,參數:CustNo的值會隨之改變,而參數:CustNo的值發生改變時,Orders中的動態SQL語句會根據新的參數值重新查詢,從數據庫表中獲取相應的訂單數據,這樣也變實現了類似於主要--明細型應用。即連接查詢。 

17.4.4 Prepare方法的使用 

在使用動態SQL語句編程時,常常用到一個很重要的方法prepare,調用prepare 方法之後,Delphi會將帶參數的SQL語句傳送給與其對應的數據庫引擎,對動態SQL語句進行語法分析和優化。雖然在用動態SQL語句編程時,調用prepare方法並不是必須的,但是這裡我們要極力推薦調用prepare方法,因為調用prepare方法後,會大大提高動態SQL 語句的執行性能,特別是當要反復多次執行同一條動態SQL語句時,其優越性會更加明顯。如果在應用程序中執行一條SQL語句之前並沒有顯式地調用prepare方法,每次在執行SQL 語句時,Delphi會隱含地調用propare方法以准備這個查詢。

TQuery部件還有一個prepare屬性,這是一個布爾型屬性,當其屬性值為True時,表明該查詢已被准備好了( SQL 語句已被傳送到數據庫引擎中 ) ,當我們使用參數編輯器Parameters Editor來為動態SQL語句中的參數賦值時,當設置完相應的參數值並退出參數編輯器時,Delphi會隱含地調用prepare方法以准備好查詢。

當SQL語句執行完之後,要想准備下一個查詢,首先必須調用close方法,然後才能調用prepare方法准備下一個查詢。一般來說,在一個應用程序中應該調用一次prepare方法,常常在窗體的OnCreate事件處理過程中調用prepare方法,然後用上述介紹的方法為參數賦值,最後調用Open方法或ExecSQL方法執行SQL語句,以完成查詢。

當然在調用prepare方法准備好一個查詢時,會消耗一些數據庫資源,因而每當一個查詢執行完畢之後,要養成調用Unprepare方法以撤消查詢的好習慣。在運行程序過程中,通過程序改變TQuery部件的SQL屬性值時,Delphi會自動地調用Close方法和Unprepare 方法,以撤消查詢。

17.5 SQL編程實例 

我們在學習了SQL程序的編寫方法之後,我們便可以著手創建自己的應用程序了,通過創建應用程序我們對Delphi的強大功能就會有更深刻的印象,同時會進一步全面掌握有關SQL編程的知識,在本節中我們主要介紹兩個例子,前一個例子主要是用靜態的SQL語句編程,後一個例子是用動態SQL語句編程。 

17.5.1 設計簡單的SQL程序編輯器 

例17.1:在這個例子中,我們設計一個交互式的SQL程序編輯器,在這個編輯器中,我們可以根據SQL語言的語法規則,編寫常用的SQL命令,並通過單擊編輯器中的有關的按鈕,直接執行編寫好的SQL命令,SQL命令的執行結果也會及時地通過一個TDBGrid 部件顯示出來。 

表17.3 SQL編輯器中個主要部件的屬性

━━━━━━━━━━━━━━━━━━━━

部 件 屬 性 值

────────────────────

Form1 Caption=SQL程序編輯器

DBGrid1 DataSource=DataSource1

Button1 Caption=執行(&E)

Button2 Caption=清除(&C)

Button3 Caption=退出(&X)

Button3 kind=bkClose

Memo1

DataSource1 DataSet=Query1

Query1 DatabaseName=DEMOS

━━━━━━━━━━━━━━━━━━━━

因為我們在設置Query1的DatabaseName屬性時將其設置為DEMOS,所以我們設計的這個SQL程序編輯器只能對DEOMS中的數據庫表進行操作。

單擊按鈕Button1的事件處理過程代碼為:

程序清單17.1

procedure TForm1.Button1Click(Sender:TObject);
begin
Query1.close;
Query1.SQL.clear;
Query1.SQL.Add(Memo1.text);
Query1.Open;
end;

單擊按鈕Button2的事件處理過程為:

程序清單17.2

procedure TForm1.Button2Click(Sender:TObject);
begin
Query1.close;
Query1.SQL.clear;
Query1.ExceSQL;
end;

下面我們對程序清單17.1和程序清單17.2中的程序代碼進行簡要的分析:

程序清單17.1中的程序代碼是用來執行查詢的。

Query1.close;

這一行程序是用來關閉Query1的,我們在前面的章節中介紹過,只有在調用close 方法將TQuery部件關閉之後,才能修改其SQL屬性值,執行close命令關閉查詢是很安全的,如果查詢已經被關閉了,調用該方法不會產生任何影響。

Query1.SQL.clear;

因為TQuery部件的SQL屬性只能包含一條SQL語句,調用Clear 方法的目的是為了清除SQL屬性原來的屬性值即原來的SQL命令語句,如果不調用clear方法清除原來的SQL命令語句,當在後面的程序中調用Add方法為SQL屬性設置新的SQL命令語句時,Delphi 會將新的SQL命令語句加在原來的SQL命令語句,這樣使得SQL屬性中包含兩條獨立的SQL語句,這是不允許的。

Query1.SQL.Add(Memo.text);

該條命令是將SQL編輯器的編輯區內的內容(TMemo部件Memo1)設置成Query1的SQL屬性值。

Query1.open;

該語句用來執行Query1中的SQL命令語句,如果執行查詢從數據庫中獲得查詢結果,查詢結果會在數據網格DBGrid1中顯示出來。

程序清單2是用來清除查詢的,其前兩行語句跟程序清單1中的代碼是一樣的。Query1.ExecSQL有一些特別,調用ExecSQL方法也是打開Query1,ExecSQL方法與open方法不一樣的,請參看前面的章節,當Query1中SQL屬性值為空時,即沒有SQL語句時,只能調用ExecSQL方法來打開Query1,如果調用 open 方法會返回一個錯誤。 在執行完 Query1.ExecSQL語句之後,應用程序將會清除數據網格DBGrid1中的所有內容。

17.5.2 設計一個數據庫查詢器 

例17.2:在數據庫查詢器中,用戶可以選擇要查詢的數據庫,查詢數據庫中的那一個表、根據數據庫表中那一個字段進行查詢,並且可以方便地指定查詢條件,指定查詢條件主要包括指定邏輯運算符(=、>、<、<=、>=、like、in、NOT like、NOT in)和字段值。

例子全部的程序清單如下:

unit main;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, DB, DBTables, Buttons, ComCtrls, Tabnotbk;
type
TQueryForm = class(TForm)
BitBtn1: TBitBtn;
DataSource1: TDataSource;
Table1: TTable;
GroupBox1: TGroupBox;
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
PageControl1: TPageControl;
TabSheet1: TTabSheet;
Label5: TLabel;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
ListBox1: TListBox;
ListBox2: TListBox;
ListBox3: TListBox;
Edit1: TEdit;
ComboBox1: TComboBox;
BitBtn2: TBitBtn;
TabSheet2: TTabSheet;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure ListBox1Click(Sender: TObject);
procedure ListBox2Click(Sender: TObject);
procedure BitBtn2Click(Sender: TObject);
end;
var
QueryForm: TQueryForm;
implementation
{$R *.DFM}
uses RSLTFORM;
procedure TQueryForm.FormCreate(Sender: TObject);
begin
Screen.Cursor := crHourglass;
{ Populate the alias list }
with ListBox1 do
begin
Items.Clear;
Session.GetAliasNames(Items);
end;
{ Make sure there are aliases defined }
Screen.Cursor := crDefault;
if ListBox1.Items.Count < 1 then
MessageDlg( 'There are no database aliases currently defined. You
need at least one alias to use this demonstration.',
mtError, [mbOK], 0 );
{ Default the drop-down list to the first value in the list }
ComboBox1.ItemIndex := 0;
end;
procedure TQueryForm.ListBox1Click(Sender: TObject);
var
strValue: string; { Holds the alias selected by the user }
bIsLocal: Boolean; { Indicates whether or not an alias is local }
slParams: TStringList; { Holds the parameters of the selected alias }
iCounter: Integer; { An integer counter variable for loops}
begin
{ Determine the alias name selected by the user }
with ListBox1 do
strValue := Items.Strings[ItemIndex];
{ Get the names of the tables in the alias and put them in the
appropriate list box, making sure the user's choices are reflected
in the list. }
ListBox2.Items.Clear;
Session.GetTableNames(strValue, { alias to enumerate }
'', { pattern to match }
CheckBox1.Checked, { show extensions flag }
CheckBox2.Checked, { show system tables flag }
ListBox2.Items); { target for table list }
{ Make sure there are tables defined in the alias. If not, show an
error; otherwise, clear the list box. }
Screen.Cursor := crDefault;
if ListBox2.Items.Count < 1 then
MessageDlg('There are no tables in the alias you selected. Please
choose another', mtError, [mbOK], 0 );
ListBox3.Items.Clear;
end;
procedure TQueryForm.ListBox2Click(Sender: TObject);
begin
Screen.Cursor := crHourglass;
try
{ First, disable the TTable object. }
if Table1.Active then
Table1.Close;
{ Open the selected table }
with ListBox1 do
Table1.DatabaseName := Items.Strings[ItemIndex];
with ListBox2 do
Table1.TableName := Items.Strings[ItemIndex];
{ Open the table and put a list of the field names in the Fields
list box. }
Table1.Open;
if Table1.Active then
Table1.GetFieldNames(ListBox3.Items);
finally
Screen.Cursor := crDefault;
end;
end;
procedure TQueryForm.BitBtn2Click(Sender: TObject);
var
strAlias, { Alias name selected by the user }
strTable, { Table name selected by the user }
strField, { Field name selected by the user }
strValue, { Field Value entered by the user }
strWhere, { WHERE clause for the user's query }
strQuote, { Holds quotes is the query field is text }
strQuery: string; { String used to construct the query }
frmQuery: TResultForm; { The Results form }
type
{ The following type is used with the Type drop-down
list. The text values corresponding with each item is
described in comments, along with the relevant SQL operators. }
etSQLOps = (soNoCondition, { not field conditions: no WHERE clause }
soEqual, { equals: = }
soNotEqual, { is not equal to: <> }
soLessThan, { is less than: < }
soLessEqual, { is less than or equal to: <= }
soMoreThan, { is greater than: > }
soMoreEqual, { is greater than or equal to: >= }
soStartsWith, { starts with: LIKE xx% }
soNoStartsWith, { doesn't start with: NOT LIKE xx% }
soEndsWith, { ends with: LIKE %xx }
soNoEndsWith, { doesn't end with: NOT LIKE %xx }
soContains, { contains: LIKE %xx% }
soNoContains, { doesn't contain: NOT LIKE %xx% }
soBlank, { is blank: }
soNotBlank, { is not blank: }
soInside, { contains only: IN ( xx, yy, zz ) }
soOutside); { doesn't contain: NOT IN (xx, yy, zz) }
begin
{ Initialize the variables needed to run the query }
with ListBox1 do
if ItemIndex = -1 then
raise Exception.Create('Can''t Run Query: No Alias Selected')
else
strAlias := Items.Strings[ItemIndex];
with ListBox2 do
if ItemIndex = -1 then
raise Exception.Create('Can''t Run Query: No Table Selected')
else
strTable := Items.Strings[ItemIndex];
with ListBox3 do
if ItemIndex = -1 then
begin
if ComboBox1.ItemIndex > Ord(soNocondition) then
raise Exception.Create('Can''t Run Query: No Field Selected')
else
strField := '';
end
else
strField := Items.Strings[ItemIndex];
if (Edit1.Text = '') and
(ComboBox1.ItemIndex > Ord(soNoCondition)) and
(ComboBox1.ItemIndex < Ord(soBlank)) then
raise Exception.create('Can''t Run Query: No Search Value Entered')
else
strValue := Edit1.Text;
{ See if the field being search is a string field. If so, then pad the
quote string with quotation marks; otherwise, set it to a null value. }
if strField <> '' then
with Table1.FieldByName(strField) do
if (DataType = ftString) or (DataType = ftMemo) then
strQuote := '"' else
strQuote := '';
{ Construct the WHERE clause of the query based on the user's choice
in Type. }
case etSQLOps(ComboBox1.ItemIndex) of
soNoCondition: strWhere := '';
soEqual: strWhere := strField + ' = ' + strQuote + strValue+ strQuote;
soNotEqual: strWhere := strField + ' <> ' + strQuote + strValue +
strQuote;
soLessThan: strWhere := strField + ' < ' + strQuote + strValue +
strQuote;
soLessEqual: strWhere := strField + ' <= ' + strQuote + strValue +
strQuote;
soMoreThan: strWhere := strField + ' > ' + strQuote + strValue +
strQuote;
soMoreEqual: strWhere := strField + ' >= ' + strQuote + strValue +
strQuote;
soStartsWith: strWhere := strField + ' LIKE ' + strQuote +
strValue + '%' + strQuote;
soNoStartsWith: strWhere := strField + ' NOT LIKE ' + strQuote +
strValue + '%' + strQuote;
soEndsWith: strWhere := strField + ' LIKE ' + strQuote +
'%' + strValue + strQuote;
soNoEndsWith: strWhere := strField + ' NOT LIKE ' +
strQuote + '%' + strValue + strQuote;
soContains: strWhere := strField + ' LIKE '+ strQuote+'%'+ strValue
+ '%' + strQuote;
soNoContains: strWhere := strField + ' NOT LIKE ' + strQuote + '%'
+ strValue + '%' + strQuote;
soBlank: strWhere := strField + ' IS NULL';
soNotBlank: strWhere := strField + ' IS NOT NULL';
end;
if ComboBox1.ItemIndex = Ord(soNoCondition) then
strQuery := 'SELECT * FROM "' + strTable + '"'
else if Table1.FieldByName(strField).DataType = ftString then
strQuery := 'SELECT * FROM "' + strTable + '" t WHERE t.' + strWhere
else
strQuery := 'SELECT * FROM "' + strTable + '" t WHERE t.' + strWhere;
{ Create an instance of the browser form. }
frmQuery := TResultForm.Create(Application);
{ Use a resource protection block in case an exception is raised. This
ensures that the memory allocated for the Results form is released. }
try
with frmQuery do
begin
Screen.Cursor := crHourglass;
if Query1.Active then Query1.Close;
Query1.DatabaseName := strAlias; {set the alias the query poitns to}
Query1.SQL.clear; { empty existing SQL in the query }
Query1.SQL.Add(strQuery); { add query string to query object }
Query1.Active := True; { try to run the query }
Screen.Cursor := crDefault;
if Query1.Active then
begin
{ If the query didn't return any records, there's no point in
displaying the form. In that event, raise an exception. }
if Query1.RecordCount < 1 then
raise Exception.create('No records matched your criteria.
Please try again.' );
{ write a message to the browse form's status line }
if strField = '' then
Panel3.Caption := 'Now showing all records from ' + strTable
+ '...'
else
Panel3.Caption := 'Now showing '+ strTable +' where '+ strField
+' contains values equal to '+ strValue + '...';
{ show the form }
ShowModal;
end;
end;
finally
frmQuery.Free;
end;
end;
end.
unit RSLTFORM;
interface
uses
SysUtils, Windows, Messages, Classes, Graphics, Controls, StdCtrls, DB,
Forms, DBCtrls, DBGrids, DBTables, Buttons, Grids, ExtCtrls, Dialogs;
type
TResultForm = class(TForm)
DBGrid1: TDBGrid;
DBNavigator: TDBNavigator;
Panel1: TPanel;
DataSource1: TDataSource;
Panel2: TPanel;
Panel3: TPanel;
Query1: TQuery;
SpeedButton2: TSpeedButton;
Panel4: TPanel;
SpeedButton1: TSpeedButton;
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
end;
var
ResultForm: TResultForm;
implementation
{$R *.DFM}
procedure TResultForm.SpeedButton1Click(Sender: TObject);
begin
Close;
end;
procedure TResultForm.SpeedButton2Click(Sender: TObject);
var
strText: string; { Variable to hold display text }
iCounter: Integer; { Loop counter variable }
begin
{ Build a string containing the query }
strText := '';
for iCounter := 0 to Query1.SQL.Count - 1 do
strText := strText + Query1.SQL[iCounter];
{ Display the query text }
MessageDlg('The underlying query is: ' + #10 + #10 + strText,
mtInformation, [mbOK], 0 );
end; 
end.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved