Горизонтальное хранение атрибутов на примере Oracle
При проектировании баз данных часто возникает вопрос: как лучше организовать хранение данных? Одним из ключевых решений является выбор между горизонтальным и вертикальным хранением данных. Оба подхода имеют свои особенности, преимущества и области применения.
Вертикальное хранение данных (Vertical Attribute Storage) предполагает, что каждый атрибут сущности представлен отдельным столбцом в таблице. Это традиционный подход, который используется в большинстве реляционных баз данных.
Горизонтальное хранение атрибутов (Horizontal Attribute Storage) — это подход к проектированию базы данных, при котором атрибуты сущности хранятся в виде строк в таблице, а не в виде отдельных столбцов. Этот метод часто используется для хранения динамических или пользовательских атрибутов, количество и структура которых заранее неизвестны. Его далее и рассмотрим на основе таблицы, содержащей информацию о местоположениях (LOCATION
).
Пример структуры таблицы
Рассмотрим следующую таблицу LOCATION_REFNUM
, которая используется для хранения атрибутов местоположений:
Название столбца | Тип данных | Обязательный | Описание |
---|---|---|---|
LOCATION_GID |
VARCHAR2(101 BYTE) |
Да | Уникальный идентификатор местоположения. |
LOCATION_REFNUM_QUAL_GID |
VARCHAR2(101 BYTE) |
Да | Глобальный идентификатор атрибута (например, тип атрибута). |
LOCATION_REFNUM_VALUE |
VARCHAR2(240 CHAR) |
Да | Значение атрибута. |
DOMAIN_NAME |
VARCHAR2(50 BYTE) |
Да (по умолчанию 'PUBLIC' ) |
Имя домена, к которому относится запись. |
INSERT_USER |
VARCHAR2(128 BYTE) |
Да | Пользователь, создавший запись. |
INSERT_DATE |
DATE |
Да | Дата создания записи. |
UPDATE_USER |
VARCHAR2(128 BYTE) |
Нет | Пользователь, обновивший запись. |
UPDATE_DATE |
DATE |
Нет | Дата последнего обновления записи. |
Пример данных
Предположим, у нас есть два местоположения: склад и офис. Для каждого из них мы хотим хранить рабочие часы и адрес (и не хотим эти данные заносить в предустановленные аттрибуты таблицы). Вставим данные в таблицу:
INSERT INTO LOCATION_REFNUM (LOCATION_GID, LOCATION_REFNUM_QUAL_GID, LOCATION_REFNUM_VALUE, DOMAIN_NAME, INSERT_USER, INSERT_DATE)
VALUES ('WAREHOUSE_001', 'WORKING_HOURS', '8:00-18:00', 'PUBLIC', 'admin', SYSDATE);
INSERT INTO LOCATION_REFNUM (LOCATION_GID, LOCATION_REFNUM_QUAL_GID, LOCATION_REFNUM_VALUE, DOMAIN_NAME, INSERT_USER, INSERT_DATE)
VALUES ('WAREHOUSE_001', 'ADDRESS', '123 Main St', 'PUBLIC', 'admin', SYSDATE);
INSERT INTO LOCATION_REFNUM (LOCATION_GID, LOCATION_REFNUM_QUAL_GID, LOCATION_REFNUM_VALUE, DOMAIN_NAME, INSERT_USER, INSERT_DATE)
VALUES ('WAREHOUSE_002', 'WORKING_HOURS', '9:00-17:00', 'PUBLIC', 'admin', SYSDATE);
Т.е. из данных имеем идентификатор исходного объекта (которому хотим добавить динамический аттрибут), идентификатор аттрибута (LOCATION_REFNUM_QUAL_GID
) и его значение (LOCATION_REFNUM_VALUE
).
Получение данных
Для получения всех атрибутов конкретного местоположения можно использовать запрос:
SELECT LOCATION_REFNUM_QUAL_GID, LOCATION_REFNUM_VALUE
FROM LOCATION_REFNUM
WHERE LOCATION_GID = 'WAREHOUSE_001';
Результат:
LOCATION_REFNUM_QUAL_GID | LOCATION_REFNUM_VALUE |
---|---|
WORKING_HOURS | 8:30-18:00 |
ADDRESS | ул. Ленина д.1 |
Преобразование в вертикальный вид
Но часто возникает необходимость преобразовать данные из горизонтального хранения в вертикальное (например, для отчетов),
Способ 1. Использовать оператор PIVOT
:
SELECT * FROM (
SELECT LOCATION_GID, LOCATION_REFNUM_QUAL_GID, LOCATION_REFNUM_VALUE
FROM LOCATION_REFNUM
)
PIVOT (
MAX(LOCATION_REFNUM_VALUE)
FOR LOCATION_REFNUM_QUAL_GID IN ('WORKING_HOURS' AS ATTRIBUTE_1, 'ADDRESS' AS ATTRIBUTE_2)
)
WHERE ATTRIBUTE_1 is not null;
Способ 2. Через группировку GROUP BY
select l.location_gid,
MAX(CASE WHEN lr.LOCATION_REFNUM_QUAL_GID = 'WORKING_HOURS' THEN lr.LOCATION_REFNUM_VALUE END) AS DELLIN_ADDRESSID,
MAX(CASE WHEN lr.LOCATION_REFNUM_QUAL_GID = 'ADDRESS' THEN lr.LOCATION_REFNUM_VALUE END) AS DELLIN_COUNTERAGENTID
FROM
location l
LEFT JOIN
location_refnum lr ON lr.location_gid = l.location_gid
AND lr.LOCATION_REFNUM_QUAL_GID IN ('WORKING_HOURS', 'ADDRESS')
GROUP BY
l.location_gid
Такой же запрос может быть использован при необходимости "разворота данных", когда необходимо получить из нескольких строк одну. Важно понимать, что если значений несколько, то может понадобиться дополнительная логика или аггрегация, чтобы свести несколько строк в одну.
Способ 3. Через подзапросы (думаю, что для больших массивов менее предпочтителен из соображений производительности)
SELECT
lr.LOCATION_GID,
(SELECT LOCATION_REFNUM_VALUE
FROM LOCATION_REFNUM
WHERE LOCATION_GID = lr.LOCATION_GID
AND LOCATION_REFNUM_QUAL_GID = 'WORKING_HOURS') AS WORKING_HOURS,
(SELECT LOCATION_REFNUM_VALUE
FROM LOCATION_REFNUM
WHERE LOCATION_GID = lr.LOCATION_GID
AND LOCATION_REFNUM_QUAL_GID = 'ADDRESS') AS ADDRESS
FROM
(SELECT DISTINCT LOCATION_GID FROM LOCATION_REFNUM) lr;
Результат:
LOCATION_GID | WORKING_HOURS | ADDRESS |
---|---|---|
WAREHOUSE_001 | 8:30-18:00 | ул. Ленина д.1 |
WAREHOUSE_002 | 9:00-17:00 | NULL |
Заключение
В Oracle ERP очень распространено применение горизонтального хранения атрибутов, такой подход позволяет эффективно управлять динамическими данными. Однако он может усложнить запросы и обработку данных, как видно, чтобы получить эти данные количество кода увеличивается по сравнению с подходом, если хранить аттрибуты в основной таблице.
Преимущества горизонтального хранения
- Гибкость: Легко добавлять новые атрибуты без изменения структуры таблицы.
- Масштабируемость: Подходит для систем с большим количеством динамических или пользовательских атрибутов.
- Универсальность: Один и тот же подход можно применять для разных типов данных.
Недостатки горизонтального хранения
- Сложность запросов: Для получения данных требуется большее количество SQL-запросов или сложные преобразования.
- Производительность: При большом объеме данных запросы могут выполняться медленнее из-за необходимости объединения строк.