【问题标题】:Star schema in SQLAlchemySQLAlchemy 中的星型模式
【发布时间】:2009-09-16 23:21:00
【问题描述】:

我有一个要在 SQLAlchemy 中表示的星型架构数据库。现在我有一个问题是如何以最好的方式做到这一点。现在我有很多带有自定义连接条件的属性,因为数据存储在不同的表中。 如果可以为不同的事实表重新使用维度,那就太好了,但我还没有弄清楚如何才能很好地做到这一点。

【问题讨论】:

    标签: sqlalchemy star-schema


    【解决方案1】:

    星型模式中的典型事实表包含对所有维度表的外键引用,因此通常不需要自定义连接条件 - 它们是根据外键引用自动确定的。

    例如,具有两个事实表的星型模式如下所示:

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

    【讨论】:

    • 非常漂亮的设计 - 漂亮!
    • 受此启发,我终于想出了如何将配置传递给declared_attr,以便库可以了解宿主应用程序模型:gist.github.com/miohtama/844cc78bcf1d317e31ca
    • 这看起来很优雅,但现在已经 10 年了,我使用的是 python 3.6,它对我不起作用。我将 BaseMeta 的 init 函数更改为具有 super().__init__(classname, bases, dict_) 以使其与 python 3.6 兼容,但我显然缺少一些东西,因为我收到错误:sqlalchemy.exc.InvalidRequestError:不知道如何从场景加入;请使用 select_from() 建立此连接的左侧实体/可选择
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-08
    • 2012-05-15
    • 2013-08-04
    相关资源
    最近更新 更多