【问题标题】:Join all related tables in SQLAlchemy加入 SQLAlchemy 中的所有相关表
【发布时间】:2016-04-05 09:10:04
【问题描述】:

我有一个实验协议表,其中包括许多其他表的外键(最突出的是它包括许多 Incubation 条目)。结构看起来像这样,逐字逐句:

class DNAExtractionProtocol(Base):
    __tablename__ = 'dna_extraction_protocols'
    id = Column(Integer, primary_key=True)
    code = Column(String, unique=True)
    name = Column(String)
    sample_mass = Column(Float)
    mass_unit_id = Column(String, ForeignKey('measurement_units.id'))
    mass_unit = relationship("MeasurementUnit", foreign_keys=[mass_unit_id])
    digestion_buffer_id = Column(String, ForeignKey("solutions.id"))
    digestion_buffer = relationship("Solution", foreign_keys=[digestion_buffer_id])
    digestion_buffer_volume = Column(Float)
    digestion_id = Column(Integer, ForeignKey("incubations.id"))
    digestion = relationship("Incubation", foreign_keys=[digestion_id])
    lysis_buffer_id = Column(String, ForeignKey("solutions.id"))
    lysis_buffer = relationship("Solution", foreign_keys=[lysis_buffer_id])
    lysis_buffer_volume = Column(Float)
    lysis_id = Column(Integer, ForeignKey("incubations.id"))
    lysis = relationship("Incubation", foreign_keys=[lysis_id])
    proteinase_id = Column(String, ForeignKey("solutions.id"))
    proteinase = relationship("Solution", foreign_keys=[proteinase_id])
    proteinase_volume = Column(Float)
    inactivation_id = Column(Integer, ForeignKey("incubations.id"))
    inactivation = relationship("Incubation", foreign_keys=[inactivation_id])
    cooling_id = Column(Integer, ForeignKey("incubations.id"))
    cooling = relationship("Incubation", foreign_keys=[cooling_id])
    centrifugation_id = Column(Integer, ForeignKey("incubations.id"))
    centrifugation = relationship("Incubation", foreign_keys=[centrifugation_id])

    volume_unit_id = Column(String, ForeignKey('measurement_units.id'))
    volume_unit = relationship("MeasurementUnit", foreign_keys=[volume_unit_id])

现在,鉴于唯一的 code 属性,我想获得一个 Pandas 数据框(或者更确切地说是一个系列),它不仅允许我选择 "dna_extraction_protocols" 表中相应条目的任何属性,而且也在相关表格中。

我目前正在选择一个熊猫数据框:

sql_query = session.query(DNAExtractionProtocol).join(DNAExtractionProtocol.digestion_buffer).filter(DNAExtractionProtocol.code == code)
for item in sql_query:
    pass
mystring = str(sql_query)
mydf = pd.read_sql_query(mystring,engine,params=[code])
print(mydf)

但这仅允许我选择相关键的 ID。我可以选择mydf["dna_extraction_protocols_mass_unit_id"] - 但我也希望能够选择mydf["dna_extraction_protocols_mass_unit_long_name"],因为"measurement_units" 表上有以下可用键:

class MeasurementUnit(Base):
    __tablename__ = "measurement_units"
    id = Column(Integer, primary_key=True)
    code = Column(String, unique=True)
    long_name = Column(String)
    siunitx = Column(String)

【问题讨论】:

    标签: python sqlite pandas sqlalchemy


    【解决方案1】:

    此类问题的经典答案是创建 SQL VIEW。

    视图类似于动态虚拟表 - 在查询中,您使用视图名称而不是表名称,并且 DBMS 运行视图定义的查询来为视图上的查询生成行。因此,您会在访问视图时看到基于表中数据的行,而不是在创建视图时。

    您可以使用如下语句创建此视图

    CREATE VIEW PROT_WITH_UNITS AS
      SELECT * FROM dna_extraction_protocols P
               JOIN measurement_units M
                 ON P.volume_unit = M.id
    

    这将为您提供两个表的所有列的视图,预先加入(我认为是)所需的外键。

    如果定义错误,您可以像删除表一样删除视图,因此您最终应该会到达那里。

    【讨论】:

    • 谢谢,这已经是一大进步了 - 但measurement_units 表只是一个例子。我想加入我的条目链接到的所有表格 - 最好不必手动指定它们。这可能吗?
    • 看来这个查询有效:sql_query = session.query(tables[table]).options(eagerload(tables[table].digestion_buffer)).filter(tables[table].code == code) 但我怎样才能避免必须 secify 每个相关表?
    • 您在视图中的一个巨大的选择语句中指定所有必要的表。一次。如果更快的数据访问比灵活性更重要,您可以通过检索视图并将其结果存储在一个大表中来实现视图。
    【解决方案2】:

    我能遇到的最 Pythonic 的处理方法是基于 the response to a related question。似乎可以使用 SQLAlchemy 的introspect module。仍然存在的一个警告是,对于您想要加入的每一级相关表,您需要为最低检查调用添加一个嵌套的 for 循环。

    这是一个连接示例: * 主表的所有相关表 * 主表所有相关表的所有相关表:

    cols = []
    joins = []
    insp = inspection.inspect(DNAExtractionProtocol)
    for name, col in insp.columns.items():
        cols.append(col.label(name))
    for name, rel in insp.relationships.items():
        alias = aliased(rel.mapper.class_, name=name)
        joins.append((alias, rel.class_attribute))
        for col_name, col in inspection.inspect(rel.mapper).columns.items():
            #the id column causes double entries, as it is mapped once on the parent table (related_table_id) and once on the child table (table_id)
            if col.key != "id":
                aliased_col = getattr(alias, col.key)
                cols.append(aliased_col.label("{}_{}".format(name, col_name)))
    
        sub_insp = inspection.inspect(rel.mapper.class_)
        for sub_name, sub_rel in sub_insp.relationships.items():
            if "contains" not in sub_name:
                sub_alias = aliased(sub_rel.mapper.class_, name=name+"_"+sub_name)
                joins.append((sub_alias, sub_rel.class_attribute))
                for sub_col_name, sub_col in inspection.inspect(sub_rel.mapper).columns.items():
                    print(sub_alias, sub_col.key, '###')
                    #the id column causes double entries, as it is mapped once on the parent table (related_table_id) and once on the child table (table_id)
                    if sub_col.key != "id":
                        sub_aliased_col = getattr(sub_alias, sub_col.key)
                        cols.append(sub_aliased_col.label("{}_{}_{}".format(name, sub_name, sub_col_name)))
    
    sql_query = session.query(*cols).select_from(DNAExtractionProtocol)
    for join in joins:
        sql_query = sql_query.outerjoin(*join)
    sql_query = sql_query.filter(DNAExtractionProtocol.code == code)
    

    我不得不添加一个技巧来排除 ID 列,因为这会导致首选命名方案的列名重复 - 但如果改为编辑命名方案,这些列也可以保留。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-26
      • 2012-06-18
      • 1970-01-01
      • 1970-01-01
      • 2015-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多