Сегодня публикуется статья spoon'a о репликации данных между двумя
базами. Когда-то мы работали вместе и он довел до ума мои
теоретические изыскания в этом направлении :)
Почему именно XML, а не DBF или TXT? Для DBF ели реплицировать нужно
несколько таблиц, то и кол-во выходных файлов будет равно кол-ву
выгружаемых таблиц и вместо одного файла придется работать с
несколькими. Формат TXT удобен тем, что в одном файле можно поместить
разнородные данные, а если добавить сюда еще и метаинформацию, то
получим как раз XML :) Следует обратить внимание, что если объем
реплицируемых данных составляет несколько десятков МБ, то нужно тогда
смотреть в сторону TXT (или применять другие методики) либо наращивать
память. Также в Делфи уже сужествуют классы по работе с XML, а в
случае TXT нам пришлось бы вю низкоуровневую работу по
парсингу/сохранению делать самому.
Экспорт и импорт данных с помощью XML.
Идея и базовая реализация Алексея Перова aka Gelios, отладка и проверка в "реальных условиях" моя:) Опять же под чутким руководством:)
В этих самых "реальных условиях" дело обстояло так:
- база данных Yaffil, состоящая из одной главной, восьми подчиненных таблиц; - компоненты прямого доступа к ней FIB Plus; - и много чего ещё, но не в этой статье:)
Начнем с выгрузки данных в формат XML. Для начала будет достаточно компонента pFIBDatabase и
pFIBDataSet потом понадобятся pFIBQuery и pFIBTransaction. Также с для работы с докуменатми XML необходимо подключить модули xmldoc и xmlintf. В процедуру выгрузки передается заголовок документа и имя таблицы.
procedure ExportTables(Table: IXMLNode; TableName: string); var Rows, Fields, Tabl : IXMLNode; i : integer;
IXMLNode - это узел документа XML иерархической структуры. Эта иерархия проявляется в следуещем:
заголовок докумената (Table) - это корень документа, самый "главный" узел; в него вложен узел, имя таблицы (Tabl); в него в свою очередь включены узлы Fields (название полей таблицы) и Rows - записи таблицы.
begin DataSet1.SelectSQL.Text := Format('select * from %s', [TableName]); DataSet1.Open;
Выбираем данные из таблицы, имя которой передано в процедуру.
try
if DataSet1.IsEmpty then Exit; Tabl := Table.AddChild('table'); Fields := Tabl.AddChild('fields');
В корень документа "вкладываем" узел Tabl, а в него в свою очередь узел Fields.
for i := 0 to DataSet1.FieldCount - 1 do Fields.AddChild('field').Attributes['f' + IntToStr(i)] := DataSet1.FieldDefs[i].Name;
Заполняем узел Fields именами полей таблицы.
Tabl.Attributes['name'] := TableName;
Указываем имя таблицы.
Rows := Tabl.AddChild('rows');
Создаем узел, обозначающий записи таблицы.
repeat with
Rows.AddChild('row') do begin for i := 0 to DataSet1.FieldCount - 1 do ChildValues['f' + IntToStr(i)] := DataSet1.Fields[i].Value; end;
DataSet1.Next; until DataSet1.Eof;
Заполняем узел Rows значениями записей таблицы.
finally DataSet1.Close; end; end;
Теперь
о том как эту процедуру использовать. Грубо говоря нам нужно создать XML документ,
заполнить его данными с помощью процедуры экспорта и сохранить.
procedure TForm1.btExportClick(Sender: TObject); var XML : IXMLDocument; Root : IXMLNode; s, FName : string;
begin XML := NewXMLDocument; Root := XML.AddChild('transport');
Создаём документ
Query1.SQL.Text := 'select e.rdb$relation_name ' + 'from rdb$relations e where
e.rdb$system_flag=0 and ' + '(e.rdb$relation_name<> ''FIB$DATASETS_INFO'' and ' + 'e.rdb$relation_name like ''REP#_%'' escape ''#'')'; Query1.ExecQuery;
Выбираем имена нужных нам таблиц из БД.
while not Query1.Eof do begin ExportTables(Root, Query1.Fields[0].AsString); Query1.Next; end; Query1.Close;
Имя
каждой таблицы передаем в процедуру выгрузки, где и заполняется документ.
Чтобы импортировать данные из XML файла в базу данных, нужно прочитать значения узлов
и заполнить их в базу. Делаем это мы с помощью процедуры импорта, в которую передаётся корень документа и имя таблицы.
procedure ImportTables(Table: IXMLNode; TableName: string); var Rows, Fields : IXMLNodeList;
Row : IXMLNode; i, j : Integer; fs, fp, us : string; qImIn, qImUp : TpFIBQuery; begin Fields := Table.ChildNodes['fields'].ChildNodes; Rows
:=
Table.ChildNodes['rows'].ChildNodes; fs := ''; fp := ''; us := '';
Инициализируем переменные.
for i := 0 to Fields.Count - 1 do begin fs
:= fs + ',' + Fields[i].Attributes[Format('f%d', [i])];
Записываем имена полей таблицы через запятую.
fp := fp + ',:' + IntToStr(i);
Записываем параметры через запятую.
us := us + ',' + Fields[i].Attributes[Format('f%d', [i])] +
'=:' + IntToStr(i); end;
В переменную us записываем через запятую "имя поля"=:"параметр".
for i := 0 to Rows.Count - 1 do begin Row := Rows[i]; qImIn := TpFIBQuery.Create(nil); qImIn.Database := Database1;
qImIn.Transaction := Transaction1; qImIn.SQL.Text := Format('insert into %s(%s) values(%s)', [Table.Attributes['name'], fs, fp]);
Создаем запрос на вставку данных.
qImUp := TpFIBQuery.Create(nil); qImUp.Database := Database1; qImUp.Transaction := Transaction1; qImUp.SQL.Text := Format('update %s set %s where id=:0', [
Table.Attributes['name'], us]);
Создаем запрос на апдейт данных.
for j := 0 to Fields.Count - 1 do begin qImIn.Params[j].AsVariant := Row.ChildValues
[Format('f%d', [j])]; qImUp.Params[j].AsVariant := Row.ChildValues[Format('f%d', [j])]; end;
Подставляем значения в параметры запроса.
try
qImIn.ExecQuery; Transaction1.Commit; except on Exception do begin Transaction1.Rollback; try qImUp.ExecQuery
; Transaction1.Commit; except Transaction1.Rollback; end; end; end; qImIn.Free;
qImUp.Free; end; Application.ProcessMessages; end;
Выполняем запрос на инсерт, при исключениях - запрос на апдейт.
Ну и пользуемся этой процедурой следующим образом:
procedure Tmain_fr.main_ImportClick(Sender: TObject); var Root, Table : IXMLNode; s : string; begin try if OpenDialog1.Execute
then begin
Root := LoadXMLDocument(OpenDialog1.FileName).ChildNodes['transport'];
Открываем документ.
Table := GetTables(Root, 'dict_klients'); if Assigned(Table) then ImportTables(Table, 'dict_klients');
Table := GetTables(Root, 'dict_st_klients'); if Assigned(Table) then ImportTables(Table, 'dict_st_klients'); Table := GetTables(Root, 'dict_tupics'); if Assigned(Table) then ImportTables(Table, 'dict_tupics');
Table := GetTables(Root, 'dict_stations'); if Assigned(Table) then ImportTables(Table, 'dict_stations'); Table := GetTables(Root, 'dict_products'); if Assigned(Table) then ImportTables(Table, 'dict_products');
Table := GetTables(Root, 'dict_kach'); if Assigned(Table) then ImportTables(Table, 'dict_kach'); Table := GetTables(Root, 'dict_platel'); if Assigned(Table) then ImportTables(Table, 'dict_platel');
Table := GetTables(Root, 'reports'); if Assigned(Table) then ImportTables(Table, 'reports'); Table := GetTables(Root, 'vagons'); if Assigned(Table) then ImportTables(Table, 'vagons');
end; except end; end;
С помощью функции GetTables, описанной ниже, находим в документе имя таблицы и передаём его в процедуру импорта.
function GetTables(Root: IXMLNode; Name: string): IXMLNode;
var Tables : IXMLNodeList; i : Integer; begin Tables := Root.ChildNodes; Result := nil; for i
:= 0 to Tables.Count - 1 do if AnsiUpperCase(
Tables.Nodes[i].Attributes['name']) = AnsiUpperCase(Name) then Result := Tables.Nodes[i]; end;