使用SQLAlchemy在Pyramid中创建一个包含很少相关表的查询

时间:2014-08-27 14:14:47

标签: python sql postgresql sqlalchemy pyramid

我在Pyramid中定义了几个表:

# coding: utf-8
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Integer, Float, DateTime, ForeignKey, ForeignKeyConstraint, String, Column
from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref,
from zope.sqlalchemy import ZopeTransactionExtension

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()


class Codes(Base):
    __tablename__ = 'Code'
    __table_args__ = {u'schema': 'Locations'}

    id = Column(Integer, nullable=False)
    code_str = Column(String(9), primary_key=True)
    name = Column(String(100))

    incoming = relationship(u'Voyages', primaryjoin='Voyage.call == Codes.code_str', backref=backref('Code'))


class Locations(Base):
    __tablename__ = 'Location'
    __table_args__ = {u'schema': 'Locations'}

    unit_id = Column(ForeignKey(u'Structure.Definition.unit_id', ondelete=u'RESTRICT', onupdate=u'CASCADE'), primary_key=True, nullable=False)
    timestamp = Column(DateTime, primary_key=True, nullable=False)
    longitude = Column(Float)
    latitude = Column(Float)


class Voyages(Base):
    __tablename__ = 'Voyage'
    __table_args__ = (ForeignKeyConstraint(['unit_id', 'Voyage_id'], [u'Locations.Voyages.unit_id', u'Locations.Voyages.voyage_id'], ondelete=u'RESTRICT', onupdate=u'CASCADE'), {u'schema': 'Locations'}
    )

    uid = Column(Integer, primary_key=True)
    unit_id = Column(Integer)
    voyage_id = Column(Integer)
    departure = Column(ForeignKey(u'Locations.Code.code_str', ondelete=u'RESTRICT', onupdate=u'CASCADE'))
    call = Column(ForeignKey(u'Locations.Code.code_str', ondelete=u'RESTRICT', onupdate=u'CASCADE'))
    departure_date = Column(DateTime)

    voyage_departure = relationship(u'Codes', primaryjoin='Voyage.departure == Codes.code_str')
    voyage_call = relationship(u'Codes', primaryjoin='Voyage.call == Codes.code_str')


class Definitions(Base):
    __tablename__ = 'Definition'
    __table_args__ = {u'schema': 'Structure'}

    unit_id = Column(Integer, primary_key=True)
    name = Column(String(90))
    type = Column(ForeignKey(u'Structure.Type.id', ondelete=u'RESTRICT', onupdate=u'CASCADE'))

    locations = relationship(u'Locations', backref=backref('Definition'))
    dimensions = relationship(u'Dimensions', backref=backref('Definition'))
    types = relationship(u'Types', backref=backref('Definition'))
    voyages = relationship(u'Voyages', backref=backref('Definition'))


class Dimensions(Base):
    __tablename__ = 'Dimension'
    __table_args__ = {u'schema': 'Structure'}

    unit_id = Column(ForeignKey(u'Structure.Definition.unit_id', ondelete=u'RESTRICT', onupdate=u'CASCADE'), primary_key=True, nullable=False)
    length = Column(Float)


class Types(Base):
    __tablename__ = 'Type'
    __table_args__ = {u'schema': 'Structure'}

    id = Column(SmallInteger, primary_key=True)
    type_name = Column(String(255))
    type_description = Column(String(255))

我在这里要做的是从Codes表中找到一个特定的行(按code_str过滤它)并获得所有相关的表,但条件是{{1} } table仅返回Location的最后一行,timestamp表必须仅返回Voyage的最后一行,并且它必须包含来自departure表的所有信息。

我开始从头开始创建一个查询,并遇到这样的事情:

Definitions

但是我注意到当我遍历我的结果(比如string_to_search = request.matchdict.get('code') sub_dest = DBSession.query(func.max(Voyage.departure).label('latest_voyage_timestamp'), Voyage.unit_id, Voyage.call.label('destination_call')).\ filter(Voyage.call== string_to_search).\ group_by(Voyage.unit_id, Voyage.call).\ subquery() query = DBSession.query(Codes, Voyage).\ join(sub_dest, sub_dest.c.destination_call == Codes.code_str).\ outerjoin(Voyage, sub_dest.c.latest_voyage_timestamp == Voyage.departure_date) )时,我实际上正在迭代我得到的每个for code, voyage in query。从理论上讲,这对我来说不是一个大问题,但我试图用Voyage表中的基本信息构建一些json响应,其中包括所有可能的Codes(如果有的话)。 例如:

Voyages

现在,这似乎有点不对,因为我不喜欢在每个循环中重写这个code_data = {} all_units = [] for code, voyage in query: if code_data is not {}: code_data = { 'code_id': code.id, 'code_str': code.code_str, 'code_name': code.name, } single_unit = { 'unit_id': voyage.unit_id, 'unit_departure': str(voyage.departure_date) if voyage.departure_date else None, } all_units.append(single_unit) return { 'code_data': exception.message if exception else code_data, 'voyages': exception.message if exception else all_units, } ,所以我把code_data行放在这里,但我想它会更好(逻辑)到以类似于此的方式迭代:

if code_data is not {}

所以,要获得唯一的for code in query: code_data = { 'code_id': code.id, 'code_str': code.code_str, 'code_name': code.name, } for voyage in code.voyages: single_unit = { 'unit_id': voyage.unit_id, 'unit_departure': str(voyage.departure) if voyage.departure else None, } all_units.append(single_unit) return { 'code_data': exception.message if exception else code_data, } 作为回报(因为我查询了该特定Code的数据库),然后将所有Code作为嵌套值与之相关,并且当然,在每个Voyages中,与Voyage的{​​{1}}相关的所有其他信息......

我的方法一开始是不是很好,我怎样才能构建我的查询以便以第二种方式迭代它?

我在Postgres数据库中使用Python 2.7.6,SQLAlchemy 0.9.7和Pyramid 1.5.1。

谢谢!

1 个答案:

答案 0 :(得分:1)

尝试更改外部查询,如下所示:

query = DBSession.query(Codes).options(contains_eager('incoming')).\
    join(sub_dest, sub_dest.c.destination_call == Codes.code_str).\
    outerjoin(Voyage, sub_dest.c.latest_voyage_timestamp == Voyage.departure_date)

如果出现问题,请尝试调用选项(...)部分:

(...) .options(contains_eager(Codes.incoming)). (...)

这会导致返回单个Codes实例,并且Voyages个对象可通过您定义的关系(incoming)访问,因此您可以继续:

results = query.all()
for code in results:
    print code 
    # do something with code.incoming
    # actually, you should get only one code so if it proves to work, you should 
    # use query.one() so that in case something else than a single Code is returned,
    # an exception is thrown

当然你需要导入,例如:from sqlalchemy.orm import contains_eager