Реинкарнация данных::Журнал СА4.2005
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Подписка
Архив номеров
Где купить
Наука и технологии
Авторам
Рекламодателям
Контакты
   

  Опросы
  Статьи

Событие  

В банке рассола ждет сисадмина с полей фрактал-кукумбер

Читайте впечатления о слете ДСА 2024, рассказанные волонтером и участником слета

 Читать далее...

Организация бесперебойной работы  

Бесперебойная работа ИТ-инфраструктуры в режиме 24/7 Как обеспечить ее в нынешних условиях?

Год назад ИТ-компания «Крок» провела исследование «Ключевые тренды сервисного рынка 2023». Результаты

 Читать далее...

Книжная полка  

Читайте и познавайте мир технологий!

Издательство «БХВ» продолжает радовать выпуском интересных и полезных, к тому же прекрасно

 Читать далее...

СУБД PostgreSQL  

СУБД Postgres Pro

Сертификация по новым требованиям ФСТЭК и роль администратора без доступа к данным

 Читать далее...

Критическая инфраструктура  

КИИ для оператора связи. Готовы ли компании к повышению уровня кибербезопасности?

Похоже, что провайдеры и операторы связи начали забывать о требованиях законодательства

 Читать далее...

Архитектура ПО  

Архитектурные метрики. Качество архитектуры и способность системы к эволюционированию

Обычно соответствие программного продукта требованиям мы проверяем через скоуп вполне себе понятных

 Читать далее...

Как хорошо вы это знаете  

Что вам известно о разработках компании ARinteg?

Компания ARinteg (ООО «АРинтег») – системный интегратор на российском рынке ИБ –

 Читать далее...

Графические редакторы  

Рисование абстрактных гор в стиле Paper Cut

Векторный графический редактор Inkscape – яркий представитель той прослойки open source, с

 Читать далее...

День сисадмина  

Учите матчасть! Или как стать системным администратором

Лето – время не только отпусков, но и хорошая возможность определиться с профессией

 Читать далее...

День сисадмина  

Живой айтишник – это всегда движение. Остановка смерти подобна

Наши авторы рассказывают о своем опыте и дают советы начинающим системным администраторам.

 Читать далее...

Виртуализация  

Рынок решений для виртуализации

По данным «Обзора российского рынка инфраструктурного ПО и перспектив его развития», сделанного

 Читать далее...

Книжная полка  

Как стать креативным и востребованным

Издательский дом «Питер» предлагает новинки компьютерной литературы, а также книги по бизнесу

 Читать далее...

Книжная полка  

От создания сайтов до разработки и реализации API

В издательстве «БХВ» недавно вышли книги, которые будут интересны системным администраторам, создателям

 Читать далее...

Разбор полетов  

Ошибок опыт трудный

Как часто мы легко повторяем, что не надо бояться совершать ошибки, мол,

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 6126
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 6837
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

12.03.2018г.
Просмотров: 4120
Комментарии: 0
Глубокое обучение с точки зрения практика

 Читать далее...

12.03.2018г.
Просмотров: 2971
Комментарии: 0
Изучаем pandas

 Читать далее...

12.03.2018г.
Просмотров: 3776
Комментарии: 0
Программирование на языке Rust (Цветное издание)

 Читать далее...

19.12.2017г.
Просмотров: 3784
Комментарии: 0
Глубокое обучение

 Читать далее...

19.12.2017г.
Просмотров: 6273
Комментарии: 0
Анализ социальных медиа на Python

 Читать далее...

19.12.2017г.
Просмотров: 3129
Комментарии: 0
Основы блокчейна

 Читать далее...

19.12.2017г.
Просмотров: 3427
Комментарии: 0
Java 9. Полный обзор нововведений

 Читать далее...

16.02.2017г.
Просмотров: 7242
Комментарии: 0
Опоздавших не бывает, или книга о стеке

 Читать далее...

17.05.2016г.
Просмотров: 10612
Комментарии: 0
Теория вычислений для программистов

 Читать далее...

30.03.2015г.
Просмотров: 12331
Комментарии: 0
От математики к обобщенному программированию

 Читать далее...

18.02.2014г.
Просмотров: 13964
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

 Читать далее...

13.02.2014г.
Просмотров: 9094
Комментарии: 0
Читайте, размышляйте, действуйте

 Читать далее...

12.02.2014г.
Просмотров: 7049
Комментарии: 0
Рисуем наши мысли

 Читать далее...

10.02.2014г.
Просмотров: 5356
Комментарии: 3
Страна в цифрах

 Читать далее...

18.12.2013г.
Просмотров: 4587
Комментарии: 0
Большие данные меняют нашу жизнь

 Читать далее...

18.12.2013г.
Просмотров: 3397
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

 Читать далее...

04.12.2013г.
Просмотров: 3123
Комментарии: 0
Паутина в облаках

 Читать далее...

03.12.2013г.
Просмотров: 3376
Комментарии: 0
Рецензия на книгу «MongoDB в действии»

 Читать далее...

02.12.2013г.
Просмотров: 2996
Комментарии: 0
Не думай о минутах свысока

 Читать далее...

Друзья сайта  

 Реинкарнация данных

Архив номеров / 2005 / Выпуск №4 (29) / Реинкарнация данных

Рубрика: Администрирование /  Продукты и решения

СЕРГЕЙ СУПРУНОВ

Реинкарнация данных

Трудно представить себе предприятие или организацию, где не использовались бы базы данных для хранения информации. Это и списки инвентаризации основных средств, и базы абонентов – пользователей услуг предприятия, и т. п. На российских просторах все еще огромной популярностью пользуется формат DBF, в частности, именно в нем хранит свои таблицы старый добрый FoxPro. Тем не менее жизнь не стоит на месте, рано или поздно возникает вопрос о переносе всех накопленных данных на более функциональные СУБД. В данной статье рассматривается несколько способов осуществления такой миграции из FoxPro в БД PostgreSQL. Способы различаются как сложностью реализации (хотя это довольно субъективный критерий), так и степенью автоматизации. Естественно, перечень возможных реализаций никоим образом не ограничивается приведенным здесь.

Итак, задача – перенести информацию, хранящуюся в формате DBF (СУБД FoxPro 2.6), в базу данных PostgreSQL (используемая версия сервера – 8.0.1, операционная система – FreeBSD 5.3). Прежде чем приступать к решениям, рассмотрим, что представляет собой DBF-файл.

Формат DBF

Формат DBF достаточно прост: 32 байта заголовка файла, «оглавление» таблицы, данные. В таблице представлен формат заголовка с кратким описанием каждого поля:

Таблица 1. Структура заголовка DBF-файла

Смещение

Длина поля

Описание поля

0

1

Тип файла (для FoxPro – значение 0x03, если не используются memo-поля, 0xF5 – если используются)

1

3

Дата последнего обновления в формате YYMMDD

4

4

Число записей (строк данных)

8

2

Смещение первой записи от начала файла

10

2

Суммарная длина записи (все поля + флаг deleted)

12

15

Не используется

28

1

Флаг связанных файлов (0x01 – имеется индексный файл (.cdx), 0x02 – имеется файл с memo-данными (.fpt), 0x03 – есть и .cdx, и .fpt)

29

1

Кодировка данных (0x65 – DOS866, 0xC9 – Win1251)

30

2

Не используется

Далее следует оглавление таблицы – каждые 32 байта описывают одно поле данных (имя поля, тип данных, длину поля и т. д.).

Таблица 2. Структура «оглавления» таблицы

Первый байт

Длина поля

Описание поля

0

11

Имя поля (максимум – 10 символов)

11

1

Тип поля (C – символы, N – цифры, D - дата, Mmemo-поле etc.)

12

4

Смещение поля в байтах от начала записи

16

1

Длина поля

17

1

Число знаков в дробной части числа

18

1

Тип столбца (0x00 - обычный, 0x01 – системный etc.)

19

13

Не используется

 «Оглавление» завершается символом 0x0D, после которого следуют записи данных. Самый первый байт каждой записи – признак удаления. Если запись удаляется командой delete, она физически остается в файле, а в поле признака удаления заносится символ 0x2A («*»). Такая запись считается помеченной на удаление и по умолчанию в операциях не участвует. Физическое удаление осуществляется только в результате так называемой упаковки (pack) таблицы данных. Таким образом, нужно помнить, что длина каждой записи будет определяться суммой длин полей плюс один байт.

Импорт внешних файлов в PostgreSQL

СУБД PostgreSQL позволяет осуществлять загрузку данных из внешних файлов и экспорт во внешние файлы с помощью команды COPY.

Ее синтаксис следующий:

serg=> h copy

Команда:   COPY

Описание:  копировать данные между файлом и таблицей

Синтаксис:

COPY tablename [ ( column [, ...] ) ]

    {FROM | TO} { "filename" | STDIN }

    [ [ WITH ]

          [ BINARY ]

          [ OIDS ]

          [ DELIMITER [ AS ] "delimiter" ]

          [ NULL [ AS ] "null string" ]

          [ CSV [ QUOTE [ AS ] "quote" ]

                [ ESCAPE [ AS ] "escape" ]

                [ FORCE NOT NULL column [, ...] ]

Для загрузки данных из внешних файлов используется команда COPY FROM. Таблица tablename, в которую будет осуществляться загрузка, должна существовать и иметь поля, тип которых соответствует формату полей загружаемого текстового файла. Обратите внимание, что исходная таблица и таблица PostgreSQL не обязательно должны иметь одинаковую структуру – в процессе подготовки данных, как будет показано ниже, и количество и типы экспортируемых полей могут меняться. Например, в процессе экспорта исходное поле типа Date может быть записано в файл в виде «2005-04-04», что позволит в дальнейшем загрузить его также в поле даты. А может принять вид «4 апреля 2005 года», и его уже можно будет импортировать только в текстовое поле.

Опции BINARY и OIDS в данном случае нас не интересуют – они могут быть полезны, когда и импорт, и экспорт выполняется в PostgreSQL.

Текстовый формат загружаемого файла достаточно прост – каждая запись должна размещаться в одной строке, поля разделяются с помощью одиночного символа «delimiter». Если символ-разделитель встречается внутри поля данных, он должен экранироваться символом «». Одиночный символ «», встречающийся в данных, должен также экранироваться, чтобы исключить его специальную трактовку.

Начиная с версии PostgreSQL 8.0 поддерживается также загрузка из CSV-файла, который является, по сути, файлом с разделителем (обычно – запятая), с той разницей, что поля данных для исключения неоднозначности при обработке символов могут заключаться в кавычки.

Еще один параметр, NULL AS, задает символьную строку, которая будет рассматриваться как значение NULL. По умолчанию используется последовательность «N».

Нужно заметить, что при работе в терминале psql доступны две команды: COPY и COPY. Первая – это SQL-команда, выполняемая сервером, вторая – команда интерактивного клиента. Разница между ними в правах, с которыми они выполняются. В первом случае возможности доступа определяются правами процесса postmaster, во втором – правами пользователя, запустившего psql.

Обратите внимание, что если в процессе загрузки возникает ошибка (например, в какой-то строке не хватает поля или имеется лишнее, формат данных не соответствует типу поля, в которое осуществляется запись и т. д.), то ни одна из записей в итоговую таблицу не попадает, то есть импорт выполняется как единый транзакционный блок.

Несколько слов о кодировках

Как всегда, если речь идет о переносе данных между различными системами, встает вопрос о преобразовании используемых кодировок. FoxPro обычно хранит данные в cp866 (FoxPro for DOS) или в cp1251 (FoxPro for Windows). В то же время кодировка баз PostgreSQL может быть иной (у меня, например, это koi8-r). Решить эту проблему можно одним из трех способов:

  • Создать базу данных PostgreSQL с кодировкой, соответствующей кодировке DBF-файлов. Это выполняется с помощью ключа ENCODING команды CREATE DATABASE. Однако при этом могут возникнуть дополнительные трудности при обработке данных, если кодировка БД не соответствует установленной локализации операционной системы.
  • Если сервер будет использоваться преимущественно как сервер баз данных, а БД будет хранить в основном данные, импортированные из FoxPro, то имеет смысл сразу «подогнать» кодировку операционной системы и базы данных под кодировку файла DBF.
  • Наиболее универсальный способ – перекодировка данных на стадии импорта.

Первые два способа особых сложностей вызвать не должны, поэтому в дальнейшем сосредоточимся на третьем, и будем осуществлять преобразование данных из cp866 в koi8-r в процессе переноса данных.

Замечание о memo-полях

FoxPro использует поля типа MEMO для хранения больших текстовых данных неопределенного размера во внешнем файле (обычно используется расширение fpt). Работа с этими полями имеет ряд особенностей, но о них поговорим в следующей части статьи, поэтому сейчас будем полагать, что экспортируемые таблицы полей такого типа не содержат.

Путь 1: подготовка данных с помощью FoxPro

СУБД FoxPro предоставляет пользователю достаточно мощный язык программирования, чем мы и воспользуемся. В данном разделе мы напишем несложную программу, которая будет формировать текстовый файл с разделителями, пригодный для импорта в PostgreSQL с помощью команды COPY. В качестве разделителя будем использовать символ табуляции.

Листинг 1. Программа dbf2pg.prg на FoxPro

* В FoxPro комментарии начинаются символом «*»

* Устанавливаем формат даты

SET DATE TO YMD

SET CENTURY ON

* Открываем таблицу для экспорта

USE s_cats ALIAS tab

* Определяем разделитель как символ табуляции

m.delimiter = CHR(9)

* Определяем строки для перекодировки в koi8

m.koiabcb = 'стўчфхЎ·щъыьэюяЁЄєЇїцшу■√¤ ∙°№рё'

m.koiabcs = '┴┬╫╟─┼╓┌╔╩╦╠═╬╧╨╥╙╘╒╞╚├▐█▌▀┘╪▄└╤'

m.koiabc = m.koiabcb + m.koiabcs

m.dosabcb = 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'

m.dosabcs = 'абвгдежзийклмнопрстуфхцчшщъыьэюя'

m.dosabc = m.dosabcb + m.dosabcs

* Указываем кодировку

m.dstcp = 'koi8'

* Создаем результирующий файл и открываем его на запись (дескриптор – в переменной m.txf)

m.txf = FCREATE('table.txt')

* Делаем таблицу активной

SELECT tab

* Цикл по всем записям таблицы

SCAN

    * Формируем строку, удаляя лишние пробелы

    m.temp = ALLTRIM(uniqueid) + m.delimiter + ;

            ALLTRIM(user) + m.delimiter + ;

            ALLTRIM(city) + m.delimiter + ;

            ALLTRIM(address) + m.delimiter + ;

            ALLTRIM(tariff) + m.delimiter + ;

            ALLTRIM(category) + m.delimiter + ;

            IIF(EMPTY(date), '\N', ;

                  STRTRAN(DTOC(date), '/', '-'))

    * Если встречается табуляция – меняем на 4 пробела

    m.temp = STRTRAN(m.temp, CHR(9), SPACE(4))

    IF m.dstcp == 'koi8'

    * Если в koi8, то используем свою функцию

           m.temp = dos2koi(m.temp)

    ELSE

    * иначе – пользуемся штатной функцией перекодировки

           m.temp = CPCONVERT(866, m.dstcp, m.temp)

    ENDIF

    * Экранируем символы '\'

    m.temp = STRTRAN(m.temp, '\', '\\')

    * Запись полученной строки в файл

    =FPUTS(m.txf, m.temp)

ENDSCAN

=FCLOSE(m.txf)

 

USE IN tab

*===============================================

* Функция перекодировки из cp866 в koi8-r

PROCEDURE dos2koi

PARAMETER m.string

m.res = ''

FOR m.i = 1 TO LEN(m.string)

    m.pos = AT(SUBSTR(m.string, m.i, 1), m.dosabc)

    IF m.pos > 0

           m.res = m.res + SUBSTR(m.koiabc, m.pos, 1)

    ELSE

           m.res = m.res + SUBSTR(m.string, m.i, 1)

    ENDIF

ENDFOR

RETURN m.res

Все здесь достаточно просто – сканируем таблицу, для каждой записи формируем строку, содержащую данные, разделенные указанным в начале программы символом. Если в данных встречается символ табуляции – заменяем его четырьмя пробелами (не самое красивое решение, но вполне пригодное). Повозиться приходится с перекодировкой. Если PostgreSQL работает в cp1251, то для преобразования кириллицы можно воспользоваться штатной функцией FoxPro CPCONVERT. Перекодировать в koi8-r она, к сожалению, не умеет, поэтому здесь придется использовать самописную функцию dos2koi. Смысл ее в том, чтобы посимвольно заменить все, что входит в строку m.dosabc символами из m.koiabc, расположенными на тех же местах. Вообще, чтобы не возиться с перекодировкой силами FoxPro (к слову, приведенная выше функция будет работать очень медленно), можно сформировать файл в исходной кодировке, а затем пропустить его через внешний перекодировщик (когда-то мне попадался замечательный плагин к FAR).

После того как текстовый файл сформирован, готовим таблицу в базе PostgreSQL и выполняем команду COPY:

serg=> create table s_cats(uid char(10), catname char(35));

CREATE TABLE

serg=> copy s_cats from "/usr/home/serg/test/psql/TABLE.TXT"

\.

serg=> select * from s_cats;

    uid     |               catname

------------+-------------------------------------

 P0RH0S0SXJ | Без льгот

 P0RH0S0SXN | Без начисления абонплаты

 P0RH0S0SXR | ВОВ инвалиды

К достоинствам рассмотренного способа можно отнести то, что на этапе подготовки файла мы можем выполнить любую предварительную обработку данных – разбить одно поле (например, «Абонент») на несколько («Фамилия», «Имя», «Отчество»), объединить несколько полей в одно, выбрать для экспорта только некоторые поля.

Недостатки – достаточно большой объем ручной работы, поскольку программу dbf2pg.prg каждый раз нужно править под конкретные поля экспортируемой таблицы. Кроме того, обработку всего, что не касается данных в таблицах, FoxPro выполняет сравнительно медленно.

Например, преобразование таблицы абонентов, содержащей 6 символьных полей и около 5000 записей, выполняется порядка 40 секунд.

Путь 2: формирование файла с разделителями с помощью сценария на Python

Если у вас нет под рукой FoxPro или вы с ним не очень дружны, то преобразование можно выполнить с помощью любого другого языка программирования, правда, в этом случае DBF-файл придется разбирать вручную, отыскивая поля данных в соответствии с информацией заголовка и «оглавления». В данном разделе мы воспользуемся языком Python, а в следующем аналогичные действия реализуем на Perl.

Из информации заголовка (первые 32 байта) интерес для нас представляют только количество записей в таблице и смещение первой записи, чтобы не вычислять эти значения лишний раз.

Сценарий будет выглядеть следующим образом:

Листинг 2. Сценарий dbf2pg.py – ручной разбор файла DBF

#!/usr/local/bin/python

# Класс для хранения информации о полях таблицы

class field:

    def __init__(self):

           self.name = ''

           self.type = 'C'

           self.len = 0

           self.dec = 0

           self.pos = 0

# Функция формирования файла с разделителями

def dbf2pg(fname):

    # Открываем dbf-Файл в бинарном режиме

    dbf = open(fname, 'rb')

    # Считываем заголовок

    head = dbf.read(32)

    # Выбираем из заголовка число записей

    dblen = ord(head[7]) * 16777216 + ord(head[6]) * 65536 + ord(head[5]) * 256 + ord(head[4])

    # и позицию первой записи

    posfirst = ord(head[9]) * 256 + ord(head[8])

    wexit = 0

    num = 0

    totlen = 1

    fields = []

    while not wexit:

    # Читаем информацию о полях таблицы

           line = dbf.read(1)

           if ord(line) == 0x0D:

           # Если первый символ – 0x0D, то выход –  «оглавление» закончено

                 wexit = 1

           else:

           # Иначе дочитываем до 32-х символов

                 line = line + dbf.read(31)

                 # Новый объект класса field

                 fld = field()

                 # Имя поля – сначала до первого символа 0x00

                 fld.name = line[:line.find('\0')]

                 # Тип поля – 11-й байт

                 fld.type = line[11]

                 # Смещение поля данных от начала записи

                 fld.pos = ord(line[15]) * 16777216 + ord(line[14]) * 65536 + ord(line[13]) * 256 + ord(line[12])

                 # Длина поля

                 fld.len = ord(line[16])

                 # Длина дробной части

                 fld.dec = ord(line[17])

                 # Заносим информацию в массив

                 fields.append(fld)

                 num = num + 1

                 totlen = totlen + fld.len

    if withcommand:

    # Если переменная истинна, то формируем команду на создание таблицы

           cmd = 'CREATE TABLE %s (' % tablename

           for i in range(num):

                 fld = fields[i]

                 cmd = cmd + fld.name + ' '

                 if fld.type == 'C':

                        cmd = cmd + 'char(' + str(fld.len) + '), '

                 elif fld.type == 'D':

                        cmd = cmd + 'date, '

                 elif fld.type == 'N' :

                        cmd = cmd + 'numeric(' + str(fld.len) + ',' + str(fld.dec) + '), '

           # Последнюю запятую и пробел меняем на закрывающую скобку и точку с запятой

           cmd = cmd[:-2] + ');'

           print cmd

           # Формируем команду чтения данных из потока ввода

           cmd = 'COPY users FROM stdin;'

           print cmd

    # Формируем сам поток ввода

    dbf.seek(posfirst)

    for rec in range(dblen):

           if dbf.read(1) != '*':

           # Если запись не помечена на удаление, то обработка:

                 line = ''

                 for i in range(num):

                 # Цикл по всем полям

                        fld = dbf.read(fields[i].len)

                        # Если дата – преобразуем в формат YYYY-MM-DD из формата YYYYMMDD

                        if fields[i].type == 'D':

                               fld = fld[:4] + '-' + fld[4:6] + '-' + fld[6:]

                        # Если дата пустая, меняем ее на значение NULL

                        if fld == '    -  -  ':

                               fld = '\N'

                        # Для пустых числовых полей ставим значение 0

                        if fields[i].type == 'N' ї

                               and fld[-1] == ' ':

                               fld = '0'

                        # Экранируем символ ‘\’, если таковой встречается в конце поля, т.к. он экранирует разделитель

                        if fld[-1] == '\\':

                               fld = fld + '\\'

                        # Для строковых полей выполняем перекодировку

                        if fields[i].type == 'C':

                               fld = unicode(fld, 'cp866').encode('koi8-r')

                        # Дописываем к строке записи через разделитель

                        line = line + fld + delimiter

                 # Выводим полученную строку, отрезав последний разделитель

                 print line[:-1]

           else:

           # Если запись помечена на удаление, то просто дочитываем ее остаток, ничего не обрабатывая

                 dbf.read(totlen - 1)

    dbf.close

    # Если формируются команды для \i, то заканчиваем данные строкой ‘\.’

    if withcommand:

           print '\\.'

# Устанавливаем настроечные переменные

delimiter = "\t"

withcommand = 1

tablename = 'users'

# вызываем функцию преобразования

dbf2pg('users.dbf')

Вывод в данном случае направляется в стандартный поток stdout, поэтому для формирования файла следует использовать перенаправление:

$ ./dbd2pg.py > USERS.TXT

При переменной withcommand, установленной в 0, мы получаем такой же файл с разделителями, как и ранее с помощью FoxPro. Соответственно импортироваться в PostgreSQL информация будет аналогично.

Однако обратите внимание, что теперь мы имеем всю информацию о полях данных, включая тип поля и его название. Так почему бы не воспользоваться этим для автоматического формирования таблицы PostgreSQL нужной структуры? Именно эта идея и реализуется, если переменной withcommand присвоить значение 1. При этом загрузка данных будет осуществляться по тому же принципу, какой используется при восстановлении из текстовой резервной копии: при выполнении команды «i файл» в терминале psql содержимое указанного файла будет рассматриваться как ввод с клавиатуры в интерактивном режиме.

Таким образом, мы формируем команду CREATE TABLE в соответствии с информацией о названиях и типах полей таблицы, а затем команду COPY .. FROM stdin, которая будет считывать последующие данные, пока не обнаружит строку «.». Здесь следует обратить внимание на то, что имена полей должны быть допустимы в PostgreSQL. Так, например, имя USER, вполне нормальное для FoxPro, PostgreSQL рассматривает как служебное слово, не позволяя создавать таблицу с таким столбцом. Впрочем, это ограничение можно обойти, если имена полей заключить в кавычки – в таком случае любое имя будет восприниматься «как есть», правда, и в дальнейшей работе нужно будет использовать кавычки и строго соблюдать регистр символов.

Еще одно замечание – теперь, поскольку таблицу мы создаем автоматически, на момент импорта она не должна существовать.

Рассмотренный способ хотя и требует несколько больших усилий на разработку сценария, но заметно упрощает перенос данных, автоматизируя создание нужных таблиц. При этом работа идет заметно быстрее – на обработку той же таблицы абонентов тратится менее секунды.

Путь 3: непосредственная запись в базу данных

Рассмотренные выше способы так или иначе привязаны к команде COPY. А что, если мы не можем записать текстовый файл на диск в том месте, где он будет доступен процессу postmaster? Или нет доступа к клиенту psql? В этом случае можно непосредственно формировать команды INSERT серверу БД вместо создания промежуточных файлов. Конечно, это будет работать заметно медленнее, но в некоторых ситуациях такой путь может оказаться единственно возможным. Рассмотрим этот способ на примере.

Для разнообразия воспользуемся языком Perl. Алгоритм разбора DBF-файла остался прежним, даже синтаксис похож. Только на этот раз нужно будет для каждой записи формировать команду INSERT и отсылать ее на сервер СУБД:

Листинг 3. Сценарий dbf2pg.pl

#!/usr/bin/perl -w

# Подключаем модули для перекодировки и работы с СУБД

use Lingua::RU::Charset qw(alt2koi);

use DBI;

# Устанавливаем соединение с БД

$dbh = DBI->connect('dbi:Pg:dbname=serg', 'serg', '');

# Исходный файл и имя итоговой таблицы

$fname = 'S_CATS.DBF';

$tabname = 's_cats_pl';

# Открываем файл DBF в бинарном режиме

open(DBF, "$fname");

binmode(DBF);

read(DBF, $head, 32);

$dblen = ord(substr($head, 7, 1)) * 16777216 + ord(substr($head, 6, 1)) * 65536 + ord(substr($head, 5, 1)) * 256 + \

    ord(substr($head, 4, 1));

$posfirst = ord(substr($head, 9, 1)) * 256 + ord(substr($head, 8, 1));

$wexit = 0; $num = 0; $totlen = 1;

while(! $wexit) {

    read(DBF, $first, 1);

    if(ord($first) == 13) {

           $wexit = 1;

    } else {

    # Далее читаем файл сразу в переменные

           read(DBF, $name, 10);

           $name = $first . $name;

    # Вырезаем из имени символы 0x00 (здесь ^@ - не два символа, просто 0x00 так выглядит в vi)

           $name =~ s/^@//g;

           read(DBF, $type, 1);

           read(DBF, $tmp, 4);

           read(DBF, $tmp, 1);

           $len = ord($tmp);

           read(DBF, $tmp, 1);

           $dec = ord($tmp);

           read(DBF, $tmp, 14);

           $fields[$num]{name} = $name;

           $fields[$num]{type} = $type;

           $fields[$num]{len} = $len;

           $fields[$num]{dec} = $dec;

           $num++; $totlen += $len;

    }

}

# Формируем команду создания таблицы

$sqlcommand = "CREATE TABLE $tabname (";

for($i = 0; $i < $num; $i++) {

    $sqlcommand .= $fields[$i]{name} . ' ';

    if($fields[$i]{type} eq 'C') {

           $tmp = 'char(' . $fields[$i]{len};

    } elsif($fields[$i]{type} eq 'D') {

           $tmp = 'date';

    } elsif($fields[$i]{type} eq 'N') {

           $tmp = 'numeric(' . $fields[$i]{len} .

                 ',' . $fields[$i]{dec};

    }

    $sqlcommand .= $tmp . '), ';

}

$sqlcommand = substr($sqlcommand, 0, length($sqlcommand) - 2);

$sqlcommand .= ');';

# Отправляем ее на сервер

$sth = $dbh->prepare($sqlcommand);

$sth->execute;

# Переход на начало данных

seek(DBF, $posfirst, 0);

for($rec = 0; $rec < $dblen; $rec++) {

    read(DBF, $first, 1);

    if($first ne '*') {

           $sqlcommand = "INSERT INTO $tabname VALUES (";

           for($i = 0; $i < $num; $i++) {

                 read(DBF, $fld, $fields[$i]{len});

                 if($fields[$i]{type} eq 'C') {

                        ($fld) = &alt2koi($fld);

                        $fld = "'" . $fld . "'";

                 } else {

                        $fld =~ s/\s//g;

                 }

                 if($fields[$i]{type} eq 'D') {

                        if($fld ne '') {

                               $fld = '"' .

                                      substr($fld, 0, 4) . '-' .

                                      substr($fld, 4, 2) . '-' .

                                      substr($fld, 6, 2) . '"';

                        } else {

                               $fld = 'NULL';

                        }

                 }

                 if(($fields[$i]{type} eq 'N') && ($fld eq '')) {

                        $fld = '0';

                 }

                 $sqlcommand .= $fld . ', ';            

           }

           $sqlcommand = substr($sqlcommand, 0,

                 length($sqlcommand) - 2);

           $sqlcommand .= ');';

           # Отправляем команду INSERT

           $sth = $dbh->prepare($sqlcommand);

           $sth->execute;

    } else {

           read(DBF, $tmp, 31);

    }

}

close(DBF);

$dbh->disconnect;

Рассмотренный способ универсален как в плане доступа к базе данных (достаточно иметь возможность отправлять на сервер SQL-команды), так и в плане использования конкретной СУБД. При минимальных синтаксических изменениях данный сценарий можно использовать для работы практически с любой системой управления базами данных – MySQL, Oralce и т. д. К тому же в данном случае каждая запись переносится отдельно от других, и если при этом возникнет ошибка, на импорт других записей это никак не повлияет. Правда, если предъявляются высокие требования к целостности данных, то подход «все или ничего» более предпочтителен. За указанные удобства приходится расплачиваться быстродействием – та же тестовая таблица из 5000 записей переносится почти одну минуту.

Завершение

Итак, мы рассмотрели несколько способов переноса данных из формата DBF в таблицу PostgreSQL. Каждый из них имеет свои особенности, и я надеюсь, вам удастся найти тот, который лучше всего подойдет именно к вашим условиям.


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-45
E-mail: sa@samag.ru