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

Лучшие статьи журнала ╚Компьютеры+Программы╩


Информационный Канал Subscribe.Ru

Здравствуйте, уважаемые подписчики! В этом выпуске вашему вниманию предлагается статья "DBF-файлы и Access", которая по результатам голосования разделила третье место со статьями "EXCEL и WORD "на подхвате" у ACCESS" и "Четверо в лодке, не считая принтера".

DBF-файлы и Access

Сергей ГУЩЕНКО,
publish@nics.kiev.ua

DBF-формат файлов баз данных продолжает активно использоваться. Полный переход на Windows-технологии в СУБД не принял массового характера. В чем причина? Нехватка ресурсов? Непревзойденная структура? Сложность программирования в Visual-системах?
А возможен ли полный отказ от DBF-файлов?

Постепенно DOS-программы уходят в прошлое, но, как говорится, жизнь продолжается. Несмотря на то, что актуальность перевода DOS-СУБД на Windows очевидна, этот процесс по многим прикладным базам, особенно по тем, что циркулируют в госструктурах, затягивается. В то же время повсеместно применяются лазерные и струйные принтеры, но они "не любят" DOS-режим, а некоторые вообще могут печатать только под Windows. В итоге "стыковка" базы данных DOS с лазерным принтером может превратиться в серьезную проблему. Один из путей ее решения может быть таким. Результирующие таблицы записываются на диск в виде текстовых файлов (если эта функция была предусмотрена). Созданный файл загружается в Word с перекодировкой. Далее текст проверяется, корректируется и печатается.

Но неужели невозможно использовать данные из DBF-файлов? Прежде чем ответить на этот вопрос, познакомимся с их структурой.

Структура DBF-файлов

На первый взгляд подобные сведения могут показаться лишними. Ведь DBF-файлы можно импортировать, например, в Access, перейдя в режим Конструктор. Но проблема в том, что при обратной процедуре экспорта Access упрощает структуру, в результате чего длина записи увеличивается, и файл становится менее "красивым", если его открыть, например, в Excel. Так, файл примера, созданный в FoxPro 2.0, на диске занимает 8042 байт. После того как он побывал в Access, его размер составил 9220 байт. Почему так получилось, обсудим позже, а пока отметим, что при экспорте DBF-файлов из Access приходится выбирать: выполнять ли его короткой командой, но с меньшей "красотой", или выполнить дополнительные действия, для понимания которых нужно разбираться в структуре DBF-файлов.

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

Каждый DBF-файл имеет (укрупненно) три части: заголовок, описание структуры (полей) и записи с данными ("Заголовок" - "Структура" - "Данные").

На заголовок отводится 32 байта. Тут применяется упакованная запись чисел. В структуре на описание каждого поля тоже отводится по 32 байта. Название полей и их тип записывается посимвольно, но для указания размеров тоже используется упаковка.

В области данных все записывается посимвольно, даже числа. Для примера рассмотрим (рис. 1) начальный фрагмент небольшого DBF-файла с 10 полями и 31 записью (pr410.dbf).

Рис. 1. Дамп начала файла "pr410.dbf", имеющего 10 полей и 31 запись с данными

Начнем с заголовка. Первые 4 байта - вспомогательные. В них записывается версия (первый байт) и дата (следующие три) в формате "год-месяц-день". В данном случае это 8 февраля (19)02 года.

Более важными являются следующие 4 байта. В них указывается количество записей в виде длинного целого числа (тип Long). И его легко можно прочитать. Так, в нашем примере это шестнадцатеричные числа: 1F 00 00 00. 1F равно 31 в десятичном виде. Напомним, что здесь числа записываются по стандартному ассемблерному правилу: сначала младший байт, затем старший. Если бы записей было 2785 (в шестнадцатеричные виде 0AE1), то здесь было бы записано E1 0A 00 00.

В следующих 2 байтах указывается длина служебной части (заголовка и структуры). Фактически они задают смещение первого байта области данных относительно начала файла (с учетом того, что смещение первого байта файла равно нулю). Тип числа - целое (Integer). В примере это 61 01, что означает смещение 00000161 (его можно увидеть на рис. 2). В левом углу нижней строки указано число 00000160: - это адрес размещенного правее байта 0D. А следующий (20) и будет иметь адрес на 1 больше.

Рис. 2. Фрагмент символьной части дампа структуры DBF-файла с излишними символами в незадействованных байтах (слева), которые удалены на виде справа

Следующие 4 байта в заголовке (тип - Long) зарезервированы для указания длины одной записи. В данном примере это F8 или 248 в десятичном исчислении. Кстати, преобразования из десятичной в шестнадцатеричную систему счисления (и наоборот) можно легко выполнять с помощью самого обычного калькулятора Windows, используя вид Инженерный.

Последние 16 байт, как правило, не используются (т.е. в них всегда стоят символы с кодом 00).

Дальше идет структура - информация о полях (название, тип, ширина), расположенная в соответствии со следующими правилами:

  • на описание каждого поля отводится 32 байта;
  • в первых 10 байтах записывается имя поля;
  • 11-й байт пустой;
  • в 12-м байте указывается тип поля: C - текстовый, N - числовой, D - дата. Под тип даты отводится 8 байт, а запись выполняется в последовательности: год (4 байта), месяц (2 байта), день (2 байта);
  • в следующих четырех байтах может не быть ничего или может располагаться число, показывающее номер начала поля внутри записи (Access при экспорте обычно игнорирует их);
  • в 17-м байте показывается ширина поля, а если тип числовой, то в следующем байте может быть указано количество байт в дробной части. Например, в поле PRICE1 у нас записано 0C 03, что означает: для поля отводится 12 символов (), из них для дроби - 3 байта (плюс 1 байт для десятичной точки). В итоге для целой части резервируется 8 байт;
  • последним символом, завершающим структуру, является символ с кодом 0D.
  • Таким образом, в структуре можно выделить байты, содержащие полезную информацию, и байты, не участвующие в обработке. В них, как правило, содержится символ с кодом 00. Но встречаются DBF-файлы, в структуре которых "лишние" байты могут содержать любые символы (см. рис. 2, левая часть). Пугаться в этом случае не нужно, достаточно их проигнорировать (см. рис. 2, правая часть). Контрольным символом, отделяющим полезную часть имени или длины поля, является символ с кодом 0.)

    Некоторые выводы

    1. В таблице Access, которую планируется экспортировать в DBF-файл, имена полей не могут быть длиннее 10 символов, которые должны быть латинскими буквами. Кроме того, имя таблицы не должно быть длиннее 8 символов и, желательно, тоже должно состоять из латинских букв.
    2. Если в Access-97 установлена связь с DBF-файлом, то удаление некоторых записей не влияет на размер файла на диске, так как при этом физически записи не удаляются! Чтобы избавиться от удаленных записей, придется смоделировать команду упаковки. В Access-2000 и ХР эта проблема отпадает в принципе - в них DBF-файлы стандартно можно открывать только для чтения.

    От теории к практике

    Ознакомившись с теорией, можно проверить длину записи в рассматриваемом примере. В нем по структуре должно быть 10 полей, ширина которых следующая (в HEX-кодах):

    12, 0A, 0A, 09, 4E, 4E, 0A, 0C, 0A, 0C
    или, в десятитичном счислении,
    18 + 10 + 10 + 9 + 78 + 78 + 10 + 12 + 10 + 12 = 247

    Но выше шла речь о числе 248 - где же еще один байт? Он всегда дописывается впереди первого поля и служит для размещения в нем признака удаленности записи. Признак имеет вид символа с кодом - "звездочки" (*), и размер файла при этом не меняется. А если эти коды каким-то образом заменить на 20, то записи восстановятся!

    Область данных файла состоит из записанных подряд (друг за другом и без всяких разделителей) записей. Самая последняя запись должна быть "закрыта" символом с кодом . В противном случае запись не будет видна при просмотре.

    Таким образом, DBF-файлы имеют достаточно простую структуру и могут быть просмотрены в разных программах - в частности, в Excel и Access. Причем Access-97 может создавать к ним индексные файлы. При работе в Excel и Access-2000/ХР возможностей меньше, но есть некоторые особенности.

    В заключение обзора структуры имеет смысл остановиться на утверждении, что DBF-файлы компактнее, чем файлы, создаваемые в visual-системах. С одной стороны, действительно, DBF-файлы состоят практически из одних только "полезных" данных. Объем заголовка и структуры незначителен. А файлы visual-систем включают в себя различную дополнительную информацию. Например, если создать пустой MDB-файл в Access-97 и импортировать в него рассматриваемый DBF-файл, то он займет на диске 75776 байт (т.е. почти в 9 раз больше). Но в данном примере записей мало - только 31. А в DBF-файлах есть один неприятный момент - числа в них записываются посимвольно, без упаковки на битовом уровне. То есть, если в каком-то поле должно появиться число значением до 2 млрд., то для него придется резервировать 10 байт (2 000 000 000) в каждой записи! В visual-системах для такого поля можно указать тип "длинное целое", на которое отводится 4 байта - в 2,5 раза меньше. И чем больше в файле числовых полей и записей, тем более компактными окажутся файлы, созданные в visual-системе и, наоборот, тем больше места будут требовать DBF-файлы. Для примера можно сравнить фрагмент дампа DBF- и MDB-файла с аналогичными данными (рис. 3).

    Рис. 3. Сравнение фрагментов символьной части дампa DBF-файла (слева) и MDB-файла (справа). Видна упакованная запись числовых полей в MDB-файле

    Об экспорте из Access

    Несколько слов о том, как Access обращается со структурой DBF-файлов при экспорте. Он ее "упрощает" по числовым полям, выделяя каждому из них одинаковое количество байт - всего 20 и из них 5 для дроби (14 05 в HEX-кодах). И это вне зависимости от того, каков был тип в самом Access - целым и вообще без дробей или же с дробями. На рис. 4 приводится фрагмент дампа DBF-файла рассматриваемого примера после его импорта в Access и обратного экспорта.

    Рис. 4. Фрагмент дампа файла после импорта в Access и обратного экспорта в DBF-файл

    Посмотрим, что изменилось. Во-первых, слегка видоизменилась дата. Год - 102-й (66 в HEX). Это правильно: если к 1900 прибавить 102, то получится 2002 - действительно, текущий год! Количество записей и адрес начала области данных остались прежними (1F и 61 01), а вот ширина записи увеличилась! (1E 01 против прежних F8). 1E 01 означает 011E или 286 в десятичном счислении. А раньше было 248 - то есть добавилось 38 "лишних" байт за счет "упрощения" размера числовых полей. В частности, увеличилась ширина числового поля N - 14 05 вместо прежних 12. Ширина текстовых полей остается без изменений. И исчезли номера начала полей в записи.

    Знание структуры DBF-файлов поможет нам понять, как они используются в Access.

    Управление DBF-файлами из Access

    В рассматриваемом далее примере выполняются следующие операции:

  • программно создается пустой MDB-файл;
  • в текущую базу данных импортируется структура некоторого DBF-файла;
  • она копируется в созданный пустой MDB-файл;
  • устанавливается связь с DBF-файлом, структура которого ранее импортировалась;
  • в цикле в пустую таблицу нового MDB-файла копируются записи из связанного DBF-файла;
  • для примера в ней программно создается индекс по полю N;
  • работа в дальнейшем производится с этой таблицей в MDB-файле;
  • если требуется вывести результаты в виде DBF-файла, то в примере показана установка связи с таблицей в MDB-файле и экспорт данных из нее в виде DBF-файла.
  • Вряд ли в конкретных задачах придется выполнять все перечисленные операции. Иногда достаточно организовать только поиск по DBF-файлу без редактирования и вывода измененных данных. Либо, наоборот, вся работа будет выполняться с MDB-файлами, а вывод может потребоваться в виде DBF-файла для записи на дискету или для пересылки по электронной почте.

    В предлагаемом ниже примере рассмотрим принципы программной обработки разных ситуаций.

    Sub proba()
    
     Dim wrkS As Workspace, ddDBF As Database, dd As Database
     Dim zapD As Recordset, zapM As Recordset, TABF2 As TableDef
     Dim NameDBF As String, Name2 As String, NameF As String
     Dim PathNakl As String, J1 As Byte, F1 As Byte
     Dim Tab1 As TableDef, IND1 As Index, Pole1 As Field

    '- Удаление старой версии MDB-файла под DBF-файл -
     If Dir("c:\baza\NewDBF.mdb") <> "" Then Kill "c:\baza\NewDBF.mdb"
    '- Создание нового MDB-файла -
     Set wrkS = DBEngine.Workspaces(0)
     Set ddDBF = wrkS.CreateDatabase("c:\baza\NewDBF.mdb", dbLangCyrillic, dbEncrypt)
    '- Удаление старой версии структуры DBF-файла в текущей базе -
     On Error Resume Next
     DoCmd.DeleteObject acTable, "StrDBF"
     On Error GoTo 0
    '- Копирование в текущую базу структуры <pr410.dbf>-файла -
    '- в виде пустой таблицы <StrDBF> -
     DoCmd.TransferDatabase acImport, "dBASE IV", "c:\baza", acTable, "pr410.dbf", "StrDBF", True
    '- Копирование этой пустой таблицы <StrDBF> -
    '- в новую MDB-базу под именем <pr410M> -
     DoCmd.CopyObject "c:\baza\NewDBF.mdb", "pr410M", acTable, "StrDBF"
    '- Подсоединение <pr410.dbf>-файла к текущей базе -
     Set dd = CurrentDb
     NameF = "TabDBF": PathNakl = "c:\baza": Name2 = "pr410.dbf"
     On Error Resume Next
     dd.TableDefs.Delete (NameF)
     On Error GoTo 0
     NameDBF = "dBASE IV;DATABASE=" & PathNakl
     Set TABF2 = dd.CreateTableDef(NameF)
     TABF2.Connect = NameDBF
     TABF2.SourceTableName = Name2
     dd.TableDefs.Append TABF2
     dd.TableDefs.Refresh

    '- Копирование данных из DBF-файла в MDB-файл -
    '- (с автоматич. перекодировкой из 866 в 1251) -
     Set zapD = dd.OpenRecordset("TabDBF", dbOpenDynaset)
     Set zapM = ddDBF.OpenRecordset("pr410M", dbOpenDynaset)
     zapD.MoveFirst
     F1 = zapD.Fields.Count - 1
     Do Until zapD.EOF
      zapM.AddNew
      For J1 = 0 To F1
       zapM(J1) = zapD(J1)
      Next J1
      zapM.Update
      zapD.MoveNext
     Loop

    '- Создание индекса в новой таблице, например, по полю -
     zapM.Close
     Set Tab1 = ddDBF.TableDefs!pr410M
     Set IND1 = Tab1.CreateIndex("NN") '- Имя индекса
     Set Pole1 = IND1.CreateField("N") '- Имя поля таблицы
     IND1.Fields.Append Pole1
     Tab1.Indexes.Append IND1
     Tab1.Indexes.Refresh
    '- Открытие таблицы <pr410M> из MDB-файла для поиска по индексу -
     Set zapM = ddDBF.OpenRecordset("pr410M", dbOpenTable)
     zapM.Index = "NN"

    '- Работа с записями с поиском по индексу -
    '- и с любым редактированием данных, -
    '- в том числе с удалением или добавлением записей -
    ' . . . . . .
    ' . . zap.seek "="... 'поиск выполняется методом <seek>
    ' . . . . . .

    '- Подсоединение таблицы <pr410M> из MDB-файла -
    '- к текущей базе под именем <TabMDB> -
    '- (если требуется экспорт результатов в DBF-файл) -
     Set dd = CurrentDb
     NameF = "TabMDB": PathNakl = "c:\baza\NewDBF.mdb"
     Name2 = "pr410M"
     On Error Resume Next
     dd.TableDefs.Delete (NameF)
     On Error GoTo 0
     NameDBF = ";DATABASE=" & PathNakl
     Set TABF2 = dd.CreateTableDef(NameF)
     TABF2.Connect = NameDBF
     TABF2.SourceTableName = Name2
     dd.TableDefs.Append TABF2
     dd.TableDefs.Refresh
    '- Экспорт этой таблицы <TabMDB> в DBF-файл под новым -
    '- именем <pr410_N.dbf> (а можно и под старым) -
     DoCmd.TransferDatabase acExport, "dBASE IV", "c:\baza", acTable, "TabMDB", "pr410_N.dbf"
    '- Удаление связей и пустой таблицы -
     On Error Resume Next
     DoCmd.DeleteObject acTable, "TabMDB"
     DoCmd.DeleteObject acTable, "TabDBF"
     DoCmd.DeleteObject acTable, "StrDBF"
     On Error GoTo 0
    End Sub

    В Access-97 можно создавать индексы прямо к присоединенным DBF-файлам. Например, так:

    Dim dd407 As Database, strSQL As String
    
    Dim strMsg As String, varReturn As Variant
    Dim tab1 As TableDef, IND1 As Index, Pole1 As Field, Inds As Index
    . . . . . .
     Set dd407 = OpenDatabase("d:\svod2", False, False, "FoxPro 2.5; ")
      '- Проверка наличия индекса -
      Set tab1 = dd407.TableDefs!Price407
      For Each Inds In tab1.Indexes
       If Inds.Name = "NN" Then
        '- Удаление старого индекса, если он есть -
        strSQL = "DROP INDEX NN ON Price407.dbf;"
        dd407.Execute strSQL
       Exit For
      End If
     Next
    '- Создание индекса ---
    '- (с выводом предупреждения в строке статуса) -
    strMsg = "Создание индекса в Price407.dbf ..."
    varReturn = SysCmd(acSysCmdSetStatus, strMsg)
    Set IND1 = tab1.CreateIndex("NN")
    Set Pole1 = IND1.CreateField("N")
    IND1.Fields.Append Pole1
    tab1.Indexes.Append IND1
    tab1.Indexes.Refresh

    В справочной системе Access ХР есть упоминание о том, что в службе технической поддержки Microsoft можно получить обновленный драйвер ISAM для работы с FoxPro. Но есть проблема с его подключением. Там же, в справке, приводятся рекомендации по внесению в реестр ссылок на новый драйвер. А в другом месте говорится, что поддержка типа FoxPro возможна только для импорта. Как обстоит дело на самом деле, автору выяснить не удалось, в связи с тем что для присоединения, импорта или экспорта DBF-файлов достаточно параметра dBase.

    Но, как уже отмечалось выше, команда экспорта в DBF-файл изменяет его структуру по числовым полям. В связи с этим новый DBF-файл может оказаться не таким "красивым", как исходный. Чтобы сохранить структуру DBF-файлов при выходе из Access, необходимо предусмотреть программу обработки, что и показано на примере.

    В рассмотренном случае нет контроля за последовательностью полей, которая может быть нарушена вручную при открытии MDB-файла, содержащего копию DBF-файла. Но если при работе с конкретной базой данных такая ситуация в принципе невозможна, то этим контролем можно пренебречь.

    Более важной может оказаться необходимость вывода окна поиска DBF-файла на диске, с тем чтобы имя найденного файла заносилось в символьную переменную (рис. 5). И такая возможность есть. Текст процедур и функций, участвующих в выводе на экран окна поиска, приводится в модуле ОбновлениеСвязейТаблиц учебной базы Решения комплекта Access-97 (обычно при инсталляции она отключена). Запускающая функция имеет имя FindNorthwind. При ее вызове следует указать стартовый каталог, например:

    Dim N1 As String
    
    . . . . .
    N1 = FindNorthwind("C:\Program files\Microsoft Office\Office\Samples")

    Рис. 5. Вид окна поиска файлов с занесением выбранного имени в символьную переменную

    Некоторым неудобством в использовании указанной функции является то, что она вызывает еще ряд функций и процедур. Кроме того, их копирование нужно выполнять вместе с копированием пользовательских типов данных, имеющихся в начале этого модуля (Type MSA_OPENFILENAME, Type OPENFILENAME), и с объявлением:

    Declare Function GetOpenFileName Lib "comdlg32.dll"
    Alias "GetOpenFileNameA" (pOpenfilename As OPENFILENAME) As Boolean.

    А при вызове бывает полезно указывать не только стартовый каталог, но и маску поиска (например, *.dbf), текст для заголовка окна и т. д. В конечном итоге эти библиотечные процедуры не сложно переработать так, чтобы их было удобно копировать в разрабатываемые базы. Причем, если исключить одну строку, вызывающую ошибку в Excel, то этот код практически без изменений можно использовать и в тексте макросов Excel. Например, в одном из них автор применил следующую строку вызова обновленной функции окна поиска:

    Dim  NameDBF as String
    
       . . . . .
       NameDBF = OpenFile("D:\Prices", "Файл Price407.dbf", "Price407.dbf", "Поиск файла <Price407.dbf>")

    То есть в функцию передаются аргументы: Стартовый каталог, Подсказка, Маска поиска, Строка в заголовок.

    Существует еще один метод вывода имен файлов на экран - в виде списка, заполняемого с помощью функции. Его пример приведен в справочной системе Access-97 по запросу "RowSourceType" - "RowSourceType - свойство"->Свойства "Тип источника строк" (…) "Источник строк" (…) -> в "Примерах" третий пример, вторая функция "Function ListMDBs"

    Таким образом, в VBA Access есть достаточно возможностей - как по работе с файлами на дисках вообще, так и с DBF-файлами в частности. В сочетании с умением грамотно проектировать экранные формы все это может превратиться в очень мощный инструмент по созданию Windows-подобных баз данных с возможностью безболезненного "перелива" информации из ранее созданных баз DOS.

    Примечание

    Начинающим программистам в Access 2000/ХР следует помнить, что, в отличие от Access 97, в этих версиях принят метод размещения операторов в отдельных библиотечных комплектах (References), подключение которых надо контролировать. В частности, свойства типов объектов Database, Recordset и т.п. будут недоступны до тех пор, пока не будет подключен какой-нибудь Reference DAO, например DAO 3.6 (см. рис. 6, справа в центре). Но еще надо знать, что некоторые Reference могут слегка конфликтовать друг с другом. В частности, по умолчанию включается Reference ActiveX (см. рис. 6, справа внизу). Но если при этом подключить DAO 3.6, то объявление переменной типа Recordset в стиле Access 97 (например, Dim zap As Recordset) вызовет ошибку. В этом случае правильнее будет написать: Dim zap As DAO.Recordset (т.е. как элемент коллекции DAO). Либо можно отключить Reference ActiveX - тогда объявлять можно как в Access 97 (Dim zap As Recordset).

    Рис. 6. Сборный рисунок окон подключения Reference в Access ХР (в Access 2000 аналогично)

    А что в Excel?

    И в заключение - несколько слов об использовании DBF-файлов в Excel.

    В этой программе тоже можно открывать DBF-файлы. Но Excel слабо контролирует их структуру. При записи он выбирает ширину и тип поля по ширине и типу содержимого первой строки сверху после строки заголовков колонок (т.е. названий полей). При этом в структуре может уменьшиться ширина текстовых полей, либо измениться тип поля, если содержимого в ячейке контрольной строки не окажется. В результате могут возникнуть разнообразные потери данных. Кроме того, Excel, как правило, не сохраняет новые колонки или добавленные строки. Поэтому после открытия DBF-файла лист желательно скопировать в другой, изменения выполнить на нем и уже из него перезаписать на диск в формате DBF-файла. Такое копирование можно не делать, если сохранение будет выполняться в формате самого Excel.

    Перед записью в формате DBF желательно расширить колонки так, чтобы содержимое полностью было видно во всех строках, причем с запасом пустого места справа. А будет еще лучше, если шрифт предварительно заменить на Courier New.

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

    Сергей ГУЩЕНКО,
    publish@nics.kiev.ua


    Задать вопрос
    Прислать свою статью для публикации в журнале
    Просто поговорить
    Получить именной бланк подписки на "бумажную" версию
    Получить каталог всех статей

    До следующего выпуска!
    Елена Полонская, редактор "К+П"
    www.cp.comizdat.com

    Перепечатка материалов этой рассылки разрешается только по согласованию с редакцией журнала "Компьютеры+Программы"



    http://subscribe.ru/
    E-mail: ask@subscribe.ru
    Отписаться
    Убрать рекламу

    В избранное