关于 SQL Server 2000 的 FOR XML AUTO 子句的问题(200分)

  • 主题发起人 主题发起人 OopsWare
  • 开始时间 开始时间
O

OopsWare

Unregistered / Unconfirmed
GUEST, unregistred user!
SQL Server 2000 可以使用 FOR XML AUTO 子句将查询完整的导出到 XML 文本,
配合 ADODB.Stream 可以将XML存储成为本地文件,另外通过 SQLXML3 的 Bulk Load
可以快速的将XML恢复回数据库。 我想用这两个方法实现数据库的备份和恢复,但问题
来了:
ADODB.Stream 是一个内存流,如果查询得到了一个巨大的表,例如DFW.letters表
有几百兆,ADODB.Stream将耗尽系统内存资源而出错。有没有一种文件流的形式,
令导出和写盘同步进行???
下面是一段PB的代码,我还尚未在Delphi下测试,大家将就一下了
function f_query_to_xml(string in_query, in_table, in_where, out_filename): integer
OLEObject cmd, conn, strmOut, rset
long i, xsdfile
string fname, fldsname = ""
// 初始化 ADO Object
cmd = Create OLEObject
cmd.ConnectToNewObject("ADODB.Command")
conn = Create OLEObject
conn.ConnectToNewObject("ADODB.Connection")
strmOut = Create OLEObject
strmOut.ConnectToNewObject("ADODB.Stream")
rset = Create OLEObject
rset.ConnectToNewObject("ADODB.RecordSet")
// 连接数据库
conn.Provider = "SQLOLEDB"
conn.Open("server="+sqlca.ServerName+";database="+sqlca.Database+";uid="+sqlca.LogId+";pwd="+sqlca.LogPass)
// 创建 XSD 文件
xsdfile = FileOpen(out_filename+".xsd", LineMode!, Write!, LockWrite!, Replace!)
FileWrite(xsdfile, '<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:sql="urn:schemas-microsoft-com:mapping-schema">')
FileWrite(xsdfile, '<xsd:element name="'+in_table+'" sql:relation="'+in_table+'" ><xsd:complexType><xsd:sequence>')
rset.Open("SELECT "+in_query+" FROM "+in_table+" WHERE 1=0", conn, 0, 1)
for i=0 to rset.Fields.Count()-1
fname = string( rset.Fields.Name )
if fldsname<>"" then
fldsname = fldsname + ","
choose case rset.Fields.Type
case 133, 134, 135
fldsname = fldsname + "CONVERT(varchar(10), "+fname+", 21) "+fname
FileWrite(xsdfile, '<xsd:element name="'+fname+'" sql:field="'+fname+'" type="xsd:date"/>')
case else
fldsname = fldsname + fname
FileWrite(xsdfile, '<xsd:element name="'+fname+'" sql:field="'+fname+'" type="xsd:string"/>')
end choose
next
FileWrite(xsdfile, '</xsd:sequence></xsd:complexType></xsd:element></xsd:schema>')
rset.Close()
FileClose(xsdfile)
// 生成 XML 数据
cmd.ActiveConnection = conn
cmd.Dialect = "{5d531cb2-e6ed-11d2-b252-00c04f681b71}"
cmd.CommandText = "<ROOT xmlns:sql='urn:schemas-microsoft-com:xml-sql'><sql:query>" &amp;
+ "SELECT "+fldsname+" FROM "+in_table+" FOR XML AUTO, ELEMENTS" &amp;
+ "</sql:query></ROOT>"
// 存为 XML 文件
strmOut.Open()
cmd.Properties("Output Stream").Value = strmOut
cmd.Execute(0, 0, 1024)
strmOut.Position = 0
strmOut.SaveToFile(out_filename+".xml", 2)
strmOut.Close()
// 释放
rset.DisConnectObject()
Destroy rset
strmOut.DisConnectObject()
Destroy strmOut
conn.DisConnectObject()
Destroy conn
cmd.DisConnectObject()
Destroy cmd
return 0
 
以前做过的一个大的数据库操作就是将sql代码改写,
一次大的查询改为多次小的查询,控制每次查出的记录数,如小于10000条
 
Delphi 的代码:
procedure TForm1.Button2Click(Sender: TObject);
var
ObjConn: OleVariant;
ObjCmd: OleVariant;
ObjSF: OleVariant;
begin
ObjConn := CoConnection.Create;
ObjCmd := CoCommand.Create;
ObjSF := CoStream.Create;
ObjConn.Provider := 'SQLOLEDB';
ObjConn.Open('server=(local);
database=DFW;
uid=sa;');
ObjCmd.ActiveConnection := ObjConn;
ObjCmd.Dialect := '{5d531cb2-e6ed-11d2-b252-00c04f681b71}';
ObjCmd.CommandText := '<ROOT xmlns:sql=''urn:schemas-microsoft-com:xml-sql''><sql:query>SELECT * FROM letters FOR XML AUTO, ELEMENTS</sql:query></ROOT>';
ObjSF.Open;
ObjCmd.Properties['Output Stream'].Value := ObjSF;
ObjCmd.Execute(0, 0, $0400);
ObjSF.Position := 0;
ObjSF.SaveToFile('letters.XML', adSaveCreateOverWrite);
ObjSF.Close;
ObjSF := NULL;
ObjCmd := NULL;
ObjConn := NULL;
end;
 
您的问题是:“有没有一种文件流的形式,令导出和写盘同步进行?”
让我试验一下。
 
要用到 ObjCmd.Properties['Output Stream'].Value := ObjSF;
的 objSF
必须是实现了 OLE DB 的 IStream 的 COM,目前我知道的这种 COM 只有
ADO 的 ADODB.Stream,ASP 的 response 以及 MSXML 的do
MDocument。
因此,苦苦寻找了实现 IStream 的方法,整整花了半天时间,最后找到了:
http://www.pjsoft.contactbox.co.uk/downloads/components/streams.html
经过提取其精华,终于制成了一个支持 FileStream COM。
因为,我现在的机器上没有 SQL Server 2000,只能这样试验:
var
xml: IXMLDOMDocument;
xsl: IXMLDOMDocument;
fs: IFileStream;
// 我做的 FileStream COM
begin
xml := CoDOMDocument.Create;
xsl := CoDOMDocument.Create;
fs := CoFileStream.Create;
fs.CreateFile('c:/tt.htm');
// 创建一个文件
xml.load('D:/test/ca.xml');
xsl.load('D:/test/ca.xsl');
xml.transformNodeToObject(xsl, fs);
// XSL 转换结果直接写入文件。
...
end;

结果非常成功!
DOMDocument 可以通过 IStream 接口向 ASP 的 Response 直接写入XSL 转换结果。
ADODB.Command 可以通过 IStream 接口向 ASP 的 Response 直接写入来自 SQL Server 的 XML。
我的 FileStream COM 成功支持了do
MDocument,如果不出意外,也应该支持 ADODB.Command。
相同的 IStream 接口嘛。
不过最后的结果还有待于星期一到单位的 SQL Server 2000 上去试验。
 
谢谢孙老师:
我开发的项目基本完成,好好的睡了一个大礼拜。没想到害你忙了半天!
最初我是希望在PB下用这方法的,PB的功能限制很多,只能利用现成的COM。
客户要求使用文件备份数据库,在PB中最简单的方法是全部Retrieve到DataWindow中
然后SaveAs到文本或Execel文件,这同样受较大的表的限制。以SQLDA的形式导出到
文本文件又受text型字段中控制符的限制。利用Ole调用Excel以能实现,但是效率非常
低。所以我首先尝试的是ASP常用的文件系统流的形式,因没有成功我就放弃了。
我一直以为 ASP 的 Response 与 FileSystemObject 是一个东西,谢谢您的提示。
看来要自定义这个COM了。PB下我暂时用别的方法解决了,估计以后也不会用PB做
什么东西了,还是Delphi用起了自由一些。
这恢复回数据库时的用法,贴出来,希望有用:
procedure TForm1.Button1Click(Sender: TObject);
var
ObjBL, ObjConn: OleVariant;
begin
ObjBL := CoSQLXMLBulkLoad.Create;
ObjConn := CoConnection.Create;
ObjConn.Provider := 'SQLOLEDB';
ObjConn.Open('server=(local);
database=AccountantMIS;
uid=sa;');
// ObjConn.Execute('DELETE FROM letters');
ObjBL.ConnectionString := 'provider=SQLOLEDB.1;data source=oops;database=DFW;uid=sa;pwd=';
ObjBL.ErrorLogFile := 'Error.xml';
objBL.Execute('letters.xsd', 'letters.xml');
ObjConn := NULL;
ObjBL := NULL;
end;
 
今天在 SQL Server 2000 上试验了我的 COM,结果非常成功!
倒出 90多万条数据,最终 XML 文件 512M,用了不到 3 分钟,很爽。
根本没有“将耗尽系统内存资源而出错”的问题。下面是试验用的源程序:
(试验的机器上没有 Delphi,所以用了 VB)
Private Sub Command1_Click()
Dim adoConn As New ADODB.Connection
Dim adoCmd As New ADODB.Command
Dim FileStream As New StreamTool.FileStream ' My COM
sConn = "PROVIDER=SQLOLEDB;server=localhost;database=xxx;uid=sa;"
sQuery = "<UserList><sql:query xmlns:sql='urn:schemas-microsoft-com:xml-sql'>Select * from tblUser [User] FOR XML AUTO, ELEMENTS</sql:query></UserList>"
adoConn.ConnectionString = sConn
adoConn.Open
Set adoCmd.ActiveConnection = adoConn
adoCmd.CommandText = sQuery
adoCmd.Dialect = "{5d531cb2-e6ed-11d2-b252-00c04f681b71}"
FileStream.CreateFile ("c:/tt.xml")
adoCmd.Properties("Output Stream") = FileStream
adoCmd.Execute , , &amp;H400
Set FileStream = Nothing
Set adoCmd = Nothing
Set adoConn = Nothing
End Sub
OopsWare, 可以把您的 'letters.xsd' 贴出来吗?
 
letters.xsd
---------------------------------------------
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:sql="urn:schemas-microsoft-com:mapping-schema">
<xsd:element name="letters" sql:relation="letters" >
<xsd:complexType>
<xsd:sequence>
<xsd:element name="ID" sql:field="ID" type="xsd:string"/>
<xsd:element name="RegDateTime" sql:field="RegDateTime" type="xsd:string"/>
<xsd:element name="DateTime" sql:field="DateTime" type="xsd:string"/>
<xsd:element name="UserFrom" sql:field="UserFrom" type="xsd:string"/>
<xsd:element name="Subject" sql:field="Subject" type="xsd:string"/>
<xsd:element name="RoomID" sql:field="RoomID" type="xsd:string"/>
<xsd:element name="Content" sql:field="Content" type="xsd:string"/>
<xsd:element name="Expert" sql:field="Expert" type="xsd:string"/>
<xsd:element name="Points" sql:field="Points" type="xsd:string"/>
<xsd:element name="Parent" sql:field="Parent" type="xsd:string"/>
<xsd:element name="Status" sql:field="Status" type="xsd:string"/>
<xsd:element name="NeedEMail" sql:field="NeedEMail" type="xsd:string"/>
<xsd:element name="IPAddr" sql:field="IPAddr" type="xsd:string"/>
<xsd:element name="GetTimes" sql:field="GetTimes" type="xsd:string"/>
<xsd:element name="ReplyTimes" sql:field="ReplyTimes" type="xsd:string"/>
<xsd:element name="uvalue" sql:field="uvalue" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
---------------------------------------
这个文件在使用 Bulk load 恢复时使用, Bulk load 包含在 SQLXML 3.0 中,模块到
http://msdn.microsoft.com/downloads/default.asp?URL=/downloads/sample.asp?url=/msdn-files/027/001/824/msdncompositedoc.xml
下载。查询使用 SELECT * FROM letters FOR XML AUTO, ELEMENTS 时日期字段被转
换成了"2002-06-18T09:25:57.747" 这在Bulk load时不被识别,所以查询导回过程
中在前面的一段代码中我将所有日期字段传成了用空格分隔日期时间的形式
"CONVERT(CHAR(19), FieldName, 21)"
这样所有字段完全可以当 string 类型存储到 xml 中,SQLSvr会自动将<>等控制符
转换成html控制符,由于当时时间限制,我没有更多的去研究 更多的字段类型,
BulkLoad还允许使用IDENTITY的自增字段,和以BASE64算法将BLOB字段转换的二进制
大数据块。
 
非常感谢 oopsware 的回贴。SQLXML 3.0 我倒是再用。但是我还真不知道那个 T 如何解决。
谢谢提示。
 
后退
顶部