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

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

Интеграция Open Source-решений  

Open Source в облачной среде

Облачные решения становятся всё более популярными в мире. Компании стремятся использовать их для

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

Автоматизация  

Нейросеть вам в руки! Как использовать ИИ для автоматизации задач

Использование ИИ для автоматизации задач помогает компании получить конкурентное преимущество, поскольку объединение

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

Рынок труда  

Специалист по этическому ИИ, инженер по квантовым вычислениям или аналитик по метавселенной?

Новые тенденции в развитии ИТ могут привести к возникновению новых специальностей в

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

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

Учитесь убеждать и побеждать

Издательство «БХВ», как всегда, порадовало своих читателей хорошими книжными новинками. Кроме популярных

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

Сетевая инфраструктура  

Как удаленная работа меняет подход к сетевой инфраструктуре?

С увеличением числа сотрудников, работающих из дома, организации сталкиваются с необходимостью создания

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

Мониторинг  

Какой мониторинг нужен сегодня?

По мнению экспертов ГК InfoWatch, действия сотрудников – самая распространенная причина инцидентов

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

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

Руководство для тех, кто увлечен ИИ, программированием. И дизайном

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

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

Мобильные приложения  

Искусственный интеллект в мобильных приложениях: возможности и перспективы

Обзор современных применений ИИ в мобильных приложениях, анализ перспектив развития этой технологии,

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

ИТ-образование  

Как сделать ИТ-образование эффективным?

Эксперты ИТ-отрасли отвечают на вопросы «СА». Обсуждаем ключевые аспекты для улучшения образовательных

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

Work-life balance  

Как айтишнику найти баланс между работой и личной жизнью?

Обсуждаем инструменты для эффективного управления временем, снижения уровня стресса и достижения гармонии. На

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

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

Всё самое нужное – под одной обложкой

Отличительная черта книжных новинок, выпущенных недавно издательством «БХВ» – это их универсальность. Не просто

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

ИТ-инфраструктура  

Системы мониторинга ИТ-инфраструктуры-2025

Без мониторинга ИТ-инфраструктуры не обходится ни одна компания, хотя бы потому, что

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

Открытое ПО  

Безопасность Open Source: рискуем или контролируем?

Компания «Кросс технолоджис» изучила, как используется ПО с открытым кодом в компаниях

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

Работа с нейросетью  

Скажи, есть ли у тебя AI, и я скажу, кто ты

Недавно сервис по поиску работы SuperJob выяснил, что каждый второй россиянин уже

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Друзья сайта  

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

Архив номеров / 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