Oracle LONG RAW/BLOB 不支持 LIKE/= 查詢,無法對大文本進行全文檢索。本文方案:將 RTF 文檔按固定長度 (如 2000 字符) 分段存入 VARCHAR2(2000) 多行,保留 RTF 格式碼,利用數據庫原生索引實現模糊查詢。
CREATE TABLE TEST (
DOCID NUMBER NOT NULL, -- 文檔主鍵
DOCNAME VARCHAR2(40) NOT NULL, -- 文檔標題
SUBID NUMBER NOT NULL, -- 片段序號 (1,2,3...)
TEXT VARCHAR2(2000) NOT NULL, -- RTF 片段內容
PRIMARY KEY (DOCID, SUBID)
);
function RTFToDB(ARichEdit: TRichEdit; DocName: string; ATable: TADOTable): Boolean;
const TmpFile = 'c:\x.rtf'; BufSize = 2000;
var DocID, SubID, L: Integer; S: string; F: file of Char; Buf: array[1..BufSize] of Char;
begin
ARichEdit.Lines.SaveToFile(TmpFile); // RichEdit 導出完整 RTF (含格式碼)
AssignFile(F, TmpFile); Reset(F);
try
DocID := SelectSQL('SELECT NVL(MAX(DOCID)+1, 101) FROM TEST'); // 獲取新 ID
if not ATable.Active then ATable.Active := True;
SubID := 0;
while not Eof(F) do
begin
Inc(SubID);
BlockRead(F, Buf, BufSize, L); // 讀取 2000 字符
SetLength(S, L); Move(Buf[1], PChar(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(TmpFile);
end;
function RTFFromDB(ARichEdit: TRichEdit; DocName: string; AQuery: TADOQuery): Boolean;
const TmpFile = 'c:\temp\x.rtf';
var S: string; F: file of Char; Buf: array[1..2000] of Char; I, L: Integer;
begin
ARichEdit.Clear; AssignFile(F, TmpFile);
try
Rewrite(F);
with AQuery do
begin
Active := False; SQL.Clear;
SQL.Add('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(TmpFile); // RichEdit 解析 RTF 還原格式
Result := True;
except Result := False; end;
DeleteFile(TmpFile);
end;
RTF 中漢字編碼為 \'XX\'YY (如「國」→ \'B9\'FA),單字節字符 (ASCII) 保持原樣。查詢前必須將用戶輸入關鍵字按相同規則轉碼。
function ChnCharToRTFCode(Ch: string): string; // Ch 為雙字節漢字
var C1, C2: Char; O1, O2: Byte;
begin
C1 := Ch[1]; C2 := Ch[2]; O1 := Ord(C1); O2 := Ord(C2);
Result := LowerCase(Format('\''%2.2X\'%2.2X', [O1, O2])); // 輸出 \'b9\'fa
end;
function MakeLikeRTFString(StrToFind: string): string;
var I: Integer; Ch: string; S: string;
begin
S := ''; I := 1;
while I <= Length(StrToFind) do
begin
if Ord(StrToFind[I]) >= $80 then // 判斷雙字節字符引導位
begin
Ch := Copy(StrToFind, I, 2); Inc(I, 2);
S := S + ChnCharToRTFCode(Ch);
end
else
begin
if StrToFind[I] = '''' then S := S + ''''''; // 單引號轉義 ''
S := S + StrToFind[I]; Inc(I);
end;
end;
Result := S;
end;
function MakeLikeString(StrToFind: string): string;
begin
Result := 'SELECT DISTINCT DOCNAME FROM TEST WHERE TEXT LIKE ''%' + MakeLikeRTFString(StrToFind) + '%''';
end;
若關鍵字恰好被切分在兩段邊界,單段 LIKE 會漏查。方案:每段有效長度 1900,末尾 100 字符存儲下一段前 100 字符 (重疊)。查詢關鍵字長度限制 ≤ 100 字符 (50 漢字),即可保證命中。
| 優點 | 缺點/限制 |
|---|---|
| 利用標準 VARCHAR 索引,查詢極快 | 存儲空間增加 ~5% (重疊段) |
| 保留完整 RTF 格式 (字體、顏色、段落) | 關鍵字長度受限 (≤ 重疊長度) |
| 無需全文檢索引擎 (Oracle Text 等) | RTF 格式碼干擾語義查詢 (如搜「紅色」會匹配格式碼) |
| 兼容任何支持 VARCHAR 的數據庫 | 大文檔分段數多,讀取需順序拼接 |
通過 RTF 文件流分段存 VARCHAR + 關鍵字 RTF 轉碼 + 重疊段策略,在不改變數據庫架構前提下,實現了富文本的高效存儲與全文檢索。代碼邏輯清晰,易於移植到其他數據庫 (SQL Server, PostgreSQL, MySQL) 及數據訪問組件 (FireDAC, UniDAC)。
record 實作 operator Add 實現結構體直接相加
多線程編程(15) - WaitableTimer 等待定時器對象(續):絕對時間觸發
在 Delphi 應用程式中使用 DLL:跨語言互調與靜態/動態載入完整指南
Delphi 輕量級自動化對象實現:$METHODINFO 與 TObjectDispatch 免註冊 COM 開發
Delphi 屬性 與 事件模型深度解析:從 Property 到 Message Handler 的封裝藝術
Windows 密碼遮罩背後的秘密:Delphi 破解星號密碼原理與實作