天天看點

多層資料庫開發九:查詢資料庫

                                                        第九章 查詢資料庫

  這一章介紹如何用TQuery構件查詢資料庫,如何通過SQL語句檢索、插入、更新和删除資料。SQL是符合工業标準的關系資料庫語言,既可以用于遠端的基于伺服器的資料庫,如Sybase、Oracle、InterBase和Microsoft SQL Server,也可以用于本地資料庫如Paradox、dBASE、FoxPro和Access以及符合ODBC的資料庫。

<b>9.1 有效地使用查詢</b>

  要有效地使用查詢,必須熟悉标準的SQL語言以及所使用的伺服器對SQL-92的限制和擴充,同時還要熟悉BDE。

<b>9.1.1 查詢桌面資料庫</b>

  作為一個桌面開發者,應對表格、記錄和字段的概念有所了解,又能熟練地使用TTable構件通路資料集中的每一條記錄和每一個字段。

還可以使用TTable的範圍和過濾功能在資料集中選擇一部分記錄,前者用于選擇一塊連續的記錄,這些記錄的值在一個特定的範圍内; 後者用于選擇非連續的記錄,這些記錄符合特定的條件。

  所謂查詢,非常類似于過濾,不同的是,查詢要用到TQuery構件和SQL屬性,有時候可能還要用到Params屬性。從功能上講,查詢要比過濾複雜和強大些,這主要展現在:

.可以同時查詢幾個表格

.可以讓查詢結果中隻包含部分字段,而過濾将傳回所有字段。

  查詢也可以帶參數,此時稱為參數化查詢。所謂參數,類似于變量,它的實際的值由BDE在執行SQL語句之前指派。參數化查詢的好處是,不需要修改SQL語句,隻要修改參數的值,就能執行不同的查詢功能。

  大部分情況下,使用TQuery構件是為了在資料集中選擇一部分字段和記錄,但也可以使用SQL語句實作更新、插入和删除記錄的功能,這是與TTable構件的一個差別。

<b>9.1.2 查詢遠端資料庫</b>

  要查詢遠端資料庫,必須熟悉SQL語句以及伺服器對标準SQL的限制和擴充。

  TQuery構件的SQL屬性用于指定要執行的SQL語句, Params屬性用于提供參數。TQuery構件的功能并不隻限于SQL語句和參數,它還是BDE與應用程式之間的接口。

  應用程式可以通過TQuery構件的屬性和方法來操縱SQL語句和參數。TQuery構件最終還是通過SQL Links與遠端伺服器進行通訊的,遠端伺服器把查詢結果傳回給BDE,再由BDE傳回給應用程式。

<b>9.2 可以查詢哪些資料庫</b>

  使用TQuery構件可以查詢下列資料庫:

  一是Paradox或dBASE,這是通過BDE内置的Local SQL實作的。Local SQL是SQL-92标準的一個子集,支援大部分DML和DDL。

  二是Local InterBase Server,這是通過InterBase引擎實作的。

  三是遠端資料庫,如Oracle、Sybase、MS-SQL Server、InFormix、DB2和InterBase,不過,必須安裝了相應的SQL Links驅動程式。不同的伺服器對标準SQL都有不同的限制和擴充,要查詢遠端資料庫之前,務必要查閱它的有關文檔。

  Delphi 4還支援異構查詢,也就是說,可以同時查詢幾個不同類型的資料庫。

<b>9.3 使用TQuery構件的一般步驟</b>

  第一步是把一個TQuery構件放到資料子產品上,設定它的DatabaseName屬性指定要通路的資料庫。對于Paradox和dBASE來說,DatabaseName屬性可以設為BDE别名如DBEMOS、DefaultDD、IBLOCAL等,也可以是自定義的别名或者表所在的路徑。

  對于SQL表來說,DatabaseName屬性隻能設為BDE别名。如果應用程式使用TDatabase構件來連接配接資料庫,DatabaseName屬性也可以設為應用程式專用的别名。

  第二步是設定SQL屬性指定要執行的SQL語句,有必要的話還可以設定Params屬性為SQL語句設定參數。

  第三步是把TDataSource構件放到資料子產品上,設定它的DataSet屬性指定TQuery構件。再把TDBGrid構件放到窗體上,設定它的DataSource屬性指定TDataSource構件。

  第四步是執行SQL語句。要執行SQL語句有兩種方式,一是在設計期把Active屬性設為True,程式啟動時将自動執行SQL語句。另一種方式是在運作期調用Open或ExecSQL執行SQL語句。如果希望傳回查詢結果,調用Open,如果不需要傳回查詢結果,調用ExecSQL。在調用Open或ExecSQL之前,最好先調用Prepare通知伺服器作好準備。

  執行SQL語句所傳回的查詢結果實際上是資料集中滿足特定條件的記錄所組成的子集,資料庫栅格中隻顯示符合特定條件的記錄。

<b>9.4 指定要執行的SQL語句</b>

  可以設定SQL屬性以指定要執行的SQL語句。在設計期,隻要把Active屬性設為True,就會自動執行SQL語句。在運作期,首先要調用Prepare通知伺服器準備好,然後調用Open或ExecSQL執行SQL函數語句。

<b>9.4.1 概述</b>

  SQL屬性是一個典型的TStrings對象。SQL屬性一般隻包含一條完整的SQL語句,但可以分成幾行寫,TQuery構件會自動把幾行字元串合并成一條SQL語句。

  把SQL語句分成幾行寫的好處是,SQL語句的邏輯結構比較清楚,有利于今後維護和調試。是以,SQL語句的SELECT部分和WHERE部分一般都不在同一行上。

  SQL語句可以不帶參數,把字段名稱和值固定在SQL語句中,例如,下面這個SQL語句就是硬寫(Hard-Coded)的:

  SELECT * FROM Customer WHERE CustNo = 1231

  注意:如果要查詢的是本地資料庫,如果SQL語句中的字段名包含空格或其他特殊符号,必須用引号括起來,前面還要加上表格名和小圓點。

  如果用參數的話,查詢就靈活得多,應用程式不需要改寫SQL語句本身,隻要修改參數的值,就能使SQL語句執行不同的查詢功能。在執行SQL語句之前,TQuery構件會自動把實際的值替換SQL語句中的參數,即使并沒有顯式地調用Prepare函數。

  下面這條SQL語句是典型的參數化查詢:

  SELECT * FROM Customer WHERE CustNo = :Number

  其中,Number就是一個參數,它的前面必須加冒号。在運作期,應用程式必須提供Number參數的值,每次執行SQL語句時,Number參數的值可以不同。

  參數的值是通過TQuery的Params屬性提供的。

<b>9.4.2 在設計期指定SQL語句</b>

  在設計期,要指定SQL語句,可以在對象觀察器中單擊SQL屬性邊上的省略号按鈕,彈出一個字元串清單編輯器,如圖9.1所示。

  圖9.1 在設計期指定SQL語句

  SQL語句可以分成幾行寫,但同一單詞不能分開。一般情況下,SQL屬性隻能包含一條完整的SQL語句,但有些伺服器允許同時執行幾條SQL語句,這種情況下,可以輸入多條SQL語句。

  如果使用Delphi 4的Client/Server版本或Enterprise版本,也可以用SQLBuilder這個實用工具來建立SQL語句。要使用SQL Builder,在TQuery構件上單擊滑鼠右鍵,在彈出的菜單中選擇“SQL Builder”指令。

<b>9.4.3 在運作期指定SQL語句</b>

  在運作期,要指定SQL屬性有三種方式,一是直接設定SQL屬性,二是調用LoadFromFile從檔案中讀取SQL語句,或者從另一個TStrings對象中獲得SQL語句。

  在直接設定SQL屬性之前,首先要調用Close函數。如果SQL屬性中本來已經有了SQL語句,還要調用Clear把原來的SQL語句清除。

  下面的代碼示範了怎樣在運作期直接設定SQL屬性:

With CustomerQuery Do

Begin

Close;

With SQL Do

Clear;

Add('SELECT * FROM Customer');

Add('WHERE Company = 'Light Diver');

End;

Open;

  有時候,可能想在原來的SQL語句的基礎上修改或增加一行,這時候就不能調用Clear把原來的SQL語句清掉,例如:

  CustomerQuery.SQL[1] := 'WHERE Company = "Light Diver"';

  也可以調用LoadFromFile從檔案中擷取SQL語句,這主要是因為TStrings對象支援檔案操作。LoadFromFile會自動把原來的SQL語句清掉。

  下面的代碼是調用LoadFromFile的例子:

CustomerQuery.Close;

CustomerQuery.SQL.LoadFromFile('c:/orders.txt');

CustomerQuery.Open;

  還可以從另一個TStrings對象中擷取SQL語句,這就要調用TStrings的Assign函數。Assign 會自動把原來的SQL語句清空。

  下面的代碼是調用Assign的例子:

CustomerQuery.SQL.Assign(Memo1.Lines);

<b>9.5 參 數</b>

  要使用參數化查詢,必須在SQL語句中加入參數,例如:

INSERT INTO Country (Name, Capital, Population)

VALUES (:Name, :Capital, :Population)

  其中,Name、Capital和Population是三個參數。

  在執行上述SQL語句之前,應用程式應當調用Prepare函數通知BDE和伺服器預先配置設定好資源,以加快查詢速度。程式示例如下:

With Query1 Do

Unprepare;

ParamByName('Name').AsString := 'China';

ParamByName('Capital').AsString := 'Beijing';

ParamByName('Population').AsInteger := '120000';

Prepare;

<b>9.5.1 在設計期提供參數</b>

  要在設計期提供參數,單擊Params屬性邊上的省略号按鈕,彈出如圖9.2所示的編輯器。

  圖9.2 在設計期設定Params屬性

  如果SQL語句中沒有包含任何參數,圖9.2所示的編輯器就是空白的。這個編輯器的工具欄總是禁止的,這意味着隻能在SQL語句中加入參數。

  選擇其中一個參數(TParam對象),就可以在對象觀察器中設定它的屬性,或者建立事件句柄。TParam的主要屬性有:

  DataType屬性用于指定參數的資料類型,它的初始值總是ftUnknown,必須設定每個參數的資料類型。

  ParamType屬性用于指定參數的使用類型,它的初始值也是ptUnknown。

  Value屬性用于給出參數的值。當然,也可以在運作期給出參數的值。

<b>9.5.2 在運作期提供參數</b>

  要在運作期通路參數,有三種方式:

  一是通過ParamByName函數按名稱通路參數。

  二是通過Params屬性按序号通路參數。

  三是通過TParams對象的ParamValues屬性按名稱通路參數。

  假設一條SQL語句有三個參數:

  INSERT INTO "COUNTRY.DB"

   (Name, Capital, Continent)

   VALUES (:Name, :Capital, :Continent)

  下面這行代碼通過ParamByName函數來通路其中的Capital參數:Query1.ParamByName('Capital').AsString := Edit1.Text;

  下面這行代碼通過Params屬性來通路其中的Name參數: 

   Query1.Params[0].AsString := Edit1.Text;

  下面這行代碼通過TParams對象的ParamValues屬性來同時通路三個參數:

   Query1.Params.ParamValues['Country;Capital;Continent'] :=VarArrayOf([Edit1.Text, Edit2.Text, Edit3.Text]);

<b>9.5.3 從另一個資料集獲得參數</b>

  TQuery構件的DataSource屬性用于指定一個資料源(TDataSource構件),如果應用程式既沒有在設計期也沒有在運作期給參數指派,它就在這個資料源中查找與參數名比對的字段,然後用這個字段的值作為參數的值。

  假設一個資料子產品叫LinkModule,上面有一個TQuery構件叫OrdersQuery,它的SQL語句如下:

  SELECT CustNo, OrderNo, SaleDate

   FROM Orders WHERE CustNo = :CustNo

  另外,資料子產品上還有下列構件:

.一個TTable構件叫CustomersTable,它的TableName屬性設為CUSTOMER.DB。

.一個TDataSource構件叫OrdersSource,它的DataSet屬性設為OrdersQuery。

.一個TDataSource構件叫CustomersSource,它的DataSet屬性設為CustomersTable。

.OrdersQuery的DataSource屬性也設為CustomersSource。

  假設應用程式有一個窗體叫LinkedQuery,窗體上有兩個TDBGrid構件,它們的DataSource屬性分别指定CustomersSource和OrdersSource。

  如果編譯和運作這個應用程式,将看到如圖9.3所示的效果:

  圖9.3 從另一個資料集獲得參數

  這裡簡單解釋一下圖9.3。如果沒有對SQL語句中的:CustNo參數指派,OrdersQuery将試圖從CustomersSource指定的資料集中查找比對的字段。由于CustomersSource是從CUSTOMER.DB中擷取資料的,而CUSTOMER.DB中恰好有一個CustNo字段,是以,:CustNo參數的值就是CustNo字段的值。如果您在顯示CUSTOMER.DB的栅格中選擇了另一條記錄,将導緻:CustNo參數的值跟着變化。

<b>9.6 執 行 查 詢</b>

  當指定了SQL語句并且提供了參數後,就可以執行查詢了。如果是第一次執行查詢,最好調用Prepare通知BDE或伺服器做好準備,這樣能加快查詢的速度。

  既可以在設計期執行查詢,也可以在運作期執行查詢。

  要在設計期執行查詢,隻要把Active屬性設為True。不過,在設計期能執行的SQL語句,僅限于SELECT語句,不能是INSERT、UPDATE或DELETE語句。

  要在運作期執行查詢,可以調用Open或ExecSQL函數,其中,Open适合于執行SELECT語句,而ExecSQL适合于執行INSERT、UPDATE或DELETE語句,後者不傳回結果。

  在調用Open或ExecSQL之前,首先要調用Close。程式示例如下:

   CustomerQuery.Close;

   CustomerQuery.Open;

  如果在程式設計的時候無法确定是否要傳回查詢結果,可以用Try...Except結構把這兩個過程都寫進去,一般Open在Try部分調用,而ExecSQL在Except部分調用,這樣,即使Open調用失敗,也能執行到ExecSQL。程式示例如下:

Try

Query2.Open;

Except

On E: Exception Do

  If not (E is ENoResultSet) then Raise;

  前面多次提到,在執行查詢前最好先調用Prepare,盡管這并不是必須的。預先調用Prepare能夠改善應用程式的性能。程式示例如下:

If not (CustomerQuery.Prepared) then

CustomerQuery.Prepare;

  上述程式首先調用Close,然後檢查Prepared屬性,如果這個屬性傳回True,表示已經準備好,如果這個屬性傳回False,表示沒有準備好,此時就要調用Prepare。

<b>9.7 異 構 查 詢</b>

  所謂異構查詢,就是同時查詢幾個不同的資料庫。這些資料庫的類型可以不同。例如,可以同時查詢Oracle資料庫、Sybase資料庫和本地的dBASE表。當程式執行異構查詢的時候,BDE通過Local SQL來分析和處理這個查詢,而不是用與伺服器相關的特定的SQL文法。

  建立一個異構查詢的一般步驟是這樣的:

  第一步,把一個TQuery構件放到窗體或資料子產品上,讓DatabaseName屬性空着。

  第二步,為要查詢的每一個資料庫建立一個單獨的BDE别名。

  第三步,設定SQL屬性以指定要執行的SQL語句。在SQL語句中,表的名字前要加别名和冒号,并且用雙引号括起來。字段名前要加表名和小圓點。例如:

SELECT Customer.CustNo, Orders.OrderNo

  FROM "Oracle1:CUSTOMER"

  JOIN "Sybase1:ORDERS"

  ON (Customer.CustNo = Orders.CustNo)

WHERE (Customer.CustNo = 1503)

  第四步,設定Params屬性提供參數。

  第五步,調用Prepare通知BDE或伺服器做好準備,然後調用Open或ExecSQL執行查詢。如果顯式地使用TDatabase構件連接配接資料庫,并且設定了它的DatabaseName屬性定義了應用程式專用的别名,在SQL語句中可以用專用的别名代替BDE别名。

<b>9.8 查 詢 結 果</b>

  預設情況下,查詢結果是隻讀的。應用程式可以用資料控件去顯示查詢結果,但使用者不能編輯資料。怎樣才能使使用者能夠編輯資料呢?

  要使使用者能夠編輯資料,必須把TQuery構件的RequestLive屬性設為True。不過,把RequestLive屬性設為True并不能保證查詢結果一定是可以修改的,因為這還取決于查詢使用的是Local SQL還是與伺服器相關的SQL。

  像查詢Paradox或dBASE以及異構查詢都是使用Local SQL,而查詢遠端伺服器則使用與伺服器相關的SQL。即使RequestLive屬性設為True,而且查詢的是本地資料庫,但由于SELECT語句的文法不合适,BDE也将傳回隻讀的查詢結果。

  是以,在編輯資料之前,先要通路CanModify屬性。隻有當這個屬性傳回True時,才表示查詢結果是可編輯的。