Схема "звезда" в SQLAlchemy

Существуют многочисленные шаблоны проектирования баз данных. Они не часто приятно формализуются, таким образом, Вам, вероятно, придется просто посмотреть на большое проектирование баз данных.

Посмотрите, например, книги Fowler на шаблонах разработки. Также Книга .

Зарубки Там блоги, как программист баз данных .

существует книга IEEE, На Основанном на шаблоне Проектировании баз данных и Реализации .

Google Search ( ссылка ) поднятый 24M хиты.

9
задан Joakim Lundborg 16 September 2009 в 23:21
поделиться

1 ответ

Типичная таблица фактов в звездообразной схеме содержит ссылки внешнего ключа на все таблицы измерений, поэтому обычно нет необходимости в настраиваемых условиях соединения - они определяются автоматически из ссылок внешнего ключа.

Например, звездообразная схема с двумя таблицами фактов будет выглядеть так:

Base = declarative_meta()

class Store(Base):
    __tablename__ = 'store'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(50), nullable=False)

class Product(Base):
    __tablename__ = 'product'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(50), nullable=False)

class FactOne(Base):
    __tablename__ = 'sales_fact_one'

    store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
    product_id = Column('product_id', Integer, ForeignKey('product.id'), primary_key=True)
    units_sold = Column('units_sold', Integer, nullable=False)

    store = relation(Store)
    product = relation(Product)

class FactTwo(Base):
    __tablename__ = 'sales_fact_two'

    store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
    product_id = Column('product_id', Integer, ForeignKey('product.id'), primary_key=True)
    units_sold = Column('units_sold', Integer, nullable=False)

    store = relation(Store)
    product = relation(Product)

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

class Store(Base):
    __tablename__ = 'store'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(50), nullable=False)

    @classmethod
    def add_dimension(cls, target):
        target.store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
        target.store = relation(cls)

, и в этом случае использование будет примерно таким:

class FactOne(Base):
    ...

Store.add_dimension(FactOne)

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

meta = MetaData()
registry = {}
def register_cls(*cls):
    for c in cls:
        instrument_declarative(c, registry, meta)

Итак, тогда мы бы сделали что-то в этом направлении of:

class Store(object):
    # ...

class FactOne(object):
    __tablename__ = 'sales_fact_one'

Store.add_dimension(FactOne)

register_cls(Store, FactOne)

Если у вас действительно есть веская причина для использования настраиваемых условий соединения, при условии, что существует некоторый шаблон для создания этих условий, вы можете создать его с помощью add_dimension () :

class Store(object):
    ...

    @classmethod
    def add_dimension(cls, target):
        target.store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
        target.store = relation(cls, primaryjoin=target.store_id==cls.id)

Но последняя крутая вещь, если вы используете 2.6, - это превратить add_dimension в декоратор классов. Вот пример, когда все очищено:

from sqlalchemy import *
from sqlalchemy.ext.declarative import instrument_declarative
from sqlalchemy.orm import *

class BaseMeta(type):
    classes = set()
    def __init__(cls, classname, bases, dict_):
        klass = type.__init__(cls, classname, bases, dict_)
        if 'metadata' not in dict_:
            BaseMeta.classes.add(cls)
        return klass

class Base(object):
    __metaclass__ = BaseMeta
    metadata = MetaData()
    def __init__(self, **kw):
        for k in kw:
            setattr(self, k, kw[k])

    @classmethod
    def configure(cls, *klasses):
        registry = {}
        for c in BaseMeta.classes:
            instrument_declarative(c, registry, cls.metadata)

class Store(Base):
    __tablename__ = 'store'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(50), nullable=False)

    @classmethod
    def dimension(cls, target):
        target.store_id = Column('store_id', Integer, ForeignKey('store.id'), primary_key=True)
        target.store = relation(cls)
        return target

class Product(Base):
    __tablename__ = 'product'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(50), nullable=False)

    @classmethod
    def dimension(cls, target):
        target.product_id = Column('product_id', Integer, ForeignKey('product.id'), primary_key=True)
        target.product = relation(cls)
        return target

@Store.dimension
@Product.dimension
class FactOne(Base):
    __tablename__ = 'sales_fact_one'

    units_sold = Column('units_sold', Integer, nullable=False)

@Store.dimension
@Product.dimension
class FactTwo(Base):
    __tablename__ = 'sales_fact_two'

    units_sold = Column('units_sold', Integer, nullable=False)

Base.configure()

if __name__ == '__main__':
    engine = create_engine('sqlite://', echo=True)
    Base.metadata.create_all(engine)

    sess = sessionmaker(engine)()

    sess.add(FactOne(store=Store(name='s1'), product=Product(name='p1'), units_sold=27))
    sess.commit()
20
ответ дан 4 December 2019 в 10:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: