Frage

Ich habe eine Stern-Schema architectured Datenbank, die ich in SQLAlchemy darstellen will. Jetzt habe ich das Problem, wie kann dies auf die bestmögliche Art und Weise erfolgen. Im Moment habe ich eine Menge von Eigenschaften mit benutzerdefinierten Bedingungen join, da die Daten in verschiedenen Tabellen gespeichert. Es wäre schön, wenn es möglich sei, die Abmessungen für unterschiedliche Tatsache tablesw wieder zu verwenden, aber ich habe nicht herausgefunden, wie das schön gemacht werden kann.

War es hilfreich?

Lösung

Eine typische Faktentabelle in einem Sternschema Fremdschlüssel Verweise auf alle Maßtabellen enthält, so in der Regel gäbe es keine Notwendigkeit für individuelle Joinbedingungen -. Sie automatisch von Fremdschlüsselreferenzen bestimmt

Zum Beispiel eines Sternschema mit zwei Faktentabellen würde wie folgt aussehen:

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)

Aber wenn Sie den Textvorschlag auf jeden Fall reduzieren wollen. Ich würde Generatoren lokal zu den Dimensionsklassen schaffen, das sich auf einer Faktentabelle konfigurieren:

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)

In diesem Fall Nutzung wäre wie:

class FactOne(Base):
    ...

Store.add_dimension(FactOne)

Aber es gibt ein Problem. Unter der Annahme, die Dimensionsspalten Sie hinzufügen sind Primärschlüsselspalten wird die Mapper-Konfiguration zum Scheitern verurteilt, da eine Klasse ihrer Primärschlüssel muss eingerichtet haben, bevor die Abbildung oben gesetzt. So gehen wir davon aus deklarativen verwenden (die Sie unten sehen werden hat einen schönen Effekt), um diesen Ansatz Arbeit zu machen müssen wir die instrument_declarative() Funktion anstelle des Standard-Metaklasse verwenden würde:

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

Also dann würden wir etwas entlang der Linien von tun:

class Store(object):
    # ...

class FactOne(object):
    __tablename__ = 'sales_fact_one'

Store.add_dimension(FactOne)

register_cls(Store, FactOne)

Wenn Sie tatsächlich einen guten Grund für kundenspezifische Bedingungen beitreten, solange es gibt einige Muster, wie diese Voraussetzungen geschaffen sind, können Sie erzeugen, dass mit Ihrer 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)

Aber die endgültige coole Sache, wenn Sie auf 2.6 sind, dann ist add_dimension in eine Klasse Dekorateur zu drehen. Hier ist ein Beispiel mit allem aufgeräumt:

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()
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top