質問

SQLAlchemyで表現したいスタースキーマアーキテクチャデータベースがあります。今、私はこれを最善の方法でどのように行うことができるかという問題を抱えています。データは異なるテーブルに保存されるため、現在、カスタム結合条件を持つ多くのプロパティがあります。 さまざまなファクトテーブルのディメンションを再利用できるといいのですが、どうすればそれがうまくできるのかわかりません。

役に立ちましたか?

解決

スタースキーマの一般的なファクトテーブルには、すべてのディメンションテーブルへの外部キー参照が含まれているため、通常、カスタム結合条件は必要ありません。外部キー参照から自動的に決定されます。

たとえば、2つのファクトテーブルを持つスタースキーマは次のようになります。

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)

それで、次の行に沿って何かをします:

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()
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top