문제
Sqlalchemy에서 표현하고 싶은 Star-Schema Architectured 데이터베이스가 있습니다. 이제 나는 이것이 가능한 최선의 방법으로 어떻게 할 수 있는지에 대한 문제가 있습니다. 데이터는 다른 테이블에 저장되므로 사용자 정의 조인 조건이 포함 된 많은 속성이 있습니다. 다른 사실 테이블의 차원을 재사용 할 수 있다면 좋을 것입니다.
해결책
스타 스키마의 일반적인 사실 테이블에는 모든 차원 테이블에 대한 외국 키 참조가 포함되어 있으므로 일반적으로 사용자 지정 조인 조건이 필요하지 않습니다. 이는 외국 키 참조에서 자동으로 결정됩니다.
예를 들어 두 개의 사실 테이블이있는 스타 스키마는 다음과 같습니다.
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()