Pergunta

Eu tenho uma estrela-do esquema arquitetado banco de dados que eu quero representar no SQLAlchemy. Agora eu tenho o problema de como isso pode ser feito da melhor maneira possível. Agora eu tenho um monte de propriedades com costume condições de junção, porque os dados são armazenados em tabelas diferentes. Seria bom se fosse possível voltar a usar as dimensões diferente fato tablesw mas eu ainda não descobri como isso pode ser feito muito bem.

Foi útil?

Solução

A tabela de fato típico em um esquema em estrela contém referências chave estrangeira para todas as tabelas de dimensões, de modo geral, não haveria qualquer necessidade de costume condições de junção -. Eles são determinados automaticamente a partir de referências de chave estrangeira

Por exemplo, um esquema em estrela com duas tabelas de fatos seria parecido com:

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)

Mas suponha que você queira reduzir o clichê em qualquer caso. Eu criar geradores locais para as classes de dimensão, que se configuram em uma tabela de fatos:

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)

Nesse caso, o uso seria como:

class FactOne(Base):
    ...

Store.add_dimension(FactOne)

Mas, há um problema com isso. Assumindo que as colunas de dimensão que você está adicionando são colunas de chave primária, a configuração do mapeador vai falhar desde uma classe precisa ter suas chaves primárias configurar antes do mapeamento é configurado. Assim, supondo que estamos usando declarativa (que você verá abaixo tem um efeito agradável), para fazer este trabalho abordagem que teria que usar a função instrument_declarative() vez do metaclass padrão:

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

Então nós faria algo ao longo das linhas de:

class Store(object):
    # ...

class FactOne(object):
    __tablename__ = 'sales_fact_one'

Store.add_dimension(FactOne)

register_cls(Store, FactOne)

Se você realmente tem uma boa razão para o costume de condições de junção, enquanto há algum padrão de como essas condições estão criadas, você pode gerar isso com o seu 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)

Mas a coisa legal final, se você está em 2,6, é transformar add_dimension em um decorador de classe. Aqui está um exemplo com tudo limpo:

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()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top