2009-09-16 12 views
7

Tengo una base de datos de arquitectura de esquema de estrellas que quiero representar en SQLAlchemy. Ahora tengo el problema de cómo se puede hacer de la mejor manera posible. En este momento tengo muchas propiedades con condiciones de combinación personalizadas, porque los datos se almacenan en diferentes tablas. Sería bueno si fuera posible reutilizar las dimensiones para diferentes tablas de hechos, pero no he descubierto cómo hacerlo bien.Esquema de estrella en SQLAlchemy

Respuesta

19

Una tabla de hechos típica en un esquema en estrella contiene referencias de claves externas a todas las tablas de dimensiones, por lo que normalmente no habría necesidad de condiciones de combinación personalizadas; se determinan automáticamente a partir de referencias de claves externas.

Por ejemplo, un esquema en estrella con dos tablas de hechos se vería así:

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) 

Pero supongamos que desea reducir el repetitivo en cualquier caso. Me gustaría crear generadores local para las clases de dimensiones que configuran a sí mismos en una tabla de hechos:

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) 

en el que el uso de caso sería como:

class FactOne(Base): 
    ... 

Store.add_dimension(FactOne) 

embargo, hay un problema con eso. Suponiendo que las columnas de dimensión que está agregando son columnas de clave primaria, la configuración del asignador va a fallar, ya que una clase necesita tener sus claves principales configuradas antes de que se establezca la asignación. Así que asumiendo que estamos usando declarativa (que se verá más adelante tiene un efecto agradable), para que este enfoque de trabajo que tendríamos que utilizar la función instrument_declarative() en lugar de la metaclase estándar:

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

Así entonces 'd hacer algo a lo largo de las líneas de:

class Store(object): 
    # ... 

class FactOne(object): 
    __tablename__ = 'sales_fact_one' 

Store.add_dimension(FactOne) 

register_cls(Store, FactOne) 

Si realmente tiene una buena razón para encargo condiciones de unión, siempre y cuando no hay un patrón de cómo se crean estas condiciones, puede generar que con su 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) 

Pero la última cosa interesante si estás en 2.6, es convertir add_dimension en un decorador de clases. Aquí hay un ejemplo con todo lo que se limpió:

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() 
+0

Very nice design - beautiful! –

+0

Inspirado por esto, finalmente descubrí cómo pasar la configuración a declaration_attr, para que las bibliotecas puedan conocer los modelos de aplicaciones de host: https://gist.github.com/miohtama/844cc78bcf1d317e31ca –