關系資料庫都提供大文檔的存儲和提取。對于視訊資料、音頻資料、圖象資料等大文檔,一般需要 另外開辟字段用于存儲摘要資訊,是以在查詢和檢索時并不通路大字段,而隻是在存儲和提取時才操作 大字段。例如,你不能對Oracle中的LONG RAW類型進行LIKE介詞的查詢,更不能使用等号“ =”查詢。 這對于存儲大段文本(容量超過2K)同時又需要全文檢索是相當不便的。本文将介紹如何利用資料庫的 字元串資料類型存取和查詢大段文本。這裡以Oracle資料庫和Delphi應用程式為例,重點介紹如何在資料庫中存取 RTF文檔。
對于純文字,可以簡單地将其分割成若幹個串,分别存儲到VARCHAR(2000)字段中即可。在查詢時 可以使用LIKE比較,進而達到全文檢索的目的。為了保留換行等段落資訊,應當将回車換行(#13#10) 也作為串的一部分進行儲存。資料錄入時可以提供Memo控件(不是DBMemo)進行錄入,然後順序連接配接各行,當連接配接成的串臨近2000個字元(單位元組字元)長度時,就存入一條記錄,然後對剩餘的行重複上述操作。這樣,最終将純文字存成若幹長度不超過2000的VARCHAR(2000) 字段中。這裡需要另外開辟字段 用于存儲文本編号和子序号,以便區分不同的文本和讀取文本時順序連接配接所有的子串。查詢純文字時, 就可以象查詢普通的VARCHAR 字段一樣,可以使用LIKE,也可以使用等号“ =”(幾乎不需要使用)。 需要注意的是,可能使用者提供的關鍵字正好被存儲在不同的子串中,這時是查詢不到的。是以,在設計時應當考慮存儲重複的串。例如,每個子串中僅有前1900個字元是有效字元,最後100 個字元用于存儲 下一個子串的前100 個字元。這樣就避免了關鍵字被分開的情況。唯一的不足是,必須限制使用者輸入的 查詢關鍵字長度不得超過100 個字元(50個漢字),但這很正常,算不上不足。
事實上,同樣可以利用這一技巧對 RTF文檔進行存取和查詢。這時,用于錄入和顯示 RTF文檔的是 RichEdit控件(不是DBRichEdit),而不再是Memo控件。對于 RTF文檔的存取,不能象存取純文字那樣 通過Memo的屬性Lines.Strings[Index]進行操作(盡管RichEdit控件具有相同的屬性),因為這樣做就 無法儲存文檔的格式了。需要利用的是RichEdit的兩個方法:SaveToFile和LoadFromFile。需要了解的 是, RTF檔案中用純字元描述字型、字号、文本等各種格式資訊和内容資訊。是以,存儲和提取時可以 視為純文字進行操作。但對于查詢,就不能直接用LIKE加關鍵字的方式進行。因為 RTF文檔中的每一個 漢字都是用特殊的表示方法存儲,隻有單位元組字元是原樣存儲。是以在查詢時要對關鍵字進行處理才能 用在查詢語句中。
在測試這個例子之前,必須有如下的資料結構,這裡以Oracle建立表的 SQL語句形式給出:
(*
CREATE TABLE TEST( { 表名為 TEST }
DOCID NUMBER NOT NULL, { 文檔編号 }
DOCNAME VARCHAR(40) NOT NULL, { 文檔标題 }
SUBID NUMBER NOT NULL, { 文檔子編号 }
TEXT VARCHAR(2000) NOT NULL, { 子文檔内容 }
PRIMARY KEY(DOCID, SUBID)); { 聯合主鍵 }
*)
下面是程式執行個體中的主要部分:
{ ... ... }
const
BufSize = 2000; { 串的最大容量 }
type
TBuffer = array [1..BufSize] of Char; { 串緩存 }
TFileOfChar = file of Char; { 字元類型檔案 }
TChnChar = string[2]; { 漢字字元類型 }
{ SQL查詢,傳回首記錄首字段的值 }
function SelectSQL(S: string): Variant;
begin
Result := NULL;
with TADOQuery.Create(Application) do try
Connection := FMain.ADOConnection1;
SQL.Append(S);
SQL.SaveToFile('c:/a.txt');
Open;
Result := Fields[0].AsVariant;
finally
Free;
end;
end;
{ 下面的函數将RTF文檔存入資料庫 }
function RTFToDB(ARichEdit: TRichEdit; { 文檔容器 }
DocName: string; { 文檔标題 }
ATable: TADOTable { 操作的表 }
): Boolean; { 傳回類型 }
const
TmpFileName = 'c:/x.rtf'; { 臨時文檔 }
var
DocID, SubID, L: Integer; { 局部變量 }
S: string; { 串 }
F: TFileOfChar; { 字元檔案 }
Buf: TBuffer; { 文本緩存 }
begin
ARichEdit.Lines.SaveToFile(TmpFileName);{ 先存入檔案 }
AssignFile(F, TmpFileName); { 打開檔案 }
Reset(F);
try
DocID := { 産生新的文檔編号 }
SelectSQL('SELECT NVL(MAX(DOCID) + 1, 101) FROM TEST');
with ATable do if not Active then Active := True;{ 确認表打開 }
SubID := 0; { 初始化子編号 }
while not EOF(F) do begin
Inc(SubID);
BlockRead(F, Buf, BufSize, L); { 讀取兩千個字元 }
S := Buf;
SetLength(S, L); { 取實際讀取到的位元組數 }
with ATable do begin { 增加一條子文檔 }
Append;
FieldByName('DOCID').AsInteger := DocID;
FieldByName('DOCNAME').AsString := DocName;
FieldByName('SubID').AsInteger := SubID;
FieldByName('TEXT').AsString := S;
Post;
end;
end;
Result := True; { 存儲成功 }
except
Result := False;{ 存儲失敗 }
end;
CloseFile(F); { 關閉檔案 }
DeleteFile(TmpFileName);{ 删除檔案 }
end;
{ 下面的函數從資料庫中讀取RTF文檔,并在指定的容器中顯示 }
function RTFFromDB(ARichEdit: TRichEdit;{ RTF文檔容器 }
DocName: string; { 文檔标題 }
AQuery: TADOQuery { 操作的資料集 }
): Boolean; { 傳回類型 }
const
TmpFileName = 'c:/temp/x.rtf'; { 臨時檔案 }
var
S: string; { 局部串變量 }
F: TFileOfChar; { 字元檔案 }
Buf: TBuffer; { 串緩存 }
I, L: Integer; { 局部變量 }
begin
ARichEdit.Clear; { 清除目前顯示的内容 }
AssignFile(F, TmpFileName); { 關聯檔案 }
try
Rewrite(F); { 打開檔案,準備寫入從資料庫讀出的資料 }
with AQuery do begin
Active := False; { 關閉資料集 }
SQL.Clear; { 重建SQL語句 }
SQL.Append('SELECT SUBID, TEXT FROM TEST WHERE DOCNAME = ''' +
DocName + ''' ORDER BY SUBID');
Open; { 打開資料集 }
if RecordCount <> 0 then begin { 确認資料集非空 }
First; { 移到首記錄-子文檔 }
repeat { 讀出一條子文檔并寫入檔案 }
S := FieldByName('TEXT').AsString;
L := Length(S);
for I := 1 to L do Buf[I] := S[I];
BlockWrite(F, Buf, L);
Next;
until EOF;
end;
end;
CloseFile(F);{ 關閉檔案 }
ARichEdit.Lines.LoadFromFile(TmpFileName);{ 從檔案中裝入RTF文檔 }
Result := True; { 讀取成功 }
except { 讀取失敗 }
try CloseFile(F); except end;
Result := False;
end;
DeleteFile(TmpFileName); { 删除臨時檔案 }
end;
{ 下面的函數将漢字單字轉換成RTF中表示的形式。 }
{ 如表示漢字“國”的是ASCII(b9)和ASCII(fa),這裡是十六進制; }
{ 那麼在 RTF檔案中對“國”字的表示占用了 8個位元組: }
{ /'b9/'fa }
{ 是以,需要在查詢之前進行轉換。由于表示方法中含有Delphi用于 }
{ 字元串的分解符:單撇号“'”,是以在轉換時需要考慮這一點, }
{ 否則就不能構造出正确的 SQL查詢語句 }
function ChnCharToRTFCode(Ch: TChnChar): string;
var
C1, C2: Char;
O1, O2: Byte;
S: string;
begin
C1 := Ch[1];
C2 := Ch[2];
O1 := Ord(C1);
O2 := Ord(C2);
S := Format('/''''%2X', [O1]) + Format('/''''%2X', [O2]);
Result := Lowercase(S);{ 轉換為小寫 }
end;
{ 根據需要檢索的關鍵字轉換成LIKE中使用的串。 }
{ 這裡用于差別漢字的方法是根據編碼。 }
{ 按照Windows 中的雙位元組編碼規則,對于雙位元組字元 }
{ 如漢字字元,是由兩個位元組構成,其中第一個位元組是 }
{ 引導字元。漢字引導字元的ASCII 碼大于 127,是以 }
{ 可以根據此特點來區分漢字和單位元組字元。 }
function MakeLikeRTFString(StrToFind: string): string;
var
I: Integer;
ChnChar: TChnChar;
S: string;
begin
S := '';
I := 0;
while I < Length(StrToFind) do begin
Inc(I);
if Integer(StrToFind[I]) >= $80 then begin{ 漢字的首位元組一定不小于128 }
ChnChar := StrToFind[I] + StrToFind[I + 1];
Inc(I);
S := S + ChnCharToRTFCode(ChnChar);
end else begin{ 單位元組字元 }
S := S + StrToFind[I];
if StrToFind[I] = '''' then S := S + StrToFind[I];{ 單撇号的特殊處理 }
end;
end;
Result := S;
end;
{ 構造對關鍵字進行全文檢索的查詢語句 }
function MakeLikeString(StrToFind: string): string;
var
S: string;
begin
S := MakeLikeRTFString(StrToFind);
S := 'SELECT DISTINCT DOCNAME FROM TEST WHERE TEXT LIKE ''%' + S + '%''';
Result := S;
end;
{ ... ... } |