如何通过多个级别的SQLAlchemy关系检索一组对象?

时间:2019-05-10 04:20:57

标签: python sqlalchemy

在以下设置中,如何从所有汽车轮胎中检索一组SensorData?

一辆汽车有很多轮胎,而轮胎有一个SensorData,因此在SQL中,我会做类似

SELECT sd.* 
FROM Car AS c 
    INNER JOIN Tire AS t ON c.id = t.car_id 
    INNER JOIN SensorData AS sd ON t.id = sd.tire_id
class Car(Base):
    pass

class Tire(Base):
    car = relationship("Car", backref="tires")

class SensorData(Base):
    tire = relationship("Tire", backref=backref("sensor_data", uselist=False))

我猜想有一种比以下方法更优雅的方法来获取所有SensorData,但我找不到它。

tires = Car.query.get(1).tires
sensor_datas = [sensor_data for tire.sensor_data in tires]

有人可以建议一种更好的方法吗?我在想应该可以做类似的事情

sensor_datas = Car.query.get(1).sensor_datas

    OR

sensor_datas = Car.query.get(1).tires.sensor_data

1 个答案:

答案 0 :(得分:1)

这些解决方案将在sensor_datas上返回一个只读Car属性,有关详细信息,请参见注释:

class Car(Base):
    id = Column(Integer, primary_key=True)

    # results in a collection of sensor_data objects. This method is the 
    # cleanest reading by far, but behaves a little differently from the
    # other two in terms of how the collection is constructed. Accessing
    # the association proxy causes sqlalchemy to query the tire table for
    # for the `Car`'s `Tire`s and then iterates over each one, accessing
    # its `sensor_data` attribute to construct the collection on `Car`.
    # With the lazy loading defaults, this resulted in 5 queries to 
    # construct the relationship, so depending on how you need to use it
    # you might consider some sort of eager loading.
    sensor_datas_aprox = association_proxy('tires', 'sensor_data')

    # Joins Tire and SensorData. TBH the docs only mention
    # using secondary in context of m:n relationship, but 
    # this seems to work OK.
    sensor_datas_rel = relationship(
        'SensorData',
        secondary='join(Tire, SensorData)',
        viewonly=True
    )

    # a query enabled property, queries for the data on 
    # access, pretty self explanatory.
    @property
    def sensor_datas_prop(self):
        return object_session(self).query(SensorData).\
            join(Tire).filter(
                Tire.car_id == self.id,
                SensorData.tire_id == Tire.id
            ).all()


class Tire(Base):
    id = Column(Integer, primary_key=True)
    car_id = Column(Integer, ForeignKey('car.id'))
    car = relationship("Car", backref="tires")


class SensorData(Base):
    tire_id = Column(Integer, ForeignKey('tire.id'), primary_key=True)
    tire = relationship("Tire", backref=backref("sensor_data", uselist=False))


if __name__ == '__main__':
    s = Session()
    for _ in range(2):
        car = Car()
        car.tires = [Tire(sensor_data=SensorData()) for _ in range(4)]
        s.add(car)
    s.commit()
    for car in s.query(Car):
        print('*' * 100)
        print(car.sensor_datas_rel)
        print(car.sensor_datas_prop)
        print(car.sensor_datas_aprox)

输出:

****************************************************************************************************
[SensorData(tire_id=1), SensorData(tire_id=2), SensorData(tire_id=3), SensorData(tire_id=4)]
[SensorData(tire_id=1), SensorData(tire_id=2), SensorData(tire_id=3), SensorData(tire_id=4)]
[SensorData(tire_id=1), SensorData(tire_id=2), SensorData(tire_id=3), SensorData(tire_id=4)]
****************************************************************************************************
[SensorData(tire_id=5), SensorData(tire_id=6), SensorData(tire_id=7), SensorData(tire_id=8)]
[SensorData(tire_id=5), SensorData(tire_id=6), SensorData(tire_id=7), SensorData(tire_id=8)]
[SensorData(tire_id=5), SensorData(tire_id=6), SensorData(tire_id=7), SensorData(tire_id=8)]