简体   繁体   English

sqlalchemy:union从具有条件的多个表中查询几列

[英]sqlalchemy: union query few columns from multiple tables with condition

I'm trying to adapt some part of a MySQLdb application to sqlalchemy in declarative base. 我正在尝试将MySQLdb应用程序的某些部分调整为声明式基础中的sqlalchemy。 I'm only beginning with sqlalchemy. 我只是从sqlalchemy开始。

The legacy tables are defined something like: 遗留表的定义如下:

student: id_number*, semester*, stateid, condition, ...
choice: id_number*, semester*, choice_id, school, program, ...

We have 3 tables for each of them ( student_tmp , student_year , student_summer , choice_tmp , choice_year , choice_summer ), so each pair ( _tmp , _year , _summer ) contains information for a specific moment. 我们每个都有3个表( student_tmpstudent_yearstudent_summerchoice_tmpchoice_yearchoice_summer ),因此每对( _tmp_year_summer )包含特定时刻的信息。

select *
from `student_tmp`
    inner join `choice_tmp` using (`id_number`, `semester`)

My problem is the information that is important to me is to get the equivalent of the following select: 我的问题是,对我来说重要的信息是获得以下选择的等价物:

SELECT t.*
FROM (
        (
            SELECT st.*, ct.*
            FROM `student_tmp` AS st
                INNER JOIN `choice_tmp` as ct USING (`id_number`, `semester`)
            WHERE (ct.`choice_id` = IF(right(ct.`semester`, 1)='1', '3', '4'))
                AND (st.`condition` = 'A')
        ) UNION (
            SELECT sy.*, cy.*
            FROM `student_year` AS sy
                INNER JOIN `choice_year` as cy USING (`id_number`, `semester`)
            WHERE (cy.`choice_id` = 4)
                AND (sy.`condition` = 'A')
        ) UNION (
            SELECT ss.*, cs.*
            FROM `student_summer` AS ss
                INNER JOIN `choice_summer` as cs USING (`id_number`, `semester`)
            WHERE (cs.`choice_id` = 3)
                AND (ss.`condition` = 'A')
        )
    ) as t

* used for shorten the select, but I'm actually only querying for about 7 columns out of the 50 availables. *用于缩短选择,但我实际上只查询50个可用的大约7列。

This information is used in many flavors... "Do I have new students? Do I still have all students from a given date? Which students are subscribed after the given date? etc..." The result of this select statement is to be saved in another database. 这些信息用于多种口味......“我有新学生吗?我是否仍然拥有特定日期的所有学生?在给定日期之后订阅哪些学生?等等...”此选择声明的结果是保存在另一个数据库中。

Would it be possible for me to achieve this with a single view-like class? 我是否有可能通过一个类似视图的类来实现这一目标? The information is read-only so I don't need to be able to modify/create/delte. 信息是只读的,因此我不需要能够修改/创建/删除。 Or do I have to declare a class for each table (ending up with 6 classes) and every time I need to query, I have to remember to filter? 或者我必须为每个表声明一个类(最后有6个类),每次我需要查询时,我必须记得过滤?

Thanks for pointers. 谢谢你的指点。

EDIT : I don't have modification access to the database (I cannot create a view). 编辑 :我没有对数据库的修改访问权限(我无法创建视图)。 Both databases may not be on the same server (so I cannot create a view on my second DB). 两个数据库可能不在同一台服务器上(因此我无法在第二个数据库上创建视图)。

My concern is to avoid the full table scan before filtering on condition and choice_id . 我担心的是在过滤conditionchoice_id之前避免全表扫描。

EDIT 2 : I've set up declarative classes like this: 编辑2 :我已经设置了这样的声明性类:

class BaseStudent(object):
    id_number = sqlalchemy.Column(sqlalchemy.String(7), primary_key=True)
    semester = sqlalchemy.Column(sqlalchemy.String(5), primary_key=True)
    unique_id_number = sqlalchemy.Column(sqlalchemy.String(7))
    stateid = sqlalchemy.Column(sqlalchemy.String(12))
    condition = sqlalchemy.Column(sqlalchemy.String(3))

class Student(BaseStudent, Base):
    __tablename__ = 'student'

    choices = orm.relationship('Choice', backref='student')

#class StudentYear(BaseStudent, Base):...
#class StudentSummer(BaseStudent, Base):...

class BaseChoice(object):
    id_number = sqlalchemy.Column(sqlalchemy.String(7), primary_key=True)
    semester = sqlalchemy.Column(sqlalchemy.String(5), primary_key=True)
    choice_id = sqlalchemy.Column(sqlalchemy.String(1))
    school = sqlalchemy.Column(sqlalchemy.String(2))
    program = sqlalchemy.Column(sqlalchemy.String(5))


class Choice(BaseChoice, Base):
    __tablename__ = 'choice'

    __table_args__ = (
            sqlalchemy.ForeignKeyConstraint(['id_number', 'semester',],
                [Student.id_number, Student.semester,]),
            )

#class ChoiceYear(BaseChoice, Base): ...
#class ChoiceSummer(BaseChoice, Base): ...

Now, the query that gives me correct SQL for one set of table is: 现在,为一组表提供正确SQL的查询是:

q = session.query(StudentYear, ChoiceYear) \
            .select_from(StudentYear) \
            .join(ChoiceYear) \
            .filter(StudentYear.condition=='A') \
            .filter(ChoiceYear.choice_id=='4')

but it throws an exception... 但它引发了一个例外......

"Could not locate column in row for column '%s'" % key)
sqlalchemy.exc.NoSuchColumnError: "Could not locate column in row for column '*'"

How do I use that query to create myself a class I can use? 如何使用该查询创建自己可以使用的类?

If you can create this view on the database, then you simply map the view as if it was a table. 如果可以在数据库上创建此视图,则只需将视图映射为表格。 See Reflecting Views . 请参见反映视图

# DB VIEW
CREATE VIEW my_view AS -- @todo: your select statements here

# SA
my_view = Table('my_view', metadata, autoload=True)
# define view object
class ViewObject(object):
    def __repr__(self):
        return "ViewObject %s" % str((self.id_number, self.semester,))
# map the view to the object
view_mapper = mapper(ViewObject, my_view)

# query the view
q = session.query(ViewObject)
for _ in q:
    print _

If you cannot create a VIEW on the database level, you could create a selectable and map the ViewObject to it. 如果无法在数据库级别创建VIEW ,则可以创建一个可选择的ViewObject映射到它。 The code below should give you the idea: 下面的代码应该给你的想法:

student_tmp = Table('student_tmp', metadata, autoload=True)
choice_tmp = Table('choice_tmp', metadata, autoload=True)
# your SELECT part with the columns you need
qry = select([student_tmp.c.id_number, student_tmp.c.semester, student_tmp.stateid, choice_tmp.school])
# your INNER JOIN condition
qry = qry.where(student_tmp.c.id_number == choice_tmp.c.id_number).where(student_tmp.c.semester == choice_tmp.c.semester)
# other WHERE clauses
qry = qry.where(student_tmp.c.condition == 'A')

You can create 3 queries like this, then combine them with union_all and use the resulting query in the mapper: 您可以创建3个这样的查询,然后将它们与union_all结合使用,并在mapper中使用生成的查询:

view_mapper = mapper(ViewObject, my_combined_qry)

In both cases you have to ensure though that a PrimaryKey is properly defined on the view, and you might need to override the autoloaded view, and specify the primary key explicitely (see the link above). 在这两种情况下,您必须确保在视图上正确定义了PrimaryKey,并且您可能需要override自动加载的视图,并明确指定主键(请参阅上面的链接)。 Otherwise you will either receive an error, or might not get proper results from the query. 否则,您将收到错误,或者可能无法从查询中获得正确的结果。


Answer to EDIT-2: 回答EDIT-2:

qry = (session.query(StudentYear, ChoiceYear).
        select_from(StudentYear).
        join(ChoiceYear).
        filter(StudentYear.condition == 'A').
        filter(ChoiceYear.choice_id == '4')
        )

The result will be tuple pairs: (Student, Choice) . 结果将是元组对:( (Student, Choice)
But if you want to create a new mapped class for the query, then you can create a selectable as the sample above: 但是,如果要为查询创建新的映射类,则可以创建一个可选的上述示例:

student_tmp = StudentTmp.__table__
choice_tmp = ChoiceTmp.__table__
.... (see sample code above)

This is to show what I ended up doing, any comment welcomed. 这是为了表明我最终做了什么,欢迎任何评论。


class JoinedYear(Base):
    __table__ = sqlalchemy.select(
            [
                StudentYear.id_number,
                StudentYear.semester,
                StudentYear.stateid,
                ChoiceYear.school,
                ChoiceYear.program,
                ],
                from_obj=StudentYear.__table__.join(ChoiceYear.__table__),
                ) \
                .where(StudentYear.condition == 'A') \
                .where(ChoiceYear.choice_id == '4') \
                .alias('YearView')

and I will elaborate from there... 我会从那里详细说明......

Thanks @van 谢谢@van

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM