简体   繁体   English

Python/SqlAlchemy 将表连接到自身不生成预期的查询

[英]Python/SqlAlchemy joining table to itself not generating expected query

What I want to do seems like it should be straightforward.我想做的事情似乎应该很简单。 I want to join a table representing data collection stations to itself, in order to track previous iterations of stations deployed in the same location.我想将代表数据收集站的表连接到自身,以便跟踪部署在同一位置的站的先前迭代。

In the code below, I have two classes: StationTable and StationTypeTable.在下面的代码中,我有两个类:StationTable 和 StationTypeTable。 The StationTable has two FK relationships -- one to a station type, and another back to the station table. StationTable 有两个 FK 关系——一个到站类型,另一个回到站表。

At the end is the generated SQL that shows the correct join to the StationType table, but no trace whatsoever of the link created by the previous_station column.最后是生成的 SQL,它显示了对 StationType 表的正确连接,但没有任何痕迹由 previous_station 列创建的链接。

What am I doing wrong?我究竟做错了什么? Note that this will eventually be used with FastApi and the Async Postgres driver, which may or may not be of interest.请注意,这最终将与 FastApi 和 Async Postgres 驱动程序一起使用,这可能会或可能不会引起人们的兴趣。 Also, I don't need to modify the related tables via the relationship;另外,我不需要通过关系修改相关表; I only need to read some attributes.我只需要读取一些属性。

Using SQLAlchemy 1.4, latest version.使用 SQLAlchemy 1.4,最新版本。

from typing import Any

import sqlalchemy as sa
from sqlalchemy import select
from sqlalchemy.orm import registry, RelationshipProperty
from sqlalchemy.schema import ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm.decl_api import DeclarativeMeta


mapper_registry = registry()


class BaseTable(metaclass=DeclarativeMeta):
    __abstract__ = True
    registry = mapper_registry
    metadata = mapper_registry.metadata

    __init__ = mapper_registry.constructor
    id = sa.Column(UUID(as_uuid=True), primary_key=True, server_default=sa.text("gen_random_uuid()"))


class Relationship(RelationshipProperty):       # type: ignore
    """ Using this class hides some of the static typing messiness in SQLAlchemy. """
    inherit_cache = True        # If this works, should improve performance


    def __init__(self, *args: Any, **kwargs: Any):
        if "lazy" not in kwargs:
            # 'joined' means items should be loaded "eagerly" in the same query as that of the parent, using a JOIN or LEFT
            # OUTER JOIN. Whether the join is "outer" or not is determined by the relationship.innerjoin parameter.
            # We need this to keep loading in async mode from blowing up.
            # https://docs.sqlalchemy.org/en/14/orm/relationship_api.html
            kwargs["lazy"] = "joined"

        super().__init__(*args, **kwargs)


class StationTypeTable(BaseTable):
    __tablename__ = "station_type"

    name = sa.Column(sa.String(255), unique=True, nullable=False)
    description = sa.Column(sa.UnicodeText)


class StationTable(BaseTable):
    __tablename__ = "station"

    name = sa.Column(sa.String(255), unique=True, nullable=False)
    installation_date = sa.Column(sa.BigInteger, nullable=False)
    station_type_id = sa.Column(UUID(as_uuid=True), ForeignKey(StationTypeTable.id), nullable=False)
    previous_station = sa.Column(UUID(as_uuid=True), ForeignKey("station.id"), nullable=True)

    station_type_table = Relationship(StationTypeTable, uselist=False)
    previous_station_table = Relationship("StationTable", uselist=False)   # self join, uselist=False ==> one-to-one



query = select(StationTable)

print(query)


# SELECT station.id, station.name, station.installation_date, station.station_type_id, station.previous_station, 
#        station_type_1.id AS id_1, station_type_1.name AS name_1, station_type_1.description 
# FROM station 
# LEFT OUTER JOIN station_type AS station_type_1 ON station_type_1.id = station.station_type_id

EDIT:编辑:

Based on Ian Wilson's reply below, I added the parameter join_depth=1 to the previous_station_table relationship, which did indeed generate the SQL for the relationship, but it is, oddly, "the wrong way around" compared to the station_type_table relationship.根据下面 Ian Wilson 的回复,我将参数join_depth=1添加到 previous_station_table 关系,这确实为关系生成了 SQL,但奇怪的是,与 station_type_table 关系相比,它是“错误的方式”。 Here is the SQL generated with that param:这是使用该参数生成的 SQL:

SELECT station.id, station.name, station.installation_date, 
          station.station_type_id, station.previous_station, 
          station_type_1.id AS id_1, station_type_1.name AS name_1, 
          station_type_1.description, station_type_2.id AS id_2, 
          station_type_2.name AS name_2, station_type_2.description AS description_1, 
          station_1.id AS id_3, station_1.name AS name_3, 
          station_1.installation_date AS installation_date_1, 
          station_1.station_type_id AS station_type_id_1, 
          station_1.previous_station AS previous_station_1 
FROM station 
LEFT OUTER JOIN station_type AS station_type_1 
    ON station_type_1.id = station.station_type_id  -- looks right
LEFT OUTER JOIN station AS station_1 
    ON station.id = station_1.previous_station      -- looks backward, see below
LEFT OUTER JOIN station_type AS station_type_2 
    ON station_type_2.id = station_1.station_type_id

I think that the marked line should be:我认为标记的行应该是:

LEFT OUTER JOIN station AS station_1 ON station.previous_station = station_1.id

The problem appears to be that you have to set join_depth for self-referential eager loading, I set it to join_depth=1 and that seemed to fix the query.问题似乎是您必须为自引用预加载设置join_depth ,我将其设置为join_depth=1并且这似乎修复了查询。 Since you have an additional eager join this actually creates 3 joins instead of 2 joins because the second set of stations have to join type too.由于您有一个额外的急切加入,这实际上会创建 3 个加入而不是 2 个加入,因为第二组站也必须加入类型。 The documentation explains join_depth here:该文档在此处解释了join_depth

configuring-self-referential-eager-loading 配置自我参考急切加载

The param for relatoinship is briefly explained here:这里简要解释了relatoinship的参数:

sqlalchemy.orm.relationship.params.join_depth sqlalchemy.orm.relationship.params.join_depth

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

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