如何配置三个表之间的关系?
How to configure relationships between three tables?
我目前有以下三个table:
class Match(Base):
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
tournament_id = Column(Integer, ForeignKey("myschema.tournament.id_"))
class Tournament(Base):
id_ = Column(Integer, primary_key=True)
latitude = Column(Float)
longitude = Column(Float)
match = relationship("Match", backref="tournament")
class Weather(Base):
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
latitude = Column(Float)
longitude = Column(Float)
conditions = Column(String(50))
我正在寻找一个查询来获取 Match
table 中每场比赛的天气状况。像这样:
qry = session.query(Match.id_, Weather.conditions)
qry = qry.select_from(Match)
qry = qry.join(Tournament, Weather)
qry = qry.all()
Weather
中的唯一键是 date_time
、latitude
和 longitude
的组合,其中:
date_time
需要连接到 Match
中的等价物(多对多)
latitude
需要连接到 Tournament
中的等价物(多对多)
longitude
需要连接到 Tournament
中的等价物(多对多)
我可以接受外键上的简单关系,例如Match
和 Tournament
的那个,但我在试图找出更复杂的东西时绝望地迷路了。
我希望以上内容是不言自明的 - 如果需要数据,请告诉我,我会添加一些。
我正在使用 SQLAlchemy v1.3。
更新:
我一直在尝试根据指南创建关系 here:
class Match(Base):
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
tournament_id = Column(Integer, ForeignKey("myschema.tournament.id_"))
weather = relationship(
"Weather", primaryjoin="Match.date_time == Weather.date_time"
)
class Tournament(Base):
id_ = Column(Integer, primary_key=True)
latitude = Column(Float)
longitude = Column(Float)
match = relationship("Match", backref="tournament")
weather = relationship(
"Weather",
primaryjoin="and_(Tournament.latitude == Weather.latitude, " +
"Tournament.longitude == Weather.longitude)"
)
class Weather(Base):
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
latitude = Column(Float)
longitude = Column(Float)
conditions = Column(String(50))
但是,当我 运行 来自上面的查询时,我得到错误:
Don't know how to join to <class 'container.Weather'>. Please use the .select_from() method to establish an explicit left side, as well as providing an explicit ON clause if not present already to help resolve the ambiguity.
我哪里错了?
选项-1:普通查询
实际上,它与编写普通 SQL 查询非常相似:
qry = (
session.query(Match.id_, Weather.conditions)
.select_from(Match)
.join(Tournament)
.join(
Weather,
and_(
Match.date_time == Weather.date_time,
Tournament.latitude == Weather.latitude,
Tournament.longitude == Weather.longitude,
),
)
).all()
选项-2:计算属性
在澄清您想建立“关系”之后,我实际上认为使用 column_property
而不是从 Match 到 Weather 的 relationship
看起来更有效:
class Match(Base):
__tablename__ = "match"
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
tournament_id = Column(Integer, ForeignKey("tournament.id_"))
# NOTE: Weather and Tournament should be defined earlier to use the expressions below. Otherwise, a stringified definition could be used instead
weather_conditions = column_property(
select([Weather.conditions.label("match_weather_conditions")])
.where(tournament_id == Tournament.id_)
.where(date_time == Weather.date_time)
.where(Weather.latitude == Tournament.latitude)
.where(Weather.longitude == Tournament.longitude)
.as_scalar()
.label("weather_conditions")
)
每当您将 Match
作为 属性 查询时,这将查询 weather_conditions
。
最终:选项 3:实际 relationship
如下所示定义关系(无需更改模型定义的任何其他部分):
class Match(Base):
__tablename__ = "match"
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
tournament_id = Column(Integer, ForeignKey("tournament.id_"))
weather = relationship(
Weather,
secondary=Tournament.__table__,
primaryjoin=tournament_id == Tournament.id_,
secondaryjoin=and_(
Weather.latitude == Tournament.latitude,
Weather.longitude == Tournament.longitude,
date_time == Weather.date_time,
),
viewonly=True,
uselist=False,
)
您在问题中提出的 Configuring how Relationship Joins link 包含类似解决方案的示例。
现在,要执行查询,您还需要指定 join
条件才能使用您想要的查询:
q = session.query(Match.id_, Weather.conditions).join(Weather, Match.weather)
可以理解,您想利用 ORM 的关系映射,而不是在 class 之外编写显式 SQL(或映射查询)。为此,您需要明确主连接和辅助连接。
这是一个使用显式主连接和辅助连接的解决方案:
注意:前面的回答既充分又正确,我的解决方案是编写代码的不同风格,但ORM的底层行为将是几乎相等。
from sqlalchemy.ext.associationproxy import association_proxy
class Match(Base):
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
tournament_id = Column(Integer, ForeignKey("myschema.tournament.id_"))
# Relationship for weather
weather = relationship("Weather",
secondary='Tournament',
primaryjoin='Tournament.id == Match.tournament_id',
secondaryjoin="""
and_(
Tournament.latitude == Weather.latitude,
Tournament.longitude == Weather.longitude,
Match.date_time == Weather.date_time
)
""",
uselist=False,
doc="""Join `Match` and `Weather` on `date_time` while using `Tournament`
as an association table to match `[latitude, longitude]`.
This will allow querying of the weather of a given match but will
not allow updating of weather through a `Match` object.
"""
)
weather_conditions = association_proxy('conditions', 'weather',
doc='Access the weather conditions without unpacking weather relationship')
class Tournament(Base):
id_ = Column(Integer, primary_key=True)
latitude = Column(Float)
longitude = Column(Float)
match = relationship("Match", backref="tournament",
doc='Join on `Tournament.id == Match.tournament_id`')
weather = relationship("Weather",
secondary='Match',
primaryjoin='Tournament.id == Match.tournament_id',
secondaryjoin="""
and_(
Tournament.latitude == Weather.latitude,
Tournament.longitude == Weather.longitude,
Match.date_time == Weather.date_time
)
""",
uselist=False,
doc="""Join `Tournament` and `Weather` on `[latitude, longitude]` while
using `Match` as an association table to match `date_time`.
This will allow querying of the weather of a given tournament but will
not allow updating of weather through a `Tournament` object.
"""
)
weather_conditions = association_proxy('conditions', 'weather',
doc='Access the weather conditions without unpacking weather relationship')
我目前有以下三个table:
class Match(Base):
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
tournament_id = Column(Integer, ForeignKey("myschema.tournament.id_"))
class Tournament(Base):
id_ = Column(Integer, primary_key=True)
latitude = Column(Float)
longitude = Column(Float)
match = relationship("Match", backref="tournament")
class Weather(Base):
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
latitude = Column(Float)
longitude = Column(Float)
conditions = Column(String(50))
我正在寻找一个查询来获取 Match
table 中每场比赛的天气状况。像这样:
qry = session.query(Match.id_, Weather.conditions)
qry = qry.select_from(Match)
qry = qry.join(Tournament, Weather)
qry = qry.all()
Weather
中的唯一键是 date_time
、latitude
和 longitude
的组合,其中:
date_time
需要连接到Match
中的等价物(多对多)latitude
需要连接到Tournament
中的等价物(多对多)longitude
需要连接到Tournament
中的等价物(多对多)
我可以接受外键上的简单关系,例如Match
和 Tournament
的那个,但我在试图找出更复杂的东西时绝望地迷路了。
我希望以上内容是不言自明的 - 如果需要数据,请告诉我,我会添加一些。
我正在使用 SQLAlchemy v1.3。
更新:
我一直在尝试根据指南创建关系 here:
class Match(Base):
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
tournament_id = Column(Integer, ForeignKey("myschema.tournament.id_"))
weather = relationship(
"Weather", primaryjoin="Match.date_time == Weather.date_time"
)
class Tournament(Base):
id_ = Column(Integer, primary_key=True)
latitude = Column(Float)
longitude = Column(Float)
match = relationship("Match", backref="tournament")
weather = relationship(
"Weather",
primaryjoin="and_(Tournament.latitude == Weather.latitude, " +
"Tournament.longitude == Weather.longitude)"
)
class Weather(Base):
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
latitude = Column(Float)
longitude = Column(Float)
conditions = Column(String(50))
但是,当我 运行 来自上面的查询时,我得到错误:
Don't know how to join to <class 'container.Weather'>. Please use the .select_from() method to establish an explicit left side, as well as providing an explicit ON clause if not present already to help resolve the ambiguity.
我哪里错了?
选项-1:普通查询
实际上,它与编写普通 SQL 查询非常相似:
qry = (
session.query(Match.id_, Weather.conditions)
.select_from(Match)
.join(Tournament)
.join(
Weather,
and_(
Match.date_time == Weather.date_time,
Tournament.latitude == Weather.latitude,
Tournament.longitude == Weather.longitude,
),
)
).all()
选项-2:计算属性
在澄清您想建立“关系”之后,我实际上认为使用 column_property
而不是从 Match 到 Weather 的 relationship
看起来更有效:
class Match(Base):
__tablename__ = "match"
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
tournament_id = Column(Integer, ForeignKey("tournament.id_"))
# NOTE: Weather and Tournament should be defined earlier to use the expressions below. Otherwise, a stringified definition could be used instead
weather_conditions = column_property(
select([Weather.conditions.label("match_weather_conditions")])
.where(tournament_id == Tournament.id_)
.where(date_time == Weather.date_time)
.where(Weather.latitude == Tournament.latitude)
.where(Weather.longitude == Tournament.longitude)
.as_scalar()
.label("weather_conditions")
)
每当您将 Match
作为 属性 查询时,这将查询 weather_conditions
。
最终:选项 3:实际 relationship
如下所示定义关系(无需更改模型定义的任何其他部分):
class Match(Base):
__tablename__ = "match"
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
tournament_id = Column(Integer, ForeignKey("tournament.id_"))
weather = relationship(
Weather,
secondary=Tournament.__table__,
primaryjoin=tournament_id == Tournament.id_,
secondaryjoin=and_(
Weather.latitude == Tournament.latitude,
Weather.longitude == Tournament.longitude,
date_time == Weather.date_time,
),
viewonly=True,
uselist=False,
)
您在问题中提出的 Configuring how Relationship Joins link 包含类似解决方案的示例。
现在,要执行查询,您还需要指定 join
条件才能使用您想要的查询:
q = session.query(Match.id_, Weather.conditions).join(Weather, Match.weather)
可以理解,您想利用 ORM 的关系映射,而不是在 class 之外编写显式 SQL(或映射查询)。为此,您需要明确主连接和辅助连接。
这是一个使用显式主连接和辅助连接的解决方案:
注意:前面的回答既充分又正确,我的解决方案是编写代码的不同风格,但ORM的底层行为将是几乎相等。
from sqlalchemy.ext.associationproxy import association_proxy
class Match(Base):
id_ = Column(Integer, primary_key=True)
date_time = Column(DateTime, index=True)
tournament_id = Column(Integer, ForeignKey("myschema.tournament.id_"))
# Relationship for weather
weather = relationship("Weather",
secondary='Tournament',
primaryjoin='Tournament.id == Match.tournament_id',
secondaryjoin="""
and_(
Tournament.latitude == Weather.latitude,
Tournament.longitude == Weather.longitude,
Match.date_time == Weather.date_time
)
""",
uselist=False,
doc="""Join `Match` and `Weather` on `date_time` while using `Tournament`
as an association table to match `[latitude, longitude]`.
This will allow querying of the weather of a given match but will
not allow updating of weather through a `Match` object.
"""
)
weather_conditions = association_proxy('conditions', 'weather',
doc='Access the weather conditions without unpacking weather relationship')
class Tournament(Base):
id_ = Column(Integer, primary_key=True)
latitude = Column(Float)
longitude = Column(Float)
match = relationship("Match", backref="tournament",
doc='Join on `Tournament.id == Match.tournament_id`')
weather = relationship("Weather",
secondary='Match',
primaryjoin='Tournament.id == Match.tournament_id',
secondaryjoin="""
and_(
Tournament.latitude == Weather.latitude,
Tournament.longitude == Weather.longitude,
Match.date_time == Weather.date_time
)
""",
uselist=False,
doc="""Join `Tournament` and `Weather` on `[latitude, longitude]` while
using `Match` as an association table to match `date_time`.
This will allow querying of the weather of a given tournament but will
not allow updating of weather through a `Tournament` object.
"""
)
weather_conditions = association_proxy('conditions', 'weather',
doc='Access the weather conditions without unpacking weather relationship')