Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Репликация данных


Доброго времени суток, уважаемые подписчики!

Сегодня публикуется статья 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;

Имя каждой таблицы передаем в процедуру выгрузки, где и заполняется документ.

          FName := AnsiReplaceStr(DateTimeToStr(Now), ':', '.');
          XML.SaveToFile(ExtractFilePath(Application.ExeName) + FName + '.xml');
        end;

Сохраняем документ.

Чтобы импортировать данные из 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 записываем через запятую "имя поля"=:"параметр".

          Delete(fs, 1, 1);
          Delete(fp, 1, 1);
          Delete(us, 1, 1);

Удаляем запятую впереди.

          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;

Ну вот в принципе и всё:)

spoon.

Ждем Ваших откликов на емайл 5781-author@subscribe.ru или subscr@chertenok.ru


Приглашаем авторов в рассылку!


С уважением,
координатор рассылки Алексей aka Gelios.

Наши координаты:

сайт - www.delphi.chertenok.ru
форум - www.forum.chertenok.ru
контактный email - 5781-author@subscribe.ru

Другие проекты:

www.travel.chertenok.ru - сайт о путешествиях!



В избранное