(1) БД на плоских файлах
(Q 1.1) Я хочу какую-нибудь простейшую БД и прямо сейчас!
(Q 1.2) Можно ли как-нибудь из Perl получить доступ к dbf файлам?
(Q 1.3) А к MS access .mdb?
(Q 1.4) Зачем и как нужно запирать (lock) файлы?
(2) Реляционные, пост-реляционные и объектно-реляционные СУБД
(Q 2.1) Как использовать DBI?
(Q 2.2) Как работать с записями кусочками: первые N записей, следующие N...?
(Q 2.2) Где взять документацию/учебник по SQL?
* * *
(1) БД на плоских файлах
(Q 1.1) Я хочу какую-нибудь простейшую БД и прямо сейчас!
(A)
Вы можете использовать простой текстовый файл с разделителями. Например, если
мы пишем нечто типа телефонной книги, то вполне вероятно предположить, что ни
в чьем имени, ни в номере телефона не встретится последовательность::, так
что именно ее и можно использовать в качестве разделителей.
Файл с данными может выглядеть так:
Иванов И.И.::888-0000::Какая-то улица, 17, кв 40
Сидоров П.И.::888-8429::Другая улица, 5, кв 21
...... и тд.
тогда программа, которая читает данные, может быть примерно такого вида:
#!/usr/bin/perl
# открываем файл
open DATA, 'data.dat' or die "Невозможно открыть data.dat: $!";
# читаем построчно из файла
while (<DATA>) {
chomp; # удаление символа конца строки
# теперь в $_ есть строка и мы ее разделяем на переменные
($name, $phone, $address) = split(/::/);
# и выведем на печать
print "Имя: $name, телефон: $phone, адрес: $address\n";
}
close DATA;
#всё
Больше проблем возникает в случее, если надо удалить или отредактировать
запись, но и их можно довольно просто и элегантно решить, если использовать
механизм редактирования на месте (inplace edit) -- при использовании операции
"ромб"(<>), можно читать из одного файла, а писать в другой:
#!/usr/bin/perl
$^I = '~'; # запускаем inplace edit
while (<>) { # Обратите внимание, что мы не открывали файл: при такой
#конструкции имя файла берется из коммандной строки
chomp;
($name, $phone, $address) = split(/::/);
if (.... некоторое условие, при котором мы оставляем наши данные ... )
{
print "$name::$phone::$address\n"; # теперь данные есть в новом файле
}
}
# конец примера
если запустить это программу как
change.pl data.dat
,то в текущем каталоге будут два файла: data.dat, с записями, которые
удовлетворили нашим условиям и data.dat~ -- предыдущая копия.
(также, во многих случаях, всю программу такого типа можно записать как
one-liner:
perl -i~ -n -e 'print if(... условие)'
)
Двоичные файлы
Для чтения двоичных файлов в Perl можно использовать функции read и unpack.
К примеру, если использовать двоичный файл для хранения телефонной книги такого
формата:
40 символов -- фамилия, И.О.
10 символов -- номер телефона,
60 cимволов -- адрес,
то строка описания формата для unpack будет выглядеть так:
$format_str = 'A40 A10 A60';
а сама программа, аналогичная первому примеру:
#!/usr/bin/perl
$format_str = 'A40 A10 A60';
open DATA, 'binary.dat' or die "$!";
while (read(DATA, $buf, 40+10+60)) { # <DATA> не покатит: такая
# конструкция будет читать до символа перевода строки, а это не то, что нужно
($name, $phone, $address) = unpack($format_str, $buf);
# Теперь в $name, $phone, $address есть данные и с ними можно делать
# все, что захочется
}
close DATA;
Чтобы вывести в файл такую запись можно использовать конструкцию типа
(Q 1.2) Можно ли как-нибудь из Perl получить доступ к dbf файлам?
(A)
Да, можно. На http://www.fi.muni.cz/~adelton/
есть модуль XBase, которыйпозволяет читать/писать dbf. При чтении он даже поддерживает индексы.
Кроме того, в комплект поставки также входит модуль DBD::XBase, при помощи
которого можно даже оперировать dbf на SQL.
(Q 1.3) А к MS access .mdb?
(A)
К файлам MS Access нельзя обращаться из perl напрямую, по крайней мере, в
настоящее время.
К MS Access можно обращаться по ODBC, при помощи DBD::ODBC.
(Q 1.4) Зачем и как нужно запирать (lock) файлы?
(A)
Представьте себе ситуацию когда одновременно работают несколько копий одной и
той же программы (к примеру, cgi-скрипты, обслуживающие запросы),
читающие/пишушие в один файл, тогда рано или поздно создатся ситуация при
которой один скрипт прочитал данные, произвел над ними некоторые действия и
собрался записать их назад в файл, но в это же время другой скрипт тоже
прочитал данные, тоже произвел над ними действия, но (!) он прочитал старые
данные, которые он и запишет поверх данных, выданных другим скриптом. Таким
образом, в файле остануться данные записаные одним из скриптов -- в лучшем
случае, в худшем -- структура файла будет испорчена. Чтобы этого избежать в
Unix и большинстве других ОС есть системный вызов flock(2) или
аналогичный.
Как использовать flock
К примеру, скрипт который записывает имена вызывающих хостов в файл. (На деле
такой список, конечно, можно получить из журнала регистрации web-сервера).
#!/usr/bin/perl
use Fcntl; # Импорт констант
open (HOSTS, '>>hosts.log'); # Файл открыт для добавления записи
flock(HOSTS, LOCK_EX);
# Теперь файл заблокирован: Если любой другой скрипт тоже вызовет flock на
# этом файле, его flock не вернет управление в программу, пока мы не
# разблокируем файл. Обратите внимание: flock -- декларативная функция, если
# один из скриптов ее не использует при записи, то вся ваша блокировка не
# работает.
print HOSTS $ENV{REMOTE_HOST}, "\n"; # записали строку
close HOSTS; # Файл при закрытии разблокируется автоматически
# Вывести сообщение для пользователей
print "Content-Type: text/plain\n\n";
print "Название вашего хоста записано\n";
Чего делать на системах, где нет flock или для блокировок файлов на сетевых
дисках?
Судя по perlfaq5(1), можно использовать модуль File::Lock с CPAN.
* * *
(2) Реляционные, пост-реляционные и объектно-реляционные СУБД
(Q 2.1) Как использовать DBI?
(A)
DBI -- это интерфейс прикладных программ к СУБД, использующим SQL в качестве
языка запросов. Cам DBI определяет только набор функций, переменных и
соглашений. Вся непосредственная работа выполняется Database Drivers (DBD) --
модулями, обеспечивающими связь с СУБД. DBI только обеспечивает стандартный
интерфейс для этих драйверов.
Полная схема архитектуры при работе DBI выглядит примерно так:
$dbh Это об'ект, при помощи его методов осуществляются взаимодействия с
СУБД.
$dsn Строка, определяющая к какой базе данных подсоединятся и другие
параметры. Зависит от DBD. На сегодняшний момент стандарта нет, но
рекомендовано использовать стиль ODBC:
'dbi:<имя DBD>:databasename=<название БД>;host=<Имя хоста>;port=<порт>';
$user Имя пользователя.
$auth Нечто, авторизующее пользователя. Обычно пароль.
options Параметры DBI, передаются через анонимный хеш. В настоящее время
понимаются три параметра:
* RaiseError, если установлен, то при любой ошибке DBI убивает
программу
* PrintError, если установлен, то при ошибке DBI вызывает warn
* AutoCommit -- определяет порядок работы с транзакциями.
Например:
$dbh = DBI->connect('dbi:Pg:dbname=apavel', 'apavel', 'SomeSecret',
{RaiseError=>1, AutoCommit=>0});
Означает: Подсоедение к СУБД PostgreSQL, к базе данных apavel, с именем
пользователя apavel и паролем. Все ошибки будут вызывать die, что удобно при
отладке, а все изменения будут внесены только при подтверждении (commit)
транзакций.
Отсоединение обеспечивается при помощи метода disconnect: $dbh->disconnect();
Механизм курсоров и подготовки запросов
При работе с базами данных при помощи DBI используюся курсоры -- специальные
об'екты, обеспечивающие последовательный доступ к результатам запросов. (В
простейших случаях можно обойтись и без них, я расскажу об этом дальше.)
Пример таблицы, используемой в дальнейшем:
create table foo (
bar varchar(50),
baz int
)
Получение данных
$cursor = $dbh->prepare('select bar, baz from foo');
# теперь $cursor -- курсор, и его необходимо исполнить
$cursor->execute;
# После исполнения запроса, результат можно получить из курсора при помощи
# метода fetchrow_array
while (($bar, $baz) = $cursor->fetchrow_array) {
print "bar is: $bar, baz: $baz\n";
}
Placeholders
Очень часто бывает надо подготовить какой-либо запрос, а потом использовать
его с разными значениями данных. DBI предлагает для механизм placeholders:
В запросе на месте таких данных указываются вопросительные знаки, а сами
значения передаются в метод execute() курсора. Например:
$cursor = $dbh->prepare('select bar from foo where baz=?');
$cursor->execute($baz);
Особенно удобно это в случае вставки данных:
$cursor = $dbh->prepare('insert into foo(bar, baz) values(?, ?)');
while ( ... ) {
$cursor->execute($bar, $baz);
}
Таким образом, СУБД разбирает запрос толлько один раз, а затем просто
исполняет его, что экономит время. (Естественно, это верно только для DBMS с
раздельными parse и execute, сейчас ни MySQL, ни PostgreSQL такое не
поддерживают, поэтому их реализации DBD просто сохраняют запрос переданный
$dbh->prepare() и затем подставляют в него данные при каждом $sth->execute().)
Работа без курсоров
DBI предоставляет несколько методов для такого рода работы:
Методы для запросов:
* selectrow_array Возвращает одну строку запроса в виде
массива,
* selectall_arrayref Возвращает весь ответ сервера в виде
массива ссылок на массивы.
Методы для выражений, не возвращающих значений
* do исполняет запрос
Пример:
#получить значение bar при baz=3
($bar) = $dbh->selectrow_array('select bar from foo where baz=3');
# установить baz в некоторое значение при bar='somestring'
$dbh->do("update set baz=1 where bar='somestring'");
(Q 2.2) Как работать с записями кусочками: первые N записей, следующие N...?
Можно несколькими способами:
1. Самый грубый и не эстетичный -- Просто прокручивая курсор:
$c = $dbh->prepare('select baz, bar from foo');
$c->execute;
# если нужна последовательность с 26 по 50
for ($k = 0; $k < 26; $k++) {
$c->fetchrow_array;
}
# теперь можно вывести данные
print "<table border=1><tr><th>bar</th><th>baz</th></tr>\n";
while (($bar, $baz) = $c->fetchtrow_array) {
print "<tr><td>$bar</td><td>$baz</td></tr>\n";
}
$c->finish; # Закрыть курсор
print "</table>";
2. Используя курсоры СУБД
# Показан ситаксис PostgeSQL
$dbh->do('declare mycursor cursor for select bar, baz from foo');
$dbh->do('move 25');
# И теперь будем получать данные
$c = $dbh->prepare('fetch forward 25 in mycursor');
while (($bar, $baz) = $c->fetchrow_array) {
print ....;
}
$c->finish;
$dbh->do('close mycursor');
3. Для MySQL, авторы которого не в состоянии реализовать курсоры, можно
использовать директиву LIMIT
$c = $dbh->prepare('select bar, baz from foo limit 26,25');
while (($bar, $baz) = $c->fetchrow_array) {
print ....;
}
$c->finish;
(Q 2.2) Где взять документацию/учебник по SQL?
(A)
* В книжных магазинах сейчас есть неплохой набор книг по SQL. (От рекоммендаций
воздержусь -- ни одну из них я не читал)
* На MCP personal bookshelf ( http://pbs.mcp.com
) есть книга
"Teach yourself SQL in 21 days" -- весьма рекомендую, очень хорошая книга.
* На citforum (http://www.citforum.ru) были какие-то руководства
======================================================================
ИНФОРМАЦИЯ для тех, кто хочет добавить ответы!
Присылайте их координатору на apv@i-connect.ru
Присылайте ответ с указанием секции, но без номера (у меня нумерация автоматическая) в
таком формате:
----------------------------------------------
faq_Q(Что такое хорошо, а что такое плохо?)
faq_A Давно я не читал стихотворения Маршака,
так что наизусть не помню.
-----------------------------------------------
Слова faq_Q и faq_A будут автоматически заменены на
нужные номера. А аргумент faq_Q еще и вставлен в
оглавление. Кто хочет узнать, как это делается, см.
инфо про GNU m4 в самом конце.
---------------------------------------------------------------------
Timestamps, нумерция глав и вопросов, а также оглавление в этом тексте
сгенерированы автоматически с помощью макропроцессора GNU m4.
( ftp://ftp.nc.ras.ru/pub/gnu/m4-1.4.tar.gz
) GNU m4 рулит!!