Возможно, вопрос поставлен слишком жестко, но все же.
Я думаю, многие меня поддержат. Реляционные базы данных — это большой геморрой. Возможно, ничего лучшего не придумано (?), но геморроем они от этого быть не перестают.
Два раза мне приходилось работать над проектами (и оба раза — неудачными
), где велась работа с разнообразными, но _родственными_ объектами. Или объектами, различающимися в одном свойстве, но от этого свойства изменялась логика работы приложения.
Пример
Имеем туристическую компанию, резервирующую места в отелях. Казалось бы, что может быть проще?
Резервации могут быть поименно/пономерно. Могут быть списком в n человек на заранее зарегистрированные номера. могут быть блоком (20 SNGL, 10 DBL, 5 TRPL) комнат на будущее.
Могут быть с полным питанием (Full Board, BB), могут быть с половинным (HB).
Могут быть с детьми (у разных отелей — разная политика на детей, например 0-6 бесплатно, 6-12 50% скидка или 0-4 бесплатно, 4-8 50%, 8-12 25%)
Могут попадать на межсезонье в отеле (половина — по одной цене, вторая половина — по другой, завышенной, а что делать — например Формула 1 проходит)
Представляете, да? При этом оператор должен видеть все эти резервации в списке, делать по ним выборку произвольным образом (а покажите-ка мне блочные резервации в отель Zurich с 15-го сентября сего года).
Что самое интересное, в таком случае проблема не в обработке данных — что их там обрабатывать? Проблема — в самих данных. Что как и куда хранить? Проектировка базы — кошмар. Нормализация базы — еще тот геморрой. Расширение понятия "резервация"? Практически нереально (дополнительные таблицы, колонки, ключи...) Я конечно не спорю, многое зависит от кривых шаловливых ручек разработчика, но, имхо, рано или поздно приходишь к выводу, что пытаешься "впихнуть невпихуемое".
Может, ну ее, эту реляцию, к чертям? Ы?
Приходиться заниматься гадостью — зарабатывать на жизнь честным трудом (Б.Шоу)
Здравствуйте, Mamut, Вы писали:
M>Я думаю, многие меня поддержат. Реляционные базы данных — это большой геморрой.
Это реляционные базы геморрой? Реляционные СУБД позволяют быстро и эффективно решать прорву вопросов.
Для интересующихся привожу фрагмент своего давнего письма товарищам на тему сетевых БД, возникшего в результате спора, "а кто же круче".
Сорри за присутствующий несколько шутливый тон и возможные неточности, это был момент с оттенком истерии ))
//------------------ letter
"..когда мужчины были настоящими мужчинами
и сами писали драйвера устройств."
Линус Торвальдс
О сетевых БД
Господа офицеры! Познакомился я в последнее время с чудесной СУБД dbVista (с) Raima Corp.
Очень старый продукт, но, что интересно, до сих пор существуют энтузиасты которые его продвигают как open-source проект.
Как человек, использовавший в разное время в разной степени Oracle, MySQL и FireBird,
испытал просто высочайшее интеллектуальное наслаждение при работе с этой сетевой СУБД,
коим и поделюсь с уважаемым мною all ниже.
Прежде всего, гордый маленький дескрипшн из официальной документации:
"Сетевая модель позволяет избежать избыточности благодаря тому,
что связывание двух типов записи происходит напрямую,
без обязательного дублирования полей и индекснов.
Кроме того, переход от одной связанной записи к другой
осуществляется за одну операцию чтения, а вот реляционная модель
вынуждает Вас делать лишний запрос для выбора ключа связанной записи."
Краткое описание процесса использования Vista.
Итак, программист, одержимый поисками силы, делает следующее:
1) описывает структуру БД с использованием DDL (есс-но Database Definition Language
гы);
2) компилит ее компилятором ddlp и создает с помощью спецтулзы initdb свою БД.
БД представляет собой:
— бинарники: набор файлов данных, словарей, ключевых файлов;
— С-хидер! Сгенерирован для доступа к нашей БД. Подключаем его к проекту,
ибо в нем описаны все наши константы и записи в виде define'ов и struct'ов;
3) Пишет всяческий код для доступа к данным пользуя вистовский АПИ;
4) Goto 1.
Теперь замечания по пунктам:
1.1) Данные ораганизовываются через records и sets.
Record описывает запись/тип с его fields, types, keys, и проч;
а в set'ах определяются связи между records. 1 set задает связь вида 1:n.
1 Owner — N members. Супер.
В set указывается также order — т.е. явно в схеме БД указываем куда vista будет
добавлять member'ы в set — в начало их будет ложить или, может, в конец очереди.
1.2) Интересная ситуация с индексами/ключами. Что меня особенно удивило, они есть.
Но:
— программист сам управляет в каких файлах каким индексам надлежит хранится.
Забегая вперед — "подводных граблей" (с) здесь водится
неимоверное количество, ибо файлы с ключами
блокируются при многопользовательской работе целиком.
Т.е. если в файле n индексов, то все n и блокируются;
— использование... вот здесь вы точно удивитесь см пункт 3.1
3.1) Про организацию доступа... отдельный протяжный стон
3.1.1) Значит так, создатели dbVista предоставляют мощный API для работы с БД — пару-тройку десятков функций с названием вида d_<кммбвнр>, где <кммбвнр> означает как_можно_меньше_букв_в_нижнем_регистре, например — d_keyfrst или d_setor.
3.1.2) На все record'ы существует одна текущая запись.
Для каждого set существует один текущий member и текущий owner.
Текущим состоянием программист манипулирует с помощью функций.
ПРИМЕР:
1) Есть БД:
record Main
{
unique key int iMainKey;
}
record Child
{
unique key int iChildKey;
}
set SuperSet
{
order last;
owner Main;
member Child;
}
2) Создадим, например, Childa и прилинкуем его к Main'у с номером 5
/* ищем в MAIN запись с iMainKey=5.
Если Vista чего-то найдет - сделает запись текущей
и с ней смогут работать другие функции
*/
d_keyfind(IMAINKEY, (char *) 5); /* type safety на высоте ))*/
/* проверка ошибок */
if (db_status!=S_OKAY) return NO_SUCH_ENTRY;
/* делаем текущую запись владельцем setа SuperSet */
d_setor(SUPERSET);
/* добавляем к ней Child'a*/
/* создаем - есс-но свежесозданная запись становится текущей */
if (d_fillnew(CHILD, &child)!=S_OKAY) return CANNOT_CREATE_CHILD;
/* прилинковываем текущую запись к setу SuperSet */
d_connect(SUPERSET);
,где:
IMAINKEY — идентификатор поля Main::iMainKey
SUPERSET — идентификатор set'а SuperSet
Vista вообще генерирует ID для всех полей, записей, наборов.
// А вот это уже интересно — попытка получения метаинформации.. в 1984 г!
3.2) Как вы уже поняли, благодаря использованию такой дивной схемы, всякие там type safety,
reentrancy functions, multithreaded applications — это все не про нас, забудьте о них.
3.3) На десерт отдельным пунктом осветим способы поиска в БД.
Итак, доступные виды поиска:
— поиск по уникальному ключу
— поиск по неуникальному ключу
— поиск по записям
— поиск по дочерним записям в set'е
Разберем подробнее.
1) Поиск по уникальному ключу осуществляется с помощью функции
d_keyfind(int iFieldId, char * lpszValue);
Простейший случай. Ищет в мндексах, посему довольно быстро.
Если находит запись — делает ее текущей, нет — возвращает код ошибки;
2) Поиск по неуникальному ключу — индексу осуществляется с помощью функций:
d_keyfind, d_keyfrst, d_keynext, d_keyprev.
Пусть хранятся у нас данные в одном индексе: 0,1,2,3,4,5
00000000000111111111111112222222222344444444444555555
| <- key first
Нам нужно выбрать все записи с полем равным например, 2.
Для этого мы можем:
— воспользоваться связкой d_keyfind, d_keynext;
Функция d_keyfind всегда ищет только первую запись в индексе. То есть певая запись всегда найдется у вас достаточно быстро. Далее, пользуйте d_keynext и ручками проверяйте эквивалентность полей, другого выхода у вас нет. Это на самом деле плохой момент, означающий, что индексированные поля сравниваются не только при добавлении, но и при поиске.
— воспользоваться связкой d_keyfrst, d_keynext;
Эта связка дает уникальную возможность перебрать все записи в индексе.
Вдруг захочется пройтись по всему индексу, действительно...
3) поиск по записям
Случай, если индекса под рукой нет, а найти чего-нибудь очень хочется.
Используем знакомую схему:
d_recfrst( int iRecordId) - ищет первую запись
d_reclast( int iRecordId) - ищет последнюю запись
// iRecordId - сгенерированный Vist'ой Id-шник типа записи. ID_MAIN, например.
d_recnext() - переходит к следующей записи
d_recprev() - переходит к предыдущей записи
4) поиск по дочерним записям в setе
Алгоритм:
1) Устанавливаем текущую запись текущим владельцем для setа (см d_setor)
2) Используем функции:
d_findfm( SET) /* Find first member of SET */
d_findlm( SET) /* Find last member of SET */
d_findnm( SET) /* Find next member of SET */
d_findpm( SET) /* Find previous member of SET */
Комментарий: нет способа искать в set'е member'ы используя индексы.
Только полный перебор. Обидно. // тоже на самом деле серьезный ляп
При поиске 2-4 Вы сами, ручками, сравниваете нужные Вам поля записи по каким нибудь критериям. Тут вам поможет вся мощь стандартной библиотеки языка С.
Интересный подход к обеспечению безопасности приложения.
Некоторые функции, например d_open возвращают какие-то коды ошибок, это все даже описано в документации... Но реальность такова, что часто программа при ошибке просто вылетает с криком "Abort!" в STDOUT'е.
Нерасмотренными остались вопросы:
— организации транзакций;
— проблемы многопользовательской работы;
— блокировки и взаимоблокировки;
Thanks,
Ligen
//------------------ end
Ничего, люди пишут и на этой СУБД, а что делать, если проекты начинались в те еще времена. Действительно, герои..