- Published on
SQLAlchemy Foreign keys and relationships
- Authors

- Name
- Gene Zhang
Foreign key and relation defination
We can define a foreign key and a relation in Child model like:
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key = True)
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key = True)
parentid = Column(Integer, ForeignKey(Parent.id))
parent = relationship(Parent)
Then we can add a pair of related parent and child:
parent = Parent()
c1 = Child()
c1.parent = parent
session.add(c1)
session.commit()
Then we try to delete the parent :
session.delete(parent)
session.commit()
Guess what would happen? It depends.
Foreign key constraint
Different target database behaves differently when deleting a related parent model in SQLAlchemy:
sqlite: allow this deletion andc1.parentwould be setNone.postgresql: reject this deletion and raise an error:
IntegrityError: (psycopg2.errors.ForeignKeyViolation) update or delete on table "parent" violates foreign key constraint "child_parentid_fkey" on table "child" DETAIL: Key (id)=(1) is still referenced from table "child".
We could then go on with a postgresqldatabase and discover more funny things.
Bidirectional reference
The above configuration establishes a parent object on Child called Child.parent.
If we also want to reference Child on Parent side, we could add the relationship.back_populates parameter:
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key = True)
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key = True)
parentid = Column(Integer, ForeignKey(Parent.id))
parent = relationship("Parent", back_populates="children")
Or more concisely by using a single relationship.backref :
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key = True)
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key = True)
parentid = Column(Integer, ForeignKey(Parent.id))
parent = relationship("Parent", backref="children")
Then a Parent can reference a collection of items represented by the Child by called Parent.children.
(backref will add a field children to Parent model implicitly)
Disappearance of foreign key constraint
After we add a back_populates or backref parameter like above, strangely, the foreign key constraint disappears, even when the target database is postgresql .
We could delete a parent without an exception and c1.parent would be set None.
Full expamle code:
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key = True)
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key = True)
parentid = Column(Integer, ForeignKey(Parent.id))
parent = relationship(Parent, backref="children")
# engine = create_engine("sqlite:///:memory:")
engine = create_engine("postgresql://postgres:postgres@localhost:5432/postgres")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
parent = Parent()
c1 = Child()
c1.parent = parent
session.add(c1)
session.commit()
print("c1.parent before deletion:", c1.parent)
session.delete(parent)
session.commit()
print("c1.parent after deletion:", c1.parent)
session.close()
output:
c1.parent before deletion: <__main__.Parent object at 0x7fc9103fb2b0>
c1.parent after deletion: None
It will raise an exception by just removing the backref parameter.
Change above code line 18 from:
parent = relationship(Parent, backref="children")
to:
parent = relationship(Parent)
output:
IntegrityError: (psycopg2.errors.ForeignKeyViolation) update or delete on table "parent" violates foreign key constraint "child_parentid_fkey" on table "child"
DETAIL: Key (id)=(2) is still referenced from table "child".
Still haven't figured out why exactly it behaves like this. I'd appreciate it if you share your insights.