Adjacency List Relationships

    See also

    This section details the single-table version of a self-referential relationship. For a self-referential relationship that uses a second table as an association table, see the section Self-Referential Many-to-Many Relationship.

    In this example, we’ll work with a single mapped class called , representing a tree structure:

    With this structure, a graph such as the following:

    1. root --+---> child1
    2. +---> child2 --+--> subchild1
    3. | +--> subchild2
    4. +---> child3
    1. id parent_id data
    2. --- ------- ----
    3. 1 NULL root
    4. 2 1 child1
    5. 3 1 child2
    6. 4 3 subchild1
    7. 5 3 subchild2
    8. 6 1 child3

    The configuration here works in the same way as a “normal” one-to-many relationship, with the exception that the “direction”, i.e. whether the relationship is one-to-many or many-to-one, is assumed by default to be one-to-many. To establish the relationship as many-to-one, an extra directive is added known as relationship.remote_side, which is a or collection of Column objects that indicate those which should be considered to be “remote”:

    Where above, the id column is applied as the of the parent relationship(), thus establishing parent_id as the “local” side, and the relationship then behaves as a many-to-one.

    As always, both directions can be combined into a bidirectional relationship using the function:

    1. class Node(Base):
    2. __tablename__ = "node"
    3. id = mapped_column(Integer, primary_key=True)
    4. parent_id = mapped_column(Integer, ForeignKey("node.id"))
    5. data = mapped_column(String(50))
    6. children = relationship("Node", backref=backref("parent", remote_side=[id]))

    See also

    A sub-category of the adjacency list relationship is the rare case where a particular column is present on both the “local” and “remote” side of the join condition. An example is the Folder class below; using a composite primary key, the account_id column refers to itself, to indicate sub folders which are within the same account as that of the parent; while folder_id refers to a specific folder within that account:

    1. class Folder(Base):
    2. __tablename__ = "folder"
    3. __table_args__ = (
    4. ForeignKeyConstraint(
    5. ),
    6. )
    7. account_id = mapped_column(Integer, primary_key=True)
    8. folder_id = mapped_column(Integer, primary_key=True)
    9. parent_id = mapped_column(Integer)
    10. name = mapped_column(String)
    11. parent_folder = relationship(
    12. "Folder", backref="child_folders", remote_side=[account_id, folder_id]
    13. )

    Above, we pass account_id into the list. relationship() recognizes that the account_id column here is on both sides, and aligns the “remote” column along with the folder_id column, which it recognizes as uniquely present on the “remote” side.

    Querying of self-referential structures works like any other query:

    However extra care is needed when attempting to join along the foreign key from one level of the tree to the next. In SQL, a join from a table to itself requires that at least one side of the expression be “aliased” so that it can be unambiguously referred to.

    1. from sqlalchemy.orm import aliased
    2. nodealias = aliased(Node)
    3. session.scalars(
    4. select(Node)
    5. .where(Node.data == "subchild1")
    6. .join(Node.parent.of_type(nodealias))
    7. .where(nodealias.data == "child2")
    8. ).all()
    9. SELECT node.id AS node_id,
    10. node.parent_id AS node_parent_id,
    11. node.data AS node_data
    12. FROM node JOIN node AS node_1
    13. AND node_1.data = ?
    14. ['subchild1', 'child2']

    Eager loading of relationships occurs using joins or outerjoins from parent to child table during a normal query operation, such that the parent and its immediate child collection or reference can be populated from a single SQL statement, or a second statement for all immediate child collections. SQLAlchemy’s joined and subquery eager loading use aliased tables in all cases when joining to related items, so are compatible with self-referential joining. However, to use eager loading with a self-referential relationship, SQLAlchemy needs to be told how many levels deep it should join and/or query; otherwise the eager load will not take place at all. This depth setting is configured via relationships.join_depth:

    1. class Node(Base):
    2. __tablename__ = "node"
    3. id = mapped_column(Integer, primary_key=True)
    4. parent_id = mapped_column(Integer, ForeignKey("node.id"))
    5. data = mapped_column(String(50))
    6. children = relationship("Node", lazy="joined", join_depth=2)
    7. session.scalars(select(Node)).all()
    8. SELECT node_1.id AS node_1_id,
    9. node_1.parent_id AS node_1_parent_id,
    10. node_1.data AS node_1_data,
    11. node_2.id AS node_2_id,
    12. node_2.parent_id AS node_2_parent_id,
    13. node_2.data AS node_2_data,
    14. node.id AS node_id,
    15. node.parent_id AS node_parent_id,
    16. node.data AS node_data
    17. FROM node
    18. LEFT OUTER JOIN node AS node_2
    19. ON node.id = node_2.parent_id
    20. LEFT OUTER JOIN node AS node_1
    21. ON node_2.id = node_1.parent_id