Postgre(No)Sql Или Снова О Хранении Данных С Гибкой Структурой

Когда дело доходит до хранения в базе данных гибких (ранее неизвестных, часто изменяемых) структур данных, разработчики обычно обращаются к «великому и ужасному» шаблону EAV или к модным ныне базам данных NOSQL. Не так давно передо мной встала похожая задача.

ЭАВ .

Оно вызывает у меня сильную неприязнь, и о нем сказано и написано много негативного (Кайт, Фаулер, Карвин, Горман).

Основной недостаток в том, что при написании запросов приходится оперировать не реальными сущностями («Сотрудник», «Дом», «Клиент», для чего и предназначен SQL), а объектами, организованными на более низком уровне (извините за путаница).

Поэтому это был наименее желательный вариант. — NOSQL .

Сначала меня очень заинтересовал этот вариант (в частности MongoDB).

После длительного использования реляционных систем поначалу начинаешь испытывать чувство полной свободы, от которого захватывает дух.

Хранить документы любой структуры, мгновенно создавать новые коллекции, запрашивать их – красота! Но после непродолжительного использования эйфория стала спадать, и начали появляться проблемы: — Плохой язык запросов (ИМХО) + отсутствие объединений; — Отсутствие схем (недавно была хорошая статья на эту тему (и не только на эту) habrahabr.ru/post/164361 ); — Отсутствие встроенной поддержки ссылочной целостности; — Отсутствие наворотов в виде хранимых процедур/функций, триггеров, представлений и многого другого.

— В моем приложении помимо данных с гибкой (изменяемой) структурой необходимо хранить и обычные статические данные — таблицу пользователей, посещений, сотрудников и т.д. Работать с которыми (опять же ИМХО) намного проще и( самое главное) надежнее в обычной реляционной базе данных (та же ссылочная целостность и т.п.

).

Первую проблему (частично) я пытался решить с помощью ORM (это был Spring Data), он позволял мне писать сносные запросы к объектам, но для этого мне нужно было создать и скомпилировать все классы (соответствующие нужным коллекциям) заранее и оперировать ими в запросах.

Меня это не устраивало, потому что.

коллекции нужно создавать и менять часто и быстро — на лету.

Второй — созданием отдельной коллекции для хранения структур всех остальных коллекций с целью проверки правильности введенных данных и т. д. До решения оставшихся проблем у меня не дошли руки, поэтому я сдался.

Уже на этом этапе моя база данных стала напоминать очень хрупкую структуру, полностью зависящую от приложения, плюс мне приходилось вручную реализовывать многие вещи, которые большинство реляционных систем могут делать «из коробки».

Может это и нормально, но я как-то не привыкла, как-то мне стало не по себе.

Тогда я задумался о том, как было бы здорово объединить реляционную и NOSQL СУБД.

С одной стороны — вся мощь реляционной системы со всем, что с ней связано, с другой — простота и элегантность документоориентированного решения.

Действительно, что мешает хранить объекты с гибкой структурой в какой-нибудь отдельной специальной таблице(ах), например в формате xml, и обращаться к ним с помощью XPATH, тем более, что во многих современных СУБД разработаны инструменты для работы с XML (в том числе и индексации).

Я решил попробовать это на небольшом примере с использованием Postgresql, чтобы посмотреть, что из этого получится и как будут выглядеть запросы: Для начала двух сервисных таблиц будет достаточно, комментарии думаю излишни:

  
  
  
   

CREATE TABLE classes ( id integer NOT NULL, name text, is_closed boolean, obects_count integer, CONSTRAINT classes_pk PRIMARY KEY (id ) ); CREATE TABLE objects ( id integer NOT NULL, body xml, id_classes integer, CONSTRAINT objects_pk PRIMARY KEY (id ), CONSTRAINT classes_objects FOREIGN KEY (id_classes) REFERENCES classes (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION ); CREATE INDEX fki_classes_objects ON objects USING btree (id_classes );

Создадим две сущности для экспериментов:

INSERT INTO classes( id, name, is_closed, obects_count) VALUES (1, 'customers', FALSE, 0); INSERT INTO classes( id, name, is_closed, obects_count) VALUES (2, 'orders', FALSE, 0);

Подготовим две функции для генерации тестовых случайных данных (взято из Интернета):

CREATE OR REPLACE FUNCTION random(numeric, numeric) RETURNS numeric AS $BODY$ SELECT ($1 + ($2 - $1) * random())::numeric; $BODY$ LANGUAGE sql VOLATILE COST 100; CREATE OR REPLACE FUNCTION random_string(length integer) RETURNS text AS $BODY$ declare chars text[] := '{0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}'; result text := ''; i integer := 0; begin if length < 0 then raise exception 'Given length cannot be less than 0'; end if; for i in 1.length loop result := result || chars[1+random()*(array_length(chars, 1)-1)]; end loop; return result; end; $BODY$ LANGUAGE plpgsql VOLATILE COST 100;

Заполнение таблицы случайными данными, объектами классов «Клиент» и «Заказ» (отношение «один ко многим», каждый клиент сделал пять заказов):

DO $$ DECLARE

Теги: #базы данных #NoSQL #postgresql #postgresql #sql #NoSQL
Вместе с данным постом часто просматривают: