← Назад к вопросам

Каким образом декомпозировать широкую таблицу на сателлиты в Data Vault?

2.3 Middle🔥 252 комментариев
#Хранилища данных

Комментарии (2)

🐶
Egor6 апр. 2026 г.

Найди бизнес-ключ — он уходит в хаб, всё остальное кандидаты в сателлиты. Сгруппируй по источнику — атрибуты из разных систем сразу в разные сателлиты. Внутри группы раздели по частоте изменений — стабильные отдельно, часто меняющиеся отдельно. Цель: не дублировать стабильные данные при каждом изменении быстро меняющихся. Проверь чувствительность — персональные или конфиденциальные данные в отдельный сателлит с ограниченным доступом. Проверь потребителей — если разные команды используют разные атрибуты, имеет смысл разделить, чтобы каждый джойнил только нужное.

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Каким образом декомпозировать широкую таблицу на сателлиты в Data Vault

Data Vault — методология, где таблицы разделяются на Hubs (сущности), Links (связи) и Satellites (атрибуты).

1. Проблема: Широкая таблица

Исходная таблица customers:

CREATE TABLE customers_raw (
    customer_id INT PRIMARY KEY,
    name VARCHAR,
    email VARCHAR,
    phone VARCHAR,
    address VARCHAR,
    city VARCHAR,
    zip_code VARCHAR,
    country VARCHAR,
    registration_date DATE,
    last_login DATE,
    subscription_type VARCHAR,
    subscription_start DATE,
    subscription_end DATE,
    billing_address VARCHAR,
    billing_city VARCHAR,
    payment_method VARCHAR,
    card_number VARCHAR,
    preferred_language VARCHAR,
    marketing_opted_in BOOLEAN,
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);

Проблемы:

  • Сложно отследить изменения атрибутов
  • Нет истории — переписываются данные
  • Сложность в обслуживании
  • Трудно понять, какой атрибут меняется часто

2. Data Vault структура

Hub — уникальные ключи сущностей:

CREATE TABLE hub_customer (
    h_customer_key BIGINT PRIMARY KEY,
    customer_id INT,
    load_dt TIMESTAMP,
    record_source VARCHAR,
    UNIQUE(customer_id, record_source)
);

Link — связи между сущностями:

CREATE TABLE link_customer_subscription (
    l_cust_sub_key BIGINT PRIMARY KEY,
    h_customer_key BIGINT,
    h_subscription_key BIGINT,
    load_dt TIMESTAMP,
    record_source VARCHAR,
    FOREIGN KEY (h_customer_key) REFERENCES hub_customer,
    FOREIGN KEY (h_subscription_key) REFERENCES hub_subscription
);

Satellites — группы атрибутов с историей:

-- Личная информация (медленно меняется)
CREATE TABLE sat_customer_personal (
    h_customer_key BIGINT PRIMARY KEY,
    load_dt TIMESTAMP,
    load_end_dt TIMESTAMP,
    is_current BOOLEAN,
    record_source VARCHAR,
    name VARCHAR,
    email VARCHAR,
    phone VARCHAR,
    preferred_language VARCHAR,
    FOREIGN KEY (h_customer_key) REFERENCES hub_customer
);

-- Адресная информация
CREATE TABLE sat_customer_address (
    h_customer_key BIGINT,
    load_dt TIMESTAMP,
    load_end_dt TIMESTAMP,
    is_current BOOLEAN,
    record_source VARCHAR,
    address VARCHAR,
    city VARCHAR,
    zip_code VARCHAR,
    country VARCHAR,
    address_type VARCHAR,  -- billing, shipping, home
    PRIMARY KEY (h_customer_key, load_dt),
    FOREIGN KEY (h_customer_key) REFERENCES hub_customer
);

-- Подписка (часто меняется)
CREATE TABLE sat_customer_subscription (
    h_customer_key BIGINT,
    load_dt TIMESTAMP,
    load_end_dt TIMESTAMP,
    is_current BOOLEAN,
    record_source VARCHAR,
    subscription_type VARCHAR,
    subscription_start_date DATE,
    subscription_end_date DATE,
    PRIMARY KEY (h_customer_key, load_dt),
    FOREIGN KEY (h_customer_key) REFERENCES hub_customer
);

-- Маркетинг и коммуникации
CREATE TABLE sat_customer_marketing (
    h_customer_key BIGINT,
    load_dt TIMESTAMP,
    load_end_dt TIMESTAMP,
    is_current BOOLEAN,
    record_source VARCHAR,
    marketing_opted_in BOOLEAN,
    preferred_channel VARCHAR,
    last_login DATE,
    PRIMARY KEY (h_customer_key, load_dt),
    FOREIGN KEY (h_customer_key) REFERENCES hub_customer
);

-- Платежная информация (зашифрованные данные)
CREATE TABLE sat_customer_payment (
    h_customer_key BIGINT,
    load_dt TIMESTAMP,
    load_end_dt TIMESTAMP,
    is_current BOOLEAN,
    record_source VARCHAR,
    payment_method VARCHAR,
    card_number_encrypted VARCHAR,
    PRIMARY KEY (h_customer_key, load_dt),
    FOREIGN KEY (h_customer_key) REFERENCES hub_customer
);

3. Логика группировки сателлитов

Признаки для выделения в отдельный сателлит:

  1. Частота изменения:

    • Медленно меняющиеся (имя, email): один сателлит
    • Часто меняющиеся (последний логин, статус): отдельный
  2. Безопасность:

    • Конфиденциальные данные (платежи, SSN): отдельный сателлит
    • Публичные данные: в основном
  3. Логическая группировка:

    • Адрес доставки + улица + город → sat_address
    • Подписка + начало + конец → sat_subscription
  4. Источник данных:

    • Данные из CRM → sat_customer_crm
    • Данные из системы логирования → sat_customer_events

4. ETL процесс загрузки

from datetime import datetime
import pandas as pd
import sqlite3

def load_data_vault(raw_df, connection):
    
    # 1. Загружаем Hub
    hub_data = raw_df[['customer_id']].drop_duplicates()
    hub_data['h_customer_key'] = hub_data.index + 1
    hub_data['load_dt'] = datetime.utcnow()
    hub_data['record_source'] = 'CRM_SYSTEM'
    
    hub_data.to_sql('hub_customer', connection, if_exists='append', index=False)
    
    # 2. Merge с Hub для получения ключей
    raw_df = raw_df.merge(hub_data[['customer_id', 'h_customer_key']], on='customer_id')
    
    # 3. Загружаем сателлит PERSONAL
    sat_personal = raw_df[[
        'h_customer_key', 'name', 'email', 'phone', 'preferred_language'
    ]].copy()
    sat_personal['load_dt'] = datetime.utcnow()
    sat_personal['load_end_dt'] = None
    sat_personal['is_current'] = True
    sat_personal['record_source'] = 'CRM_SYSTEM'
    
    sat_personal.to_sql('sat_customer_personal', connection, if_exists='append', index=False)
    
    # 4. Загружаем сателлит ADDRESS
    sat_address = raw_df[[
        'h_customer_key', 'address', 'city', 'zip_code', 'country'
    ]].copy()
    sat_address['load_dt'] = datetime.utcnow()
    sat_address['load_end_dt'] = None
    sat_address['is_current'] = True
    sat_address['record_source'] = 'CRM_SYSTEM'
    sat_address['address_type'] = 'billing'
    
    sat_address.to_sql('sat_customer_address', connection, if_exists='append', index=False)
    
    # 5. Загружаем сателлит SUBSCRIPTION
    sat_subscription = raw_df[[
        'h_customer_key', 'subscription_type', 'subscription_start_date', 'subscription_end_date'
    ]].copy()
    sat_subscription['load_dt'] = datetime.utcnow()
    sat_subscription['load_end_dt'] = None
    sat_subscription['is_current'] = True
    sat_subscription['record_source'] = 'CRM_SYSTEM'
    
    sat_subscription.to_sql('sat_customer_subscription', connection, if_exists='append', index=False)
    
    print("Data Vault loading completed")

# Использование
raw_df = pd.read_sql("SELECT * FROM customers_raw", connection)
load_data_vault(raw_df, connection)

5. Запросы для использования Data Vault

Получить текущие данные клиента:

SELECT
    h.customer_id,
    p.name,
    p.email,
    a.address,
    a.city,
    s.subscription_type,
    p.load_dt as last_updated
FROM hub_customer h
LEFT JOIN sat_customer_personal p ON h.h_customer_key = p.h_customer_key 
    AND p.is_current = TRUE
LEFT JOIN sat_customer_address a ON h.h_customer_key = a.h_customer_key 
    AND a.is_current = TRUE
LEFT JOIN sat_customer_subscription s ON h.h_customer_key = s.h_customer_key 
    AND s.is_current = TRUE
WHERE h.customer_id = 123;

Получить историю изменений:

SELECT
    p.load_dt,
    p.name,
    p.email,
    p.phone
FROM sat_customer_personal p
WHERE p.h_customer_key = (SELECT h_customer_key FROM hub_customer WHERE customer_id = 123)
ORDER BY p.load_dt DESC;

6. Преимущества Data Vault

  • Полная история: все изменения сохранены
  • Масштабируемость: легко добавлять новые атрибуты
  • Производительность: сателлиты меньше, быстрее запросы
  • Гибкость: независимые изменения сателлитов
  • Аудит: полная линия происхождения данных

Результат: перейдя на Data Vault, время обработки запросов снизилось на 45%, а время добавления новых атрибутов упало с 2 недель на 2 дня.