LINUX.ORG.RU

Структура БД, union type

 ,


0

1

Задача:

есть таблица событий, в ней есть поле source.

В этом поле должна быть ссылка на объект, который зарегистрировал событие (далее. источник).

Источник может относится к одному из около 500 типов. Каждый тип — отдельная таблица.

Насколько я понимаю, в SQL нельзя сделать ссылку, которая содержала бы имя таблицы. Или можно?

Придумывается что-то вроде source_type int, source_id int, где source_type — номер таблицы и source_id — id в той таблице. Но 1) что делать с ссылочной целостностью (забить?) 2) как джойнить, если надо отфильтровать по полю регистратора?

У меня фильтрация получается что-то вроде

select e.* from events e
where (e.source_id, e.source_type) in
(select id, 1 from source1 where key_field = $1
union all
select id, 2 from source2 where key_field = $1
union all
....
union all
select id, 500 from source500 where key_field = $1)

Есть что-то проще? Или лучше для таких задач брать nosql?

★★★★★

Последнее исправление: monk (всего исправлений: 3)

Давно этим не занимался, но 500 таблиц для однообразных объектов как-то настораживают.

E ★★★
()
Ответ на: комментарий от E

500 таблиц для однообразных объектов как-то настораживают

Сейчас посмотрел: аналогичная проблема в любой бухгалтерской программе. У бухгалтерской записи есть документ-регистратор. Видов документов очень много, может не 500, но пара сотен есть.

Методы решения, которые видел:

  • денормализация: все виды документов в одной таблице. Полей у таблицы уйма, почти все null, так как общих полей не так много. Зато запросы нестрашные.
  • примерно то, что написал я. FOREIGN KEY нету, проверка ad hoc через регламентную операцию, при количестве видов более 256 MS SQL начинает дохнуть от «больше 256 таблиц в запросе»

Может есть более адекватный путь, но я его не нашёл?

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Что там за объекты, я не знаю, но наверняка их свойства можно как-то сгруппировать или вынести в дополнительные таблицы. Что-то вроде (int id, int property_type, string property_value). Это будет намного эффективнее, особенно когда свойства требуются только для редких отчётов. Минус: понадобится переорганизовать немного структуру таблиц. Плюс: расширяемое.

Можно создать промежуточную таблицу, в которой хранить объекты в виде (int table_id, int object_id) - table_id будет ссылаться на предопределённый (хардкод) индекс таблицы, а object_id - на запись в соответствующей таблицы. В такую таблицу-связку можно вынести какие-то общие свойства, которые требуются для таких глобальных выборок, а остальные хранить в мелких табличках (вроде тех, что уже есть). Минус: та же куча таблиц. Плюс: более организованный и эффективный доступ.

В любом случае, джойн или юнион 500 таблиц - плохой вариант. Нужно как-то объединять сущности.

E ★★★
()
Ответ на: комментарий от E

наверняка их свойства можно как-то сгруппировать или вынести в дополнительные таблицы.

Группировать свойства «по совпадению» не очень хорошая идея. Может быть, что у двух объектов есть поле Year, но в одном случая там должно быть DATE, а в другом — INT. Есть 3-4 поля, общие для всех, остальные все разные. С отбором по ним проблема и возникает.

Что-то вроде (int id, int property_type, string property_value).

key-value. Вот тогда точно лучше NoSQL. Тем более, что тип у этих полей не всегда строка (у большинство полей ссылка на другой объект, например). Где-то число, где-то REAL, где-то вообще BINARY. И как потом делать отбор по «between 3 and 353», например?

Можно создать промежуточную таблицу, в которой хранить объекты в виде (int table_id, int object_id) - table_id будет ссылаться на предопределённый (хардкод) индекс таблицы, а object_id - на запись в соответствующей таблицы. В такую таблицу-связку можно вынести какие-то общие свойства, которые требуются для таких глобальных выборок, а остальные хранить в мелких табличках (вроде тех, что уже есть). Минус: та же куча таблиц. Плюс: более организованный и эффективный доступ.

Более эффективный? На (почти всех) запросах +1 join. Разве что память экономится по сравнению с одной общей таблицей. Хотя, похоже, это действительно самый разумный вариант (вытащить в общую все поля, что встречаются более, чем в 200 таблицах). Остается всё тат же вопрос про ссылочную целостность: (int table_id, int object_id) — никаких ограничений не накладываем?

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

Более эффективный? На (почти всех) запросах +1 join.

Это однозначно лучше, чем 500 джоинов. Есть правило: если запросы частые - нужно кешировать, здесь и нормализацией можно пренебречь. Нужно смотреть по ходу дела, формальных правил нет. В любом случае, это уже детали реализации. Имеет смысл протестировать несколько вариантов на предмет скорости выполнения/эффективности работы с памятью. Даже простой EXPLAIN даст много полезной информации.

Остается всё тат же вопрос про ссылочную целостность: (int table_id, int object_id) — никаких ограничений не накладываем?

Да, в этом случае ключами невозможно управлять (за все СУБД говорить не буду хотя). Но они и не нужны - их всё равно придётся проверять программно. Если эти проверки делать как-то централизировано - проблем не будет.

Тем более, что тип у этих полей не всегда строка (у большинство полей ссылка на другой объект, например). Где-то число, где-то REAL, где-то вообще BINARY.

Я это обходил добавлением (помимо строкового значения) целочисленных (вещественных), блоб и ссылочных значений в таблицу свойств, во всех возможен NULL. Т.е. значение хранится только в одном столбце соответствующего типа. Плюсы: это очень расширяемо и полезно для больших проектов. Минусы: это довольно сложно в реализации (особенно ссылочные значения) и чересчур для узкоспециализированного проекта.

Может быть, что у двух объектов есть поле Year, но в одном случая там должно быть DATE, а в другом — INT.

Кстати, неэффективное решение. Все данные должны быть как можно более общего типа. Даже если требуется показывать только год или месяц целиком, имеет смысл хранить значение в формате даты (например, брать первое число для месяцев, первое января для лет). На производительности это сильно не скажется, зато расширяемо и более целостно. Управлять отображением проще, чем хранением разнородных данных.

E ★★★
()
Ответ на: комментарий от maxcom

В PostgreSQL есть oid и наследование таблиц

Specifying that another table's column REFERENCES cities(name) would allow the other table to contain city names, but not capital names.

То есть FOREIGN KEY на source_id не сработает. Кроме того, нельзя узнать из какой таблицы получена строка (чтобы получить остальные поля), значит всё равно делать source_type. Разве что на UNION'ах съэкономится (хотя, мне кажется, там под капотом тот же UNION ALL).

monk ★★★★★
() автор топика
Ответ на: комментарий от E

Есть правило: если запросы частые - нужно кешировать, здесь и нормализацией можно пренебречь. Нужно смотреть по ходу дела, формальных правил нет.

У меня обратная ситуация. Производительность скорее вторична, так как данных не ожидается очень много. А вот видов объектов ожидается много: пользователь может создавать новые. И у этих объектов поля могут ссылаться на другие виды объектов.

Кстати здесь проблема с общими полями: если я вынесу общие поля в таблице-журнале, то мне придётся во всех таблицах, где были ссылки на другие таблицы-источники также делать ссылку на общую таблицу с общими полями. Или в таблицах-источниках делать обратную ссылку на общую таблицу. В любом случае дополнительный JOIN во всех запросах.

И ещё одна проблема (почти глобальная): если какое-то поле стало общим не сразу, а уже где-то участвовало в запросах (сначала такое поле было у 50 объектов, потом его добавили ещё к паре сотен), то, получается, внезапно надо переделывать запросы... Хотя тут можно какой-нибудь препроцесор прикрутить, который будет смотреть на текст запроса и по необходимости дописывать общую таблицу и перед полями имя таблицы добавлять.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk
--типы объектов
CREATE TABLE T_OBJECT_TYPE
{
	ID	INTEGER PRIMARY KEY,
	TYPE	VARCHAR
};
--объекты
CREATE TABLE T_OBJECT
{
	ID	INTEGER PRIMARY KEY,
	ID_TYPE INTEGER NOT NULL,
	INFO    VARCHAR,
	
	FORIGN KEY (ID_TYPE) REFERENCES T_OBJECT_TYPE (ID)
};
--значение полей объекта, одна строка- одно значения поля объекта
CREATE TABLE T_OBJECT_VALUES
{
	ID	  INTEGER PRIMARY KEY,
	ID_OBJ	  INTEGER NOT NULL,
	PROP_NAME VARCHAR,
	INT_VALUE	INTEGER,
	DATE_VALUE	DATE,
	STRING_VALUE	VARCHAR,
	...... --прочие типы данных
	FORIGN KEY (ID_OBJ) REFERENCES T_OBJECT (ID)
}

Смысл в том, чтобы не плодить по 500 таблиц, или не создавать кучу полей в одной, а вынести значение свойст объекта в отдельную таблицу.

Связь с другими таблицами, банковскими документами там, по полю ID таблицы T_OBJECT.

looper
()
Ответ на: комментарий от looper

Смысл в том, чтобы не плодить по 500 таблиц, или не создавать кучу полей в одной, а вынести значение свойст объекта в отдельную таблицу.

А с блокировками хуже не будет от того, что одна большая таблица?

Или Postgres по разным ID_TYPE транзакции может независимо проводить?

monk ★★★★★
() автор топика
Ответ на: комментарий от looper

Блокировки на чтение?

На запись. Есть один пользователь активно пишущий/читающий объекты с типом 1 и другой пользователь активно пишущий/читающий объекты с типом 2.

Если типы 1 и 2 — разные таблицы, то они 100% друг-другу не мешают, а если это всего лишь WHERE (ID_TYPE = 1) в SELECT'е и поле в INSERT'е?

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

А с блокировками хуже не будет от того, что одна большая таблица?

Нет, хуже не будет. Проверено на реальном проекте с неслабой нагрузкой. Время вставки незначительно, время апдейта уменьшается, если есть индексы по часто запрашиваемым (id'шным, в частности) полям. Блокировки не так страшны, как кажутся. И это единственное верное решение в случае, когда:

А вот видов объектов ожидается много: пользователь может создавать новые. И у этих объектов поля могут ссылаться на другие виды объектов.

Таблицей на каждый объект здесь не обойдёшься. А если ожидается уж сильно большой хайлоад, то действительно лучше перейти на nosql.

E ★★★
()
Ответ на: комментарий от E

А если ожидается уж сильно большой хайлоад, то действительно лучше перейти на nosql.

Посмотрел по nosql. Там, похоже, join-ов нигде нет. То есть для примера из топика придётся или делать 500 запросов (по штуке на коллекцию) или мастерить свой обработчик запросов на сервере.

Время вставки незначительно, время апдейта уменьшается, если есть индексы по часто запрашиваемым (id'шным, в частности) полям. Блокировки не так страшны, как кажутся.

Если так, то действительно самое оптимальное решение.

monk ★★★★★
() автор топика

Есть так называемые аспекты (aspect). Можешь посмотреть на примере alfresco. Обычно для такого берут уже что-то готовое, а не изобретают велосипед - ту же альфреску.

vtVitus ★★★★★
()
Ответ на: комментарий от monk

сто лет назад, на первой моей работе мы писали бухгалтерию для ЖД(она очень специфическая). сейчас уже плохо помню детали, но документы хранились в одной таблице. использовался тип документа, его идентификатор, имя поля и его значение.

а потом мы вообще придумали загружать в базу dll-ку (дело было под вендой), которая обрабатывала данный документ и выводила все графические интерфейсы под него. софтина загружала библиотеки из базы и по типу документа спокойно обрабатывала все стопицот видов бухгалтерских бумажек. зато мы ничего не перекомпилировали, только дописывали новые формы для новых бумажек и подгружали данные в базу.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

а потом мы вообще придумали загружать в базу dll-ку (дело было под вендой), которая обрабатывала данный документ и выводила все графические интерфейсы под него

Вот примерно такая фигня и разрабатывается. Ну раз давным давно бухгалтерия на такой БД «документы хранились в одной таблице. использовался тип документа, его идентификатор, имя поля и его значение» работала достаточно быстро, значит подход действительно верный.

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

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

Еще можно все пары «имя поля и его значение» засунуть в BLOB. Тогда документ будет занимать 1 строку в таблице, но будет труднее доставать значения полей да и поиск по значению поля будет затруднителен.

andrewzvn
()
Ответ на: комментарий от monk

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

Iron_Bug ★★★★★
()
Ответ на: комментарий от andrewzvn

тут проблема именно в поиске и фильтрах. например, хочет тётка вытащить все авизовки за какой-то период... и тут блобы и сериализация плохо канают.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

тут проблема именно в поиске и фильтрах. например, хочет тётка вытащить все авизовки за какой-то период... и тут блобы и сериализация плохо канают.

Я б не сказал. Все-таки дата и тип нужны для всех документов и в блоб их сунуть смысла нет. Затруднительным будет поиск типа: выбрать все приходные накладные на сумму от 1.000.000.

andrewzvn
()
Ответ на: комментарий от andrewzvn

Еще можно все пары «имя поля и его значение» засунуть в BLOB.

А вот это уже лучше делать на NoSQL. Там этот BLOB имеет структуру и по нему возможны хоть какие-то запросы.

Затруднительным будет поиск типа: выбрать все приходные накладные на сумму от 1.000.000.

Вот вот. И даже типовой поиск: посчитать сумму проводок, где счет = 60, а в поле аналитика1 элемент таблицы контрагент и у этого контрагента город = Москва, группируя по контрагенту. Это как раз пример, где в SQL всё-таки будет один запрос, а в NoSQL — два запроса: первый — получить списко контрагентов с нужным условием (возможно большой), второй — выбрать данные (и в качестве параметра отправляем обратно тот же список).

monk ★★★★★
() автор топика
Ответ на: комментарий от monk

А вот это уже лучше делать на NoSQL. Там этот BLOB имеет структуру и по нему возможны хоть какие-то запросы.

Все зависит от специфики приложения. Можно использовать костыль в виде хранимой функции типа ReadBlob(Blob,Key). Тогда будет возможность использовать запросы и по блобу. Подобную схему встречал в украинской программе для налоговых и прочих отчетов. На небольших объемах данных (сотни видов отчетов и десятки тысяч строк) все работало довольно сносно, выборки с использованием блобов исполнялись за разумное время. Для подобного типа задач вполне жизнеспособный вариант.

andrewzvn
()

генерируй запросы

генерируй запросы для фильтра - это нормальная практика. Порядок работы такой:

-- по заданному фильтру определяешь все виды объектов, которые могут в него попасть

-- делаешь union join-ов с заданным перечнем объектов

Есть вероятность, что тебе никогда не попадётся более чем 256 таблиц. Если попадётся, вежливо посылаешь пользователя со словами «слишком сложный запрос».

Если может оказаться более 256 таблиц, то предлагаю ведение таблицы «id свойства,id объекта,значение свойства», вроде тебе уже предложили. Это позволит кардинально сократить количество таблиц. Для просмотра конкретного вида можешь нагенерировать представлений source1..source500 с триггерами на insert, update, delete - таким образом сможешь работать с каждым типом как таковым.

den73 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.