关系数据库都提供大文档的存储和提取对于视频资料音频资料图象资料等大文档一般需要另外开辟字段用于存储摘要信息因此在查询和检索时并不访问大字段而只是在存储和提取时才操作 大字段例如你不能对Oracle中的LONG RAW类型进行LIKE介词的查询更不能使用等号 =查询这对于存储大段文本(容量超过K)同时又需要全文检索是相当不便的本文将介绍如何利用数据库的字符串数据类型存取和查询大段文本这里以Oracle数据库和Delphi应用程序为例重点介绍如何在数据库中存取 RTF文档
对于纯文本可以简单地将其分割成若干个串分别存储到VARCHAR()字段中即可在查询时可以使用LIKE比较从而达到全文检索的目的为了保留换行等段落信息应当将回车换行(##)也作为串的一部分进行保存数据录入时可以提供Memo控件(不是DBMemo)进行录入然后顺序连接各行当连接成的串临近个字符(单字节字符)长度时就存入一条记录然后对剩余的行重复上述操作这样最终将纯文本存成若干长度不超过的VARCHAR() 字段中这里需要另外开辟字段 用于存储文本编号和子序号以便区分不同的文本和读取文本时顺序连接所有的子串查询纯文本时就可以象查询普通的VARCHAR 字段一样可以使用LIKE也可以使用等号 =(几乎不需要使用)需要注意的是可能用户提供的关键字正好被存储在不同的子串中这时是查询不到的因此在设计时应当考虑存储重复的串例如每个子串中仅有前 个字符是有效字符最后 个字符用于存储 下一个子串的前 个字符这样就避免了关键字被分开的情况唯一的不足是必须限制用户输入的查询关键字长度不得超过 个字符(个汉字)但这很正常算不上不足
事实上同样可以利用这一技巧对 RTF文档进行存取和查询这时用于录入和显示 RTF文档的是 RichEdit控件(不是DBRichEdit)而不再是Memo控件对于 RTF文档的存取不能象存取纯文本那样通过Memo的属性LinesStrings[Index]进行操作(尽管RichEdit控件具有相同的属性)因为这样做就无法保存文档的格式了需要利用的是RichEdit的两个方法SaveToFile和LoadFromFile需要了解的 是 RTF文件中用纯字符描述字体字号文本等各种格式信息和内容信息因此存储和提取时可以视为纯文本进行操作但对于查询就不能直接用LIKE加关键字的方式进行因为 RTF文档中的每一个汉字都是用特殊的表示方法存储只有单字节字符是原样存储所以在查询时要对关键字进行处理才能 用在查询语句中
在测试这个例子之前必须有如下的数据结构这里以Oracle创建表的 SQL语句形式给出
(*
CREATE TABLE TEST( { 表名为 TEST }
DOCID NUMBER NOT NULL { 文档编号 }
DOCNAME VARCHAR() NOT NULL { 文档标题 }
SUBID NUMBER NOT NULL { 文档子编号 }
TEXT VARCHAR() NOT NULL { 子文档内容 }
PRIMARY KEY(DOCID SUBID)); { 联合主键 }
*) 下面是程序实例中的主要部分
{ }
const
BufSize = ; { 串的最大容量 }
type
TBuffer = array [BufSize] of Char; { 串缓存 }
TFileOfChar = file of Char; { 字符类型文件 }
TChnChar = string[]; { 汉字字符类型 }
{ SQL查询返回首记录首字段的值 }
function SelectSQL(S: string): Variant;
begin
Result := NULL;
with TADOQueryCreate(Application) do try
Connection := FMainADOConnection;
SQLAppend(S);
SQLSaveToFile(c:\atxt);
Open;
Result := Fields[]AsVariant;
finally
Free;
end;
end;
{ 下面的函数将RTF文档存入数据库 }
function RTFToDB(ARichEdit: TRichEdit; { 文档容器 }
DocName: string; { 文档标题 }
ATable: TADOTable { 操作的表 }
): Boolean; { 返回类型 }
const
TmpFileName = c:\xrtf; { 临时文档 }
var
DocID SubID L: Integer; { 局部变量 }
S: string; { 串 }
F: TFileOfChar; { 字符文件 }
Buf: TBuffer; { 文本缓存 }
begin
ARichEditLinesSaveToFile(TmpFileName);{ 先存入文件 }
AssignFile(F TmpFileName); { 打开文件 }
Reset(F);
try
DocID := { 产生新的文档编号 }
SelectSQL(SELECT NVL(MAX(DOCID) + ) FROM TEST);
with ATable do if not Active then Active := True;{ 确认表打开 }
SubID := ; { 初始化子编号 }
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\xrtf; { 临时文件 }
var
S: string; { 局部串变量 }
F: TFileOfChar; { 字符文件 }
Buf: TBuffer; { 串缓存 }
I L: Integer; { 局部变量 }
begin
ARichEditClear; { 清除当前显示的内容 }
AssignFile(F TmpFileName); { 关联文件 }
try
Rewrite(F); { 打开文件准备写入从数据库读出的数据 }
with AQuery do begin
Active := False; { 关闭数据集 }
SQLClear; { 重建SQL语句 }
SQLAppend(SELECT SUBID TEXT FROM TEST WHERE DOCNAME = +
DocName + ORDER BY SUBID);
Open; { 打开数据集 }
if RecordCount <> then begin { 确认数据集非空 }
First; { 移到首记录子文档 }
repeat { 读出一条子文档并写入文件 }
S := FieldByName(TEXT)AsString;
L := Length(S);
for I := to L do Buf[I] := S[I];
BlockWrite(F Buf L);
Next;
until EOF;
end;
end;
CloseFile(F);{ 关闭文件 }
ARichEditLinesLoadFromFile(TmpFileName);{ 从文件中装入RTF文档 }
Result := True; { 读取成功 }
except { 读取失败 }
try CloseFile(F); except end;
Result := False;
end;
DeleteFile(TmpFileName); { 删除临时文件 }
end;
{ 下面的函数将汉字单字转换成RTF中表示的形式 }
{ 如表示汉字国的是ASCII(b)和ASCII(fa)这里是十六进制 }
{ 那么在 RTF文件中对国字的表示占用了 个字节 }
{ \b\fa }
{ 因此需要在查询之前进行转换由于表示方法中含有Delphi用于 }
{ 字符串的分解符单撇号因此在转换时需要考虑这一点 }
{ 否则就不能构造出正确的 SQL查询语句 }
function ChnCharToRTFCode(Ch: TChnChar): string;
var
C C: Char;
O O: Byte;
S: string;
begin
C := Ch[];
C := Ch[];
O := Ord(C);
O := Ord(C);
S := Format(\%X [O]) + Format(\%X [O]);
Result := Lowercase(S);{ 转换为小写 }
end;
{ 根据需要检索的关键字转换成LIKE中使用的串 }
{ 这里用于区别汉字的方法是根据编码 }
{ 按照Windows 中的双字节编码规则对于双字节字符 }
{ 如汉字字符是由两个字节构成其中第一个字节是 }
{ 引导字符汉字引导字符的ASCII 码大于 因此 }
{ 可以根据此特点来区分汉字和单字节字符 }
function MakeLikeRTFString(StrToFind: string): string;
var
I: Integer;
ChnChar: TChnChar;
S: string;
begin
S := ;
I := ;
while I < Length(StrToFind) do begin
Inc(I);
if Integer(StrToFind[I]) >= $ then begin{ 汉字的首字节一定不小于 }
ChnChar := StrToFind[I] + StrToFind[I + ];
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;
{ }