# Relationships --- ### Outline 1. Review types of relationships 2. When you should use an association table in SQLAlchemy 3. How to use a association table (CRUD) 4. Some other relationship options --- ## Types of relationships --- ### What type of relationship would you have between... - `students` and `cohorts` - `posts` and `comments` - `users` and `user_profiles` - `guests` and `events` - `books` and `authors` --- ### One-to-one - not expecially common - typically useful when you have - a collection of columns that would have null values in most rows - that you won't access most of the time - e.g. `users` and `user_profiles` --- ### One-to-many/Many-to-one - these are most common - the "many" side has the foreign key - e.g. - `employees` and `departments` - `players` and `teams` --- ### Many-to-many - no direct relationship between the two tables - an intermediate table contains both the foreign keys - e.g. - `posts` and `tags` - `students` and `classes` - self joins too—`users` with `users` in a follows table --- ### Many-to-many - on the database level, there is no distinction between a many-to-many relationship and just two one-to-many relationships - the difference emerges in the models/in the ORM --- ### Association tables - in SQLAlchemy, you only use an association table (as opposed to a model) when there is no other information on that table—just the two primary keys - it also doesn't let you represent duplicates - join table uses `db.Table` --- ### Association tables ```python= pet_owners = db.Table( "pet_owners", db.Column( "pet_id", db.Integer, db.ForeignKey("pets.id"), primary_key=True ), db.Column( "owner_id", db.Integer, db.ForeignKey("owners.id"), primary_key=True) ) ``` --- ### Relationships with association tables - on the Owner model ```python= pets = db.relationship( "Pet", secondary=pet_owners, back_populates="owners" ) ``` - on the Pet model ```python= owners = db.relationship( "Owner", secondary=pet_owners, back_populates="pets" ) ``` --- ### Use model class or association table? - `guests` and `events` - `pets` and `owners` - `orders` and `products` - `characters` and `items` --- ## Join table CRUD --- ### Create entries in a join table - get or create the two entities you want to associate - use `append` to add one entity to the relevent list ```python= # get my pet object from the database juice = Pets.query.filter_by(name="Juice").first() # create an owner object juliet = Owner(name="Juliet") # create join table entry for juliet and juice juliet.pets.append(juice) db.session.add(juliet) db.session.commit() ``` --- ### Create entries in a join table (using only ids) - if you already have the ids of the objects, you would usually just query the database for the objects (using `.get()`) and then insert using the previous, however, if you really needed to, you could insert the values directly into the join table, then add and commit - the syntax is not as memorable ```python= db.session.connection().execute( pet_owners.insert().values(owner_id=5, pet_id=2) ) ``` --- ### Read entries in a join table - the `db.relationship` will allow you to access the collection of the associated objects as a property ```python= pets = juliet.pets # use a comprehension to get the information # that you actually want print([pet.name for pet in pets]) ``` --- ### Update entries in a join table - you don't! - there's nothing to update—either the two entries are joined in the table, or they aren't - there's no other information to change --- ### Delete entries in a join table - get or create the two objects that you no longer want to be associated - use `remove` to delete the association ```python= not_juliet.pets.remove(juice) db.session.commit() ``` --- ## Additional options --- ### Cascade delete - add `passive_deletes=True` to the `db.relationship` on the parent model - add `ondelete="CASCADE"` to the `db.ForeignKey` column --- ### Cascade delete ```python= class Circle(db.Model): __tablename__ = 'circles' id = db.Column(db.Integer, primary_key=True) students = db.relationship( "Student", back_populates="circle", passive_deletes=True, ) class Student(db.Model): __tablename__ = 'students' id = db.Column(db.Integer, primary_key=True) circle_id = db.Column( db.Integer, db.ForeignKey('circles.id', ondelete="CASCADE") ) circle = db.relationship("Circle", back_populates="students") ``` --- ### Lazy and eager loading - by default, a `db.relationship` will use lazy loading (`lazy='select'`) - for eager loading, you could use `lazy='joined'` - sometimes you may want to use `lazy='dynamic'` so that you can perform additional filtering - you can also configure the loading for an individual query - more options: [documentation](https://docs.sqlalchemy.org/en/13/orm/relationship_api.html#sqlalchemy.orm.relationship.params.lazy) - but remember: make it work before you make it fast
{"metaMigratedAt":"2023-06-15T21:54:13.590Z","metaMigratedFrom":"Content","title":"Relationships","breaks":true,"contributors":"[{\"id\":\"332e0935-ffc0-4c07-8c84-93f35cbacf83\",\"add\":7991,\"del\":2814}]"}
    737 views