【问题标题】:Renaming columns when querying with SQLAlchemy into Pandas DataFrame使用 SQLAlchemy 查询时将列重命名为 Pandas DataFrame
【发布时间】:2015-06-30 20:03:36
【问题描述】:

当您将数据查询到 pandas 数据框时,有没有办法保留 SqlAlchemy 属性名称?

这是我的数据库的简单映射。对于学校表,我已将数据库名称“SchoolDistrict”重命名为更短的“地区”。我从 DBA 中删除了几层,因此在源代码中更改它们是不可行的。

class School(Base):
    __tablename__ = 'DimSchool'

    id = Column('SchoolKey', Integer, primary_key=True)
    name = Column('SchoolName', String)
    district = Column('SchoolDistrict', String)


class StudentScore(Base):
    __tablename__ = 'FactStudentScore'

    SchoolKey = Column('SchoolKey', Integer, ForeignKey('DimSchool.SchoolKey'), primary_key = True)
    PointsPossible = Column('PointsPossible', Integer)
    PointsReceived = Column('PointsReceived', Integer)

    school = relationship("School", backref='studentscore')

所以当我查询类似:

query = session.query(StudentScore, School).join(School)
df = pd.read_sql(query.statement, query.session.bind)

在返回的 DataFrame df 中,我最终得到了列的底层“SchoolDistrict”名称,而不是我的属性名称。

编辑: 更烦人的情况是跨表存在重复的列名。例如:

class Teacher(Base):
    __tablename__ = 'DimTeacher'

    id = Column('TeacherKey', Integer, primary_key=True)
    fname = Column('FirstName', String)
    lname = Column('FirstName', String)

class Student(Base):
    __tablename__ = 'DimStudent'

    id = Column('StudentKey', Integer, primary_key=True)
    fname = Column('FirstName', String)
    lname = Column('FirstName', String)

因此,跨两个表(如下表)的查询会生成一个包含重复 FirstName 和 LastName 列的数据框。

query = session.query(StudentScore, Student, Teacher).join(Student).join(Teacher)

是否可以在查询时重命名这些列?现在,我对这两种列名系统感到头疼。

【问题讨论】:

  • 您好,您可以尝试在此SO answer 中执行select as,或者您可以在读取数据框后更改列的名称。希望对您有所帮助。
  • 嗯。所以 select 方法会起作用,但会迫使我远离更纯粹的 orm 方法。重命名列也会有点不方便,因为有很多变量名称不好和不明确。我真的在寻找一种在映射级别重命名并忘记实际名称的方法。
  • 我当然可能错了,但我认为不可能。 Pandas read_sql 正在使用 sqlalchemy 提供的 sql 语句,除非您更改语句,否则它将具有错误的名称(sqlalchemy 之后映射它,即在运行查询之后,在处理结果时) .在 pandas 中,没有办法像您建议的那样进行映射;如果您可以更改列的名称,为什么会有?祝你好运!
  • 该死。所以也许我需要建立一个所有列的字典,并在拉取数据框后将其全部映射......
  • 是的,我想这是最简单的方法。但请记住,您可以使用反射从 sqlalchemy 类中获取您的字典。你知道我的意思吗?

标签: python pandas sqlalchemy


【解决方案1】:

无论如何我都不是 SQLAlchemy 专家,但我想出了一个更通用的解决方案(或至少是一个开始)。

注意事项

  • 不会处理跨不同模型的具有相同名称的映射列。您应该通过添加后缀来处理这个问题,或者您可以在下面修改我的答案以将 pandas 列创建为<tablename/model name>.<mapper column name>

它涉及四个关键步骤:

  1. 使用标签限定查询语句,这将导致 pandas 中的列名称为 <table name>_<column name>
df = pd.read_sql(query.statement, query.session.bind).with_labels()
  1. 将表名与(实际)列名分开
table_name, col = col_name.split('_', 1)
  1. 根据表名获取模型(来自this question's answers
for c in Base._decl_class_registry.values():
            if hasattr(c, '__tablename__') and c.__tablename__ == tname:
                return c
  1. 找到正确的映射名称
for k, v in sa_class.__mapper__.columns.items():
        if v.name == col:
            return k

综合起来,这是我想出的解决方案,主要需要注意的是,如果您(可能)有重复的映射名称,它将导致 数据框中的列名重复类。

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class School(Base):
    __tablename__ = 'DimSchool'

    id = Column('SchoolKey', Integer, primary_key=True)
    name = Column('SchoolName', String)
    district = Column('SchoolDistrict', String)


class StudentScore(Base):
    __tablename__ = 'FactStudentScore'

    SchoolKey = Column('SchoolKey', Integer, ForeignKey('DimSchool.SchoolKey'), primary_key = True)
    PointsPossible = Column('PointsPossible', Integer)
    PointsReceived = Column('PointsReceived', Integer)

    school = relationship("School", backref='studentscore')


def mapped_col_name(col_name):
    ''' Retrieves mapped Model based on
    actual table name (as given in pandas.read_sql)
    '''

    def sa_class(table_name):
        for c in Base._decl_class_registry.values():
            if hasattr(c, '__tablename__') and c.__tablename__ == tname:
                return c

    table_name, col = col_name.split('_', 1)
    sa_class = sa_class(table_name)

    for k, v in sa_class.__mapper__.columns.items():
        if v.name == col:
            return k

query = session.query(StudentScore, School).join(School)
df = pd.read_sql(query.statement, query.session.bind).with_labels()
df.columns = map(mapped_col_name, df.columns)

【讨论】:

    【解决方案2】:

    如果我之后必须维护代码,我会非常抱怨这种解决方案。但是你的问题有很多限制,我找不到更好的。

    首先,您使用这样的自省构造一个具有模式和类列等价的字典(我使用的是您发布的第一个示例):

    In [132]:
    
    def add_to_dict(c_map, t_map, table):
        name = table.__tablename__
        t_map[name] = table.__name__
        #print name
        c_map[name] = {}
        for column in dir(table):
            c_schema_name = table.__mapper__.columns.get(column)
            if isinstance(c_schema_name, Column):
                #print column, c_schema_name.name
                c_map[name][c_schema_name.name] = column
    
    c_map = {}
    t_map = {}
    add_to_dict(c_map, t_map, School)
    add_to_dict(c_map, t_map, StudentScore)
    print c_map['DimSchool']['SchoolKey']
    print c_map['FactStudentScore']['SchoolKey']
    print t_map['DimSchool']
    id
    SchoolKey
    School
    

    [编辑:关于通过自省构建字典的方式的说明

    • c_map 是列名对应的字典
    • t_map 是表名对应字典
    • 需要为每个表的每个类调用
    • 表名的对应很容易,因为它只是表类的属性
    • 对于类的列名,第一次使用dir迭代类的属性
    • 对于类的每个属性(将是表的列,但也包括许多其他内容)尝试使用sqlalchemy mapper 获取数据库列名称
    • 仅当属性确实是列时,映射器才会返回 Column 对象
    • 因此对于Column 对象,将它们添加到列名字典中。数据库名用.name获取,另一个就是属性

    在数据库中创建所有对象后只运行一次,每个表类调用一次。]

    然后你使用你的 sql 语句并建立一个你将要获得的列的翻译列表:

    In [134]:
    
    df_columns = []
    for column in str(query.statement).split('FROM')[0].split('SELECT')[1].split(','):
        table = column.split('.')[0].replace('"', '').strip()
        c_schema = column.split('.')[1].replace('"', '').strip()
        df_columns += [t_map[table] + '.' + eq[table][c_schema]]
    print df_columns
    ​
    ['StudentScore.SchoolKey', 'StudentScore.PointsPossible', 'StudentScore.PointsReceived', 'School.id', 'School.name', 'School.district']
    

    最后,您阅读问题中的数据框并更改列的名称:

    In [137]:
    
    df.columns = df_columns
    In [138]:
    
    df
    Out[138]:
    StudentScore.SchoolKey  StudentScore.PointsPossible StudentScore.PointsReceived School.id   School.name School.district
    0   1   1   None    1   School1 None
    

    (数据只是我创建的一个愚蠢的寄存器)。

    希望对你有帮助!

    【讨论】:

    • 现在正在实施,但您能澄清一下 add_to_dict 函数的工作原理吗?作为一个“会痛苦地抱怨维护它”的家伙,我希望能够巩固我的理解:D
    猜你喜欢
    • 2019-12-01
    • 2023-01-20
    • 2019-07-21
    • 2015-10-28
    • 2021-04-24
    • 1970-01-01
    • 2020-07-29
    • 2017-03-05
    • 2013-11-19
    相关资源
    最近更新 更多