TTable 組件詳解



 
Delphi4核心編程技術-第八章

第八章 訪 問 表 格
  這一章介紹怎樣在數據庫應用程序中使用TTable構件。TTable的上級是TDBDataSet,而TDBDataSet是從TBDEDataSet繼承下來的,TBDEDataSet又是從TDataSet繼承下來的。因此,如果對第六章的內容完全掌握了的話,您應該對TTable構件不感到陌生。
8.1 使用TTable構件的一般步驟
  TTable構件可以訪問數據庫表格中的每一行和每一列。TTable構件既可以訪問本地的數據庫如Paradox、dBASE、Access、FoxPro,也可以訪問ODBC數據庫,還可以訪問遠程數據庫如InterBase、Sybase和SQL Server。
  既可以顯示和編輯表格的所有行和所有列,也可以選擇一定范圍內的行,或者用過濾技術檢索出其中一部分行,可以搜索記錄,復制、換名或刪除一個表格。
  一個TTable構件往往只訪問一個表格,不過,如果需要的話,一個TTable構件也可以訪問幾個表格。
8.1.1 設置TTable構件
  下面是使用和設置TTable構件的一般步驟,有些應用程序可能還需要其他步驟。
  第一步是把一個TTable構件放到窗體或數據模塊上,設置它的Name屬性指定構件的名稱,不能與同一個應用程序中的其他構件同名。
  第二步是設置DatabaseName屬性指定要訪問的數據庫。
  第三步是設置TableName屬性指定要訪問的表。
  第四步是把一個TDataSource構件放到窗體或數據模塊上,設置它的DataSet屬性指定TTable構件
  第五步是把一個數據控件如TDBGrid放到窗體上,設置它的DataSource屬性指定TDataSource構件。
  最后,把TTable構件的Active屬性設為True。
8.1.2 指定要訪問的數據庫
  DatabaseName屬性用於指定要訪問的數據庫。對於Paradox和dBASE來說,DatabaseName屬性可以設為BDE別名或表的路徑。對於SQL數據庫來說,DatabaseName屬性必須設為BDE別名。
  用BDE別名指定數據庫的好處是,只要修改別名的定義,就可以訪問不同的數據庫,而應用程序不必作任何修改。要修改別名的定義,就要用到SQLExplorer。
  在修改DatabaseName屬性之前,先要把Active屬性設為False。
  如果用TDatabase構件來管理與數據庫的連接,可以把DatabaseName屬性設為應用程序專用的別名。
8.1.3 指定要訪問的表
  TableName屬性用於指定要訪問的表。在設置TableName屬性之前,首先要把Active屬性設為False,並且設置DatabaseName屬性指定要訪問的數據庫。
  既可以在設計期指定要訪問的表,也可以在運行期指定要訪問的表,程序示例如下:
With OrderOrCustTable Do
Begin
Active := False;
If TableName = 'CUSTOMER.DB' then TableName := 'ORDERS.DB'
ElseTableName := 'CUSTOMER.DB';
Active := True;
End;
8.1.4 指定表格的類型
  如果要訪問的數據庫是Paradox、dBASE、FoxPro或用逗號隔開的ASCII文本文件,必須設置TableType 屬性指定表格的類型。
  默認情況下,TableType屬性設為ttDefault,BDE將根據文件擴展名來區分表格的類型。擴展名為.DB就認為是Paradox,擴展名為.DBF就認為是dBASE,擴展名為.TXT就認為是ASCII文本,沒有擴展名就認為是Paradox。
  如果需要確切地指定表格的類型,就必須設置TableType屬性,設為ttDefault表示讓BDE來判斷表格的類型,設為ttParadox表示是Paradox,設為ttDBase表示是dBASE,設為ttFoxPro表示是FoxPro,設為ttASCII表示是ASCII文本。
8.1.5 打開和關閉一個表
  要顯示和編輯表格中的數據,首先要打開這個表格。要打開一個表格有兩種方式,一是把Active屬性設為True,二是調用Open。表格打開后,數據集就進入dsBrowse狀態。
  如果不再需要顯示和編輯表格中的數據,或者要修改TTable構件的某些屬性如DatabaseName、TableName和TableType,此時就要關閉表格。
  要關閉表格也有兩種方式,一是把Active屬性設為False,二是調用Close函數。表格關閉后,將使數據集進入dsInactive狀態。
8.1.6 控制對表格的讀寫
  當一個表格打開后,應用程序需要取得對它讀和寫的訪問權,這就要用到TTable構件的三個屬性:CanModify、ReadOnly和Exclusive。
  CanModify是一個只讀的屬性,當一個表格打開后,可以通過這個屬性來判斷表格的數據是否修改。如果CanModify屬性返回False,應用程序就不能修改表格的數據。
  如果CanModify屬性返回True,應用程序可以修改表格的數據。
  如果ReadOnly屬性設為False,用戶就可以編輯表格的數據。如果要限制用戶只能查看數據,不能修改數據,就得把ReadOnly屬性設為True。
  Exclusive屬性用於設置應用程序是否能獨占表格。如果要獨占對表格的訪問權,就在打開表格之前把Exclusive屬性設為True,其他應用程序就既不能讀也不能寫這個表格。
  Exclusive屬性一般只適用於Paradox、dBASE或FoxPro等本地數據庫,對於SQL數據庫來說,有的服務器不支持表格級的鎖定,有的服務器雖然支持鎖定,但其他應用程序仍然可以讀表格的數據。
8.2 在表格中搜索記錄
  要在表格中搜索記錄有幾種方式,我們推薦調用Locate函數和Lookup函數,這兩個函數可以在任何表格中基於任何類型的字段來搜索記錄,不管表格是否建立了索引。 
  Locate函數用於在表格中搜索一條符合特定條件的記錄,如果找到的話,就使該記錄成為當前記錄。
  Lookup函數與Locate 函數的區別是,如果找到匹配的記錄后,當前記錄不變。事實上,Locate函數和Lookup函數並不僅適用於TTable構件,它還適用於其他數據集構件。
8.2.1 基於索引中的字段搜索
  記錄除了Lookup函數與Locate 函數外,TTable構件還保留了Goto系列和Find系列的方法,這些方法可以基於索引中的字段搜索匹配的記錄,並使找到的記錄成為當前記錄。在Delphi 4中,索引中的字段稱為關鍵字段。
  與Locate不同的是,GotoKey、GotoNearest要求表格必須已建立了索引,並且只能基於關鍵字段搜索記錄,而Locate則沒有這個要求。下面列出了Goto系列和Find系列中的6種方法:
.EditKey使數據集進入dsSetKey狀態,程序就可以指定關鍵字段的值。
.FindKey綜合了SetKey和GotoKey兩個方法。
.FindNearest綜合了SetKey和GotoNearest兩個方法。
.GotoKey基於關鍵字段搜索特定的記錄,並使找到的記錄成為當前記錄。
.GotoNearest類似於GotoKey,但只要近似匹配即可(適合於字符串類型的字段)。
.SetKey使數據集進入dsSetKey狀態,程序就可以指定關鍵字段的值。與EditKey不同的是,如果SetKey沒有調用成功,原先的鍵值被清掉。
  其中,GotoKey和FindKey是返回布爾值的函數,如果調用成功,就返回True,並使找到的記錄成為當前記錄。如果沒有找到匹配的記錄,這兩個函數就返回False。
  GotoNearestand和FindNearest分別類似於GotoKey和FindKey,不同的是,如果它們沒有找到精確匹配的記錄,就查找近似匹配的記錄。
8.2.2 Goto系列
  使用Goto系列的方法來搜索和定位記錄,一般是這么幾個步驟:
  第一步是設置IndexName屬性指定要使用的索引(對SQL數據庫來說,需要設置IndexFieldNames屬性)。當然,如果使用表格本身的主索引,可以不設置這個屬性。
  第二步是打開表格。
  第三步是調用SetKey使數據集進入dsSetKey狀態。
  第四步是通過Fields屬性或FieldByName函數指定關鍵字段的值。
  第五步是調用GotoKey或GotoNearest查找匹配的記錄。
  下面的例子實際演示了上述5個步驟:
Procedure TSearchDemo.SearchExactClick(Sender: TObject);
Begin
Table1.SetKey;
Table1.Fields[0].AsString := Edit1.Text;
If not Table1.GotoKey then ShowMessage('Record not found');
End;
  上述例子演示的是GotoKey,其實GotoNearest也是差不多的,程序示例如下:
Table1.SetKey;
Table1.Fields[0].AsString := 'Sm';
Table1.GotoNearest;
8.2.3 Find系列使用
  Find系列的方法來搜索和定位記錄,一般是這么幾個步驟:
  第一步是設置IndexName屬性指定要使用的索引(對SQL數據庫來說,需要設置IndexFieldNames屬性)。當然,如果您使用表格本身的主索引,可以不設置這個屬性。
  第二步是打開表格。
  第三步是調用FindKey或FindNearest函數查找匹配的記錄。這兩個方法都要傳遞一個KeyValues參數,用於指定關鍵字段的值。其中,FindNearest只適合於字符串類型的字段。
8.2.4 KeyExclusive屬性和KeyFieldCount屬性
  調用GotoNearest、FindNearest等函數時,如果KeyExclusive屬性設為False,匹配的記錄將成為當前記錄(如果找到的話); 如果KeyExclusive屬性設為True,匹配的記錄的下一條記錄成為當前記錄。
  如果一個索引中有多個字段,而您只想基於其中部分字段進行搜索,就要設置KeyFieldCount屬性指定關鍵字段的個數。例如,假設索引中有三個關鍵字段,而您只想基於其中第一個字段進行搜索,就把KeyFieldCount屬性設為1。
  如果把KeyFieldCount屬性設為2,表示基於前面2個字段進行搜索。如果把KeyFieldCount屬性設為3,表示基於前面3個字段進行搜索。但是,如果要基於第一個和第三個字段進行搜索,KeyFieldCount屬性無能為力。
8.2.5 基於副索引進行搜索
  如果不想基於主索引而想基於副索引進行搜索,就要通過IndexName屬性指定索引名稱。在設置IndexName屬性之前,首先要關閉表格。程序示例如下:
Table1.Close;
Table1.IndexName := 'CityIndex';
Table1.Open;
Table1.SetKey;
Table1['City'] := Edit1.Text;
Table1.GotoNearest;
  對於SQL表來說,可以直接用IndexFieldNames屬性指定索引中的字段。對於Paradox和dBASE表來說,必須事先建立索引,而且只能基於索引中的字段進行搜索,否則,會觸發異常。
  每次調用SetKey或FindKey函數,總是清除前一次對鍵值的設置。如果想基於前面設置的鍵值繼續搜索,或者要在原來的基礎上加入新的關鍵字段,就不能調用SetKey或FindKey,而要調用EditKey函數。例如,假設原先設置City和Country兩個鍵值,現在要增加一個Company字段,程序示例如下:
Table1.EditKey;
Table1[' Company] := Edit2.Text;
Table1.GotoNearest;
8.3 對記錄排序
  索引的作用就是對記錄排序。默認情況下,Paradox表按照主索引以升序排列,如果要以其他方式排序,就要用到副索引(Paradox或dBASE)或者直接列出索引中的字段(SQL)。
8.3.1 檢索已定義的索引名
  可以調用GetIndexNames函數來檢索一個表格已定義的索引名,它能夠返回一個列表,該列表由所有已定義的索引的名稱組成。程序示例如下:
varIndexList: TList;
...
CustomersTable.GetIndexNames(IndexList);
  注意:對於Paradox來說,主索引是沒有名稱的,因此,GetIndexNames函數返回的列表中不包含主索引。
8.3.2 指定使用哪個副索引
  對於Paradox表來說,如果要按副索引排序,首先要設置IndexName屬性指定一個副索引。例如:
  CustomersTable.IndexName := 'CustDescending';
  如果先按副索引排序,以后又想按主索引排序,只要把IndexName屬性設為空。
  對於dBASE表來說,如果要按副索引排序,首先要設置IndexFiles屬性指定一個或幾個索引文件。
  在設計期,可以在對象觀察器中單擊IndexFiles屬性邊上的省略號按鈕,打開如圖8.1所示的對話框:
  圖8.1在設計期設置IndexFiles屬性
  在運行期,可以通過TStrings對象動態地增加或刪減索引文件,也可以用一個字符串列出要使用的索引文件名,文件名與文件名之間用分號隔開。
  下面的代碼指定了一個索引文件ANIMALS.MDX,並且指定其中的NAME作為副索引來排序:
  AnimalsTable.IndexFiles := 'ANIMALS.MDX';
  AnimalsTable.IndexName := 'NAME';
  對於SQL表來說,記錄的排列順序取決於SQL語句的ORDER BY部分。可以設置IndexName屬性指定一個已有的索引,也可以設置IndexFieldNames屬性直接指定索引中的字段,讓SQL表按這些字段排序。
  IndexName屬性和IndexFieldNames屬性是互斥的,設置其中一個,另一個就被清空。
  IndexFieldNames屬性是一個字符串列表,可以列出所有要用來排序的字段的名稱,彼此之間用分號隔開。只能以升序排列,至於是否大小寫敏感,取決於服務器。
  下面的代碼試圖把PhoneTable表先按LastName字段再按FirstName字段排序:
  PhoneTable.IndexFieldNames := 'LastName;FirstName';
  注意:如果對Paradox和dBASE表使用IndexFieldNames屬性,Delphi 4將試圖查找有沒有類似的索引,如果沒有,就觸發異常。
8.3.3 檢查索引中的字段
  如果想知道一個索引中有幾個字段、有哪些字段,可以使用IndexFieldCount屬性和IndexFields屬性。
  IndexFieldCount屬性能夠返回索引中的字段的個數。IndexFields屬性是一個字符串列表,由索引中的字段的名稱組成。程序示例如下:
var I: Integer;
ListOfIndexFields: array[0 to 20} of string;
Begin
With CustomersTable Do
Begin
For I := 0 to IndexFieldCount - 1 Do
ListOfIndexFields[I] := IndexFields[I];
End;
End;
8.4 選擇部分記錄
  數據庫往往是龐大的,而一個應用程序通常只關心其中一部分數據。對於TTable構件來說,要限制應用程序能訪問的記錄有兩種方式。一是使用過濾技術,它不但適用於TTable,也適用於TQuery和TStoredProc。二是設置范圍,它只適用於TTable構件。
8.4.1 過濾和范圍的區別
  過濾和范圍都是為了選擇一部分記錄,但它們的工作方式是不同的。
  所謂范圍,是指一段連續的記錄,這些記錄的值在一定的范圍內。例如,假設有一個雇員表格,本來按LastName字段排序,如果關心LastName字段的值在“Jones”和“Smith”之間的記錄,就需要設置范圍。只能對排序的Paradox和dBASE表格設置范圍,對SQL表格,也可以對IndexFieldNames屬性中出現的字段設置范圍。
  而過濾則不需要依賴於索引,它類似於SQL Select語句,只選擇滿足特定條件的記錄,這些記錄不一定是連續的。例如,假設有一個雇員表格,可以用過濾技術找出其中出生在California並且在公司任職超過5年以上的雇員。
  從功能上講,過濾的功能比設置范圍的功能強。不過,對於一個很大的數據庫並且記錄已經按某個字段排序,用范圍來選擇部分記錄比較快。
8.4.2 設置范圍的起點
  要設置范圍的起點,就要調用SetRangeStart。SetRangeStart將使數據集進入dsSetKey狀態。一旦調用了SetRangeStart后,以后對字段的賦值就認為是范圍的起點。對於Paradox 和dBASE來說,調用SetRangeStart后,您只能對關鍵字段賦值。
  例如,假設有一個CUSTOMER表,基於CustNo字段排序。可以這樣設置范圍的起點和終點:
With Customers Do
Begin
SetRangeStart;FieldByName('CustNo') := StartVal.Text;
SetRangeEnd;
If EndVal.Text <> '' then
FieldByName('CustNo') := EndVal.Text;
ApplyRange;
End;
  讀者可能注意到,在調用了SetRangeEnd后,首先檢查用戶是否在編輯框EndVal中輸入了范圍的終點,這是因為,如果范圍的終點設為NULL,將不會有任何記錄被選擇。
  如果索引中有多個字段,可以在調用了SetRangeStart后對所有字段賦值,也可以只對部分字段賦值。如果某個字段沒有被賦值,就認為該字段的值是NULL。如果在調用了SetRangeStart后對不是索引中的字段賦值,則是無效的。
  如果您不想設置范圍的起點,只想設置終點,可以不調用SetRangeStart。
8.4.3 設置范圍的終點
  要設置范圍的終點,就要調用SetRangEnd函數。SetRangeEnd將使數據集進入dsSetKey狀態。一旦調用了SetRangeEnd函數后,以后對字段的賦值就認為是范圍的終點。對於Paradox 和dBASE來說,調用SetRangeEnd函數后,只能對關鍵字段賦值。
  注意:范圍的起點可以省略,但終點不能省略,即使終點無關緊要。如果沒有設置終點,將認為終點是NULL,此時,將不會有任何記錄被選擇。下面這個例子基於LastName字段設置范圍:
With Table1 Do
Begin
SetRangeStart;
FieldByName('LastName') := Edit1.Text;
SetRangeEnd;FieldByName('LastName') := Edit2.Text;
ApplyRange;
End;
  如果索引中有多個字段,可以在調用SetRangeEnd函數后對所有字段賦值,也可以只對部分字段賦值。如果某個字段沒有被賦值,就認為該字段的值是NULL。如果在調用了SetRangeEnd后對不是索引中的字段賦值,將觸發異常。
8.4.4 同時設置起點和終點
  其實,不必分別調用SetRangeStart和SetRangeEnd來設置范圍的起點和終點,只要調用SetRange就可以同時設置起點和終點。SetRange將使數據集進入dsSetKey狀態。
  SetRange需要傳遞兩個數組類型的參數,一個用於設置起點,另一個用於設置終點,程序示例如下:
  SetRange([Edit1.Text, Edit2.Text], [Edit3.Text, Edit4.Text]);
  如果索引中有多個字段,您可以在調用SetRange時對所有字段賦值,也可以只對部分字段賦值。如果某個字段沒有被賦值,就認為該字段的值是NULL。在調用SetRange 時對不是索引中的字段賦值是無效的。
  注意:調用SetRange時,范圍的起點可以省略,但終點不能省略,即使終點是無關緊要的。如果您沒有設置終點,將認為終點是NULL,此時,將不會有任何記錄被選擇。
8.4.5 近似匹配
  如果組成索引的字段是字符串類型,可以在調用SetRangeStart、SetRangeEnd或SetRange函數時給出字段的近似值,程序示例如下:
Table1.SetRangeStart;Table1['LastName'] := 'Smith';
Table1.SetRangeEnd;Table1['LastName'] := 'Zzzzzz';
Table1.ApplyRange;
  下列代碼將使所有LastName字段的值大於“Sm”並且FirstName字段的值大於“J”的記錄被選擇。
Table1.SetRangeStart;Table1['LastName'] := 'Sm';
Table1['FirstName'] := 'J';
Table1.SetRangeEnd;Table1['LastName'] := 'Zzzzzz';
Table1.ApplyRange;
8.4.6 應用和取消范圍
  調用SetRangeStart、SetRangeEnd或SetRange只是設置了范圍的起點和終點,但還沒有真正地使范圍有效,還需要調用ApplyRange函數。
  調用了ApplyRange 后,應用程序只能工作於一定范圍內的記錄。如果以后又想工作於所有的記錄,可以調用CancelRange。不過,調用了CancelRange后,原先設置的起點和終點仍然保留,因為下次還可能要用到它們。起點和終點的設置將一直保留,除非設置了新的起點和終點。
8.4.7 修改范圍的起點和終點
  要修改原有的起點和終點,可以調用EditRangeStart和EditRangeEnd函數。調用EditRangeStart和EditRangeEnd將使數據集處於dsSetKey狀態。
  一旦調用EditRangeStart后,以后對字段的賦值就認為是范圍的起點。對於Paradox 和dBASE來說,調用EditRangeStart后,只能對關鍵字段賦值。
  一旦調用EditRangeEnd后,以后對字段的賦值就認為是范圍的終點。對於Paradox 和dBASE來說,調用EditRangeEnd后,只能對關鍵字段賦值。
8.5 對表格整體的操作
  下面介紹幾個對表格整體的操作,包括刪除表格中的所有記錄、刪除表格、給表格換名、創建新的表格、兩個表格之間保持同步。
8.5.1 刪除表格中的所有記錄
  要刪除表格中的所有記錄,可以調用EmptyTable函數。對於SQL表來說,必須擁有刪除記錄的權限。用EmptyTable刪除的記錄是不能恢復的。
  在調用EmptyTable之前,必須先設置DatabaseName、TableName和TableType等屬性,如果表處於打開狀態,必須是以獨占方式打開的。程序示例如下:
Procedure TForm1.Button1Click(Sender: TObject);
Begin
With Table1 Do
Begin
Active := False;
DatabaseName := 'Delphi_Demos';
TableName := 'CustInfo';
TableType := ttParadox;
EmptyTable;
End;
End;
8.5.2 刪除一個表格
  在設計期,要刪除一個表格,可以用鼠標右鍵單擊TTable構件,在彈出的菜單中選擇“Delete Table”命令。當然,必須事先設置了DatabaseName屬性和TableName屬性,彈出的菜單中才會有“Delete Table”命令。
  在運行期,要刪除一個表格,可以調用DeleteTable。注意:用DeleteTable刪除的表格是不可恢復的。在調用DeleteTable函數之前,必須設置DatabaseName、TableName和TableType屬性,並且表格必須處於關閉狀態。程序示例如下:
With Table1 Do
Begin
Active := False;
DatabaseName := 'DBDEMOS';
TableName := 'Customer';
TableType := ttParadox;
DeleteTable;
End;
8.5.3 給表格換名
  在設計期,要給一個Paradox或dBASE表換名,可以用鼠標右鍵單擊TTable構件,在彈出的菜單中選擇“Rename Table”命令。
  在運行期,要給一個Paradox或dBASE表換名,可以調用RenameTable函數,例如:
  Customer.RenameTable('CustInfo');
8.5.4 創建表格
  既可以在設計期也可以在運行期創建表格。通過“Create Table”命令(設計期)或CreateTable函數(運行期),無須具備SQL的知識就能創建一個表格。不過,必須對數據集構件的有關屬性、方法和事件比較熟悉,尤其是對TTable。
  下面是創建一個表格的一般步驟:
  第一步是設置DatabaseName屬性指定要把表格放到哪個數據庫中。
  第二步是設置TableType屬性指定表格的類型,如果是Paradox、dBASE或ASCII文本,就把TableType屬性設為ttParadox、ttDBase或ttASCII。如果是其他類型,就把TableType屬性設為ttDefault。
  第三步是設置TableName屬性指定表格的名稱。如果TableType屬性設為ttParadox或ttDBase,您不必給出擴展名。
  第四步是建立字段定義。在設計期,您可以在對象觀察器中單擊FieldDefs屬性邊上的省略號按鈕,Delphi 4將打開一個如圖8.2所示的編輯器:
  圖8.2 建立字段定義
  單擊工具欄上的按鈕可以創建一個新的字段,單擊按鈕可以刪除一個字段,單擊按鈕可以把字段的順序上移,單擊按鈕可以把字段的順序下移。
  選擇其中一個字段,就可以在對象觀察器中設置字段(TFieldDef對象)的定義。
  在運行期,可以調用TFieldDefs的Add函數創建一個字段。
  第五步是建立索引定義(可選)。在設計期,您可以在對象觀察器中單擊IndexDefs屬性邊上的省略號按鈕,Delphi 4將打開一個如圖8.3所示的編輯器:
  圖8.3 建立索引定義
  單擊工具欄上的按鈕可以創建一個新的索引,單擊按鈕可以刪除一個索引,單擊按鈕可以把索引的順序上移,單擊按鈕可以把索引的順序下移。
  選擇其中一個字段,就可以在對象觀察器中設置索引(TIndexDef對象)的定義。
  在運行期,可以調用TIndexDefs的Add函數創建一個新的索引。
  注意:在設計期,可以借用一個已有的表格中的字段定義和索引定義。首先要設置DatabaseName屬性和TableName屬性指定一個已有的表格,然后在TTable構件上單擊鼠標右鍵,在彈出的菜單中選擇“Update Table Definition”命令,此命令將使這個表格中的字段定義和索引定義讀到FieldDefs屬性和IndexDefs屬性中。接着,按第一步和第二步設置DatabaseName屬性和TableName屬性。
  第六步是創建這個表格。在設計期,可以用鼠標右鍵單擊TTable構件,在彈出的菜單中選擇“Create Table”命令。在運行期,可以調用CreateTable函數。
  注意:如果新建的表格與一個已有的表格重名,已有的表格將被覆蓋,並且無法恢復。
  下面的代碼演示了怎樣在運行期動態地創建一個表格:
var
NewTable: TTable;
NewIndexOptions: TIndexOptions;
TableFound: Boolean;
Begin
NewTable := TTable.Create;
NewIndexOptions := [ixPrimary, ixUnique];
With NewTable Do
Begin
Active := False;
DatabaseName := 'DBDEMOS';
TableName := Edit1.Text;
TableType := ttDefault;
FieldDefs.Clear;
FieldDefs.Add(Edit2.Text, ftInteger, 0, False);
FieldDefs.Add(Edit3.Text, ftInteger, 0, False);
IndexDefs.Clear;
IndexDefs.Add('PrimaryIndex? Edit2.Text, NewIndexOptions);
End;
TableFound := FindTable(Edit1.Text);
If TableFound = True then
 If MessageDlg('Overwrite existing table ' + Edit1.Text + '?', mtConfirmation,mbYesNo, 0) = mrYes then
  TableFound := False;
If not TableFound then
  CreateTable;
End;
8.5.5 兩個表格之間保持同步
  多個TTable構件可以指向同一個表格,即它們的DatabaseName屬性和TableName屬性相同。每個TTable構件所連接的TDataSource構件可以不同,也就是說,它們完全可以獨立地工作,在不同的TTable 構件中,當前記錄的位置是不同的。例如,在Table1中,第2條記錄是當前記錄,而在Table2中,第3條記錄是當前記錄。
  不過,可以調用GotoCurrent函數使多個TTable構件保持同步。例如,CustomerTableOne中第2條記錄是當前記錄,而在CustomerTableTwo中,第3條記錄是當前記錄。可以這樣調用GotoCurrent:
  CustomerTableOne.GotoCurrent(CustomerTableTwo);
  如果要同步的兩個TTable構件不在同一個數據模塊或窗體內,可以這樣寫:
  CustomerTableOne.GotoCurrent(Form2.CustomerTableTwo);
8.6 Master/Detail關系
  TTable構件的MasterSource屬性和MasterFields屬性可以用來建立一對多的Master/Detail關系。
  這兩個屬性都可用於Detail表。其中,MasterSource屬性用於指定一個TDataSource構件,該構件的DataSet屬性指定了Master表。
  MasterFields屬性用於指定一個或幾個字段,這些字段用於連接Master表和Detail表。如果有多個字段的話,彼此之間要用分號隔開,例如:
  Table1.MasterFields := 'OrderNo;ItemNo';
  下面我們舉例說明建立Master/Detail關系的一般步驟。假設Master表叫CustomersTable,Detail表叫OrdersTable。
  第一步,把兩個TTable構件和兩個TDataSource構件放到窗體上。
  第二步,把第一個TTable構件的DatabaseName屬性設為DBDEMOS,把TableName屬性設為CUSTOMER,把Name屬性設為CustomersTable。
  第三步,把第二個TTable構件的DatabaseName屬性設為DBDEMOS,把TableName屬性設為ORDERS,把Name屬性設為OrdersTable。
  第四步,把第一個TDataSource構件的Name屬性設為CustSource,把DataSet屬性設為CustomersTable。
  第五步,把第二個TDataSource構件的Name屬性設為OrdersSource,把DataSet屬性設為OrdersTable。
  第六步,把兩個TDBGrid構件放到窗體上,把其中第一個TDBGrid構件的DataSource屬性設為CustSource,把第二個TDBGrid構件的DataSource屬性設為OrdersSource。
  第七步,把OrdersTable的MasterSource屬性設為CustSource,這就把CUSTOMER表與ORDERS表連接起來。
  第八步,單擊OrdersTable的MasterFields屬性邊上的省略號按鈕打開“FieldLink Designer”對話框,在“Available Indexes”框內選擇CustNo,然后在“DetailFields”框和“Master Fields”框都選擇CustNo,單擊“Add”按鈕。
  第九步,把CustomersTable和OrdersTable的Active屬性設為True。
  第十步,編譯和運行這個程序。您將發現,在第一個柵格中瀏覽CUSTOMER表時,第二個柵格中的ORDERS表將顯示當前客戶的所有訂單。
8.7 嵌 套 表
  Delphi 4用TNestedTable構件來訪問嵌套表中的數據,TNestedTable是從TBDEDataSet繼承下來的。下面是訪問嵌套表的一般步驟:
  第一步,把一個數據集構件如TTable、TQuery放到窗體或數據模塊上,這個數據集中必須包含有TDataSetField或TReferenceField類型的字段。
  第二步,把一個TNestedTable構件放到窗體或數據模塊上,設置它的DataSetField屬性指定要訪問的TDataSetField或TReferenceField類型的字段,可以從下拉列表中選擇。
  第三步,把一個TDataSource構件放到窗體或數據模塊上,設置它的DataSet屬性指定TNestedTable構件。
  第四步,把一個數據控件如TDBGrid構件放到窗體或數據模塊上,設置它的DataSource屬性指定TDataSource構件。
  第五步,在設計期或運行期把TNestedTable構件的Active屬性設為True。
8.8 從另一個表格中引入數據
  TTable構件的BatchMove函數能夠從其他表中引入數據,包括復制、添加、修改或刪除記錄,並返回被操作的記錄數。
  BatchMove函數需要傳遞兩個參數,一個是源數據集的名稱,另一個是操作方式。BatchMove函數支持以下操作方式:
.batAppend把源表中的所有記錄復制到目標表的末尾;
.batUpdate用源表中的記錄更新目標表中的記錄;
.batAppendUpdate把源表中不存在於目標表中的記錄添加在目標表的末尾;
.batDelete刪除源表在目標表中重復的記錄;
.batCopy把源表復制到目標表。
  下面的代碼用Customer表中的記錄更新當前表中的記錄:
  Table1.BatchMove('CUSTOMER.DB', batUpdate);
  與TBatchMove構件相比,BatchMove函數的功能比較簡單。如果要移動很多的記錄,最好選用TBatchMove構件。
8.9 使用TBatchMove
  TBatchMove構件封裝了一部分BDE的功能,能夠從另一個數據集中引入數據。TBatchMove構件經常用於從服務器下載數據或者把本地的數據上載到服務器。
  TBatchMove構件能夠自動建立一個新的表,並映射源數據集中的字段名稱和類型。
8.9.1 使用TBatchMove構件的一般步驟
  第一步,把一個TTable構件或TQuery構件放到窗體或數據模塊上作為源數據集。
  第二步,把另一個TTable構件放到窗體或數據模塊上作為目標數據集。
  第三步,把一個TBatchMove 構件放到數據模塊上,設置它的Source屬性指定源數據集,設置它的Destination屬性指定目標數據集。
  第四步,設置Mode屬性指定操作方式,可以設為batAppend、batUpdate、batAppendUpdate、batCopy、batDelete等。
  第五步(可選),設置ProblemTableName屬性指定一個表的名字,執行批量移動操作時有問題的記錄將放到這個表中。設置KeyViolTableName屬性指定一個表的名字,更新一個Paradox表時違反主索引定義的記錄將放到這個表中。設置ChangedTableName 屬性指定一個表的名字,執行批量移動操作時目標表中變動的記錄將放到這個表中。
  第六步(可選),設置Mappings屬性指定字段的映射方式。
  第七步,調用Execute執行批量移動操作。
8.9.2 指定操作方式
  TBatchMove構件的Mode屬性用於指定操作方式:
.batAppend把源表中的所有記錄復制到目標表的末尾;
.batUpdate用源表中的記錄更新目標表中的記錄;
.batAppendUpdate把源表中不存在於目標表中的記錄添加在目標表的末尾;
.batDelete刪除源表在目標表中重復的記錄;
.batCopy把源表復制到目標表。
  對於batAppend方式來說,目標數據集必須是已存在的。如果需要的話,BDE會自動進行數據類型的轉換,不過,轉換並不總是能成功的。對於batUpdate方式來說,目標數據集必須是已存在的,並且必須已建立索引。TBatchMove構件根據關鍵字段用源數據集中的記錄更新目標數據集中匹配的記錄。在更新過程中,BDE會盡可能地轉換數據類型。
  對於batAppendUpdate方式來說,目標數據集必須是已存在的,並且必須已建立索引。TBatchMove構件根據關鍵字段用源數據集中的記錄更新目標數據集中匹配的記錄,如果沒有找到匹配的記錄,TBatchMove構件就把記錄添加在源數據集的末尾。
  對於batCopy方式來說,目標數據集最好是不存在的,如果已經存在,目標數據集中的記錄會被源數據集中的記錄覆蓋。如果源數據集和目標數據集的結構不同,例如,一個是Paradox,另一個是InterBase,BDE會盡可能地轉換。不過,TBatchMove構件不會復制源數據集的索引、糾錯規則、存儲過程。
  對於batDelete方式來說,目標數據集必須是已存在的,並且必須已建立索引。TBatchMove構件根據關鍵字段刪除目標數據集中重復的記錄,
8.9.3 映射字段類型
  默認情況下,在源數據集和目標數據集之間批量移動記錄時是以字段的位置匹配的,也就是說,源數據集中的第一個字段到目標數據集中仍然是第一個字段,依次類推。如果不希望按字段的位置匹配,而希望按字段的名稱匹配,就要設置Mappings屬性。
  Mappings屬性是一個字符串列表。假設源數據集中有一個字段叫SourceColName,希望它在目標數據集中匹配DestColName字段,Mapping屬性中應加入這么一行:
  DestColName = SourceColName
  程序示例如下:
Procedure TForm1.Button1Click(Sender: TObject);
var Maps: TStringList;
Begin
With Maps Do
Begin
Clear;Add('CustNo=CustomerNum');
Add('SSN');
End;
BatchMove1.Mappings := Maps;
End;
  如果等號兩邊的字段的數據類型不同,Delphi 4會盡可能地轉換,但可能會丟失數據或精度。假設一個字段是CHAR(10),如果試圖轉換為CHAR(5),后5個字符將被截掉。
8.9.4 執行批量移動操作
  調用Execute函數將執行批量移動操作。例如,假設BatchMoveAdd是TBatchMove構件的名稱,要執行批量移動操作,可以這樣寫:
  BatchMoveAdd.Execute;
  實際上,在設計期也可以執行批量移動操作,方法是:在TBatchMove 構件上單擊鼠標右鍵,在彈出的菜單中選擇“Execute”命令。
  調用了Execute后,可以訪問MovedCount屬性,查看實際移動了多少條記錄。
  RecordCount屬性用於限制每次批量移動的記錄數。如果RecordCount屬性設為0,表示沒有限制。
8.9.5 處理錯誤
  在進行批量移動操作的過程中,有可能發生兩種類型的錯誤,一是數據類型轉換錯誤,還有一種是違反數據完整性。
  如果AbortOnProblem屬性設為True,只要遇到一個數據類型轉換錯誤就會停止批量移動操作。如果AbortOnProblem屬性設為False,即使遇到數據類型轉換錯誤也不會停止批量移動操作。可以在ProblemTableName屬性指定的表中找到這些有錯誤的記錄。
  ProblemCount屬性返回有錯誤的記錄數。如果AbortOnProblem屬性設為True,ProblemCount屬性最多返回1,因為一旦遇到一個錯誤,操作就停止了。
  AbortOnKeyViol屬性類似於AbortOnProblem屬性,如果AbortOnKeyViol屬性設為True,只要遇到一個違反數據完整性的錯誤就會停止批量移動操作。
  可以設置ChangedTableName屬性指定一個表的名稱,讓TBatchMove構件把目標數據集中所有變化的記錄寫到這個表中。
  可以設置KeyViolTableName屬性指定一個表的名稱,讓TBatchMove構件把所有違反數據完整性的記錄放到這個表中。
  可以設置ProblemTableName屬性指定一個表的名稱,讓TBatchMove構件把所有有數據類型轉換錯誤的記錄寫到這個表中。
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM