Импорт данных из 1С (dbf) в MySQL

Apr 27, 2013 17:11 · 603 words · 3 minute read tutorial

Давным давно писал статью на СвободноХабр про импорт данных из 1С файлов dbf в MySQL, решил перепостить её сюда, т.к., увы, ухожу с того ресурса.

Предыстория

Работали себе люди долго-долго в самописной конфигурации 1С и бед не знали, как открылись у организации филиалы, и директор захотел все данные в одном месте. Сказано — сделано: через пару месяцев родился корпоративный сайт (написанный на php (Yii) + MySQL). Пользователи ринулись добавлять новую информацию, печатать отчёты… в общем, работа закипела. А что ж делать со старыми данными? Правильно, импортировать в новую систему дабы не потерялись. Итак, возникла задача: произвести миграцию из 1С в MySQL. Замечу, что 1С работает на dbf-файлах. Язык программирования для реализации импорта/экспорта был выбран Python. (Я был очень удивлён насколько просто решилась эта задача!).

Структура dbf файлов 1С

Итак, небольшой реверс-инжиниринг. Помимо системных dbf, в каталоге базы находятся и пользовательские:

  • SC*.dbf — справочники
  • DH*.dbf — документы
  • DT*.dbf — табличная часть документов

Главный ключ у справочников — ID, у документов и табличной части — IDDOC. Поля имеют сквозную нумерацию и начинаются с префикса SP. Значения ключей имеют несколько странный вид: 1, 2, 3… 9, A, B… F, G, H… Z, 10, 11… 19, 1A… 1z и т.д., то есть находятся в 36-значной системе счисления.

Реализация

Как говорилось ранее, язык программирования — Python, благо опыт импорта/экспорта уже есть (Oracle, текстовые файлы). Необходимые библиотеки нашлись сразу: dbfpy для работы с dbf и MySQLdb для работы с MySQL. Первая засада ждала меня при создании БД dbf. Способ, описанный в инструкции db = dbf.Dbf(«SC21.dbf») не заработал, небольшое чтение исходников предложило другой

db = dbf.Dbf()
db.OpenFile("SC21.dbf")

Внимание! Все названия полей только в верхнем регистре. Обращаться к полю можно по имени. В случае даты получим тьюпл вида (год, месяц, число). Строковые поля находятся в кодировке ANSI. У библиотеки dbfpy есть также методы для добавления записей, но не будем на этом останавливаться, т.к. это хорошо описано автором в документации.

Из граблей, на которые я наступил:

  1. Т.к. читаю я из dbf, то надо учитывать, что некоторые записи являются удалёнными. Проверка на актуальность записи в dbfpy выполняется с помощью свойства isDeleted;
  2. По каким-то причинам ID надо стрипать (rec[“ID”].strip());

Работа с базой MySQL тоже ведётся довольно просто. Приведён пример импорта таблицы “Автомобили”.

from dbfpy import dbf
from types import *
from importCommon import _unicode, recode
import MySQLdb

db = dbf.Dbf()
db.openFile("SC20.DBF")

dbSQL = MySQLdb.connect(host="localhost", user="root", passwd="root", db="AutoSchoolTest", charset='utf8')
cursor = dbSQL.cursor()

sql = "INSERT INTO AutoSchoolTest.Automobile(ID, RegNo, Model) VALUES \n"
for rec in db:
    if rec.isDeleted:
        continue
    sql += "(%s, '%s', '%s'), \n" % (recode(rec["ID"].strip()), _unicode(rec["DESCR"]), _unicode(rec["SP22"]))
    sql = sql[:-3]
    print sql
    cursor.execute(sql)
dbSQL.commit()
dbSQL.close()

Напоследок несколько функций конвертации

from types import *
import datetime

# перекодировка строки из Win1251 в UTF8
def _unicode(str):
    str = str.decode('windows-1251').encode('utf-8')

# перекодировка значений ключей
def recode(str):
    # преобразование одного символа в число
    def recalcChar(char):
        charDict = {'1': 1, '2': 2, '0': 0, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, 'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15, 'G': 16, 'H': 17, 'I': 18, 'J': 19, 'K': 20, 'L': 21, 'M': 22, 'N': 23, 'O': 24, 'P': 25, 'Q': 26, 'R': 27, 'S': 28, 'T': 29, 'U': 30, 'V': 31, 'W': 32, 'X': 33, 'Y': 34, 'Z': 35}
        return charDict[char]
    if str == '0':
        return 'null'
    # перевод из 36-значной в 10-значную систему счисления
    pow = 1
    result = 0
    while str != '':
        val = recalcChar(str[-1:])
        result += val*pow
        pow = pow*36
        str = str[:-1]
    return result

# преобразование тьюпла даты в дату, понятную MySQL
def _date(d):
    if (d[0] == 0) and (d[1] == 0) and (d[2] == 0):
        return ''
    else:
        return datetime.date(d[0], d[1], d[2])