crooksey
crooksey

Reputation: 8809

Sqlalchemy filter parent and child table for parents

I have some models that I am trying to search, so I am looking to show return a result of all Parent objects, where either the Parent name is "foo" or the Child name is "foo".

I have the query:

parents = Session.query(Parent).\
            join(Child_s3).\
            filter(Parent.name.ilike("%foo%")).\
            filter(Child_s3.name.ilike("%foo%")).\
            order_by(asc(Product.name))

And the models:

class Parent(BaseSO):
    __tablename__ = 'parents'
    id = Column(Integer, primary_key=True)
    name = Column(Unicode(100), nullable=False, unique=True)
    colours = relationship('Child_s3', secondary=Parent_images, backref='Parentc')

class Child_s3(BaseSO):
    __tablename__ = 'children'
    id = Column(Integer, primary_key=True)
    name = Column(Unicode)

Parent_images = Table(
    'Parent_images', BaseSO.metadata,
    Column('parent_id', Integer, ForeignKey('parents.id')),
    Column('child_id', Integer, ForeignKey('children.id'))
)

The query I have shows parents with the name of "foo" but does not show any parent objects, that also have children called "foo", can anyone help build this query to search both tables for the corresponding parent objects?

Upvotes: 4

Views: 2324

Answers (1)

snakecharmerb
snakecharmerb

Reputation: 55600

This code shows how to get the result using either an explicit join or a subquery:

import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


Parent_images = sa.Table(
    'Parent_images', Base.metadata,
    sa.Column('parent_id', sa.Integer, sa.ForeignKey('parents.id')),
    sa.Column('child_id', sa.Integer, sa.ForeignKey('children.id'))
)


class Parent(Base):
    __tablename__ = 'parents'
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.Unicode(100), nullable=False, unique=True)
    colours = orm.relationship('Child_s3', secondary=Parent_images, backref='parents')

    def __repr__(self):
        return 'Parent(name=%s)' % self.name

    __str__ = __repr__


class Child_s3(Base):
    __tablename__ = 'children'
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.Unicode)

    def __repr__(self):
        return 'Child_s3(name=%s)' % self.name

    __str__ = __repr__


if __name__ == '__main__':
    engine = sa.create_engine('sqlite:///')
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)
    Session = orm.sessionmaker(bind=engine)

    session = Session()
    for parent, child in [('boofoo', 'spam'), ('baz', 'foobar'), ('bar', 'quux')]:
        p1 = Parent(name=parent)
        session.add(p1)
        p1.colours.append(Child_s3(name=child))
    session.commit()

    print('Join')
    session = Session()
    q = (session.query(Parent)
                .join(Child_s3, Parent.colours)
                .filter(sa.or_(Parent.name.ilike('%foo%'),
                               Child_s3.name.ilike('%foo%'))))
    for p in q.all():
        print(p, p.colours)
    session.commit()
    print()

    print('Subquery')
    session = Session()
    q = (session.query(Parent)
                .filter(sa.or_(Parent.name.ilike('%foo%'),
                               Parent.colours.any(Child_s3.name.ilike('%foo%')))))
    for p in q.all():
        print(p, p.colours)
    session.commit()
    print()

The join query

q = (session.query(Parent)
            .join(Child_s3, Parent.colours)
            .filter(sa.or_(Parent.name.ilike('%foo%'),
                           Child_s3.name.ilike('%foo%'))))

generates this SQL

SELECT parents.id AS parents_id, parents.name AS parents_name 
FROM parents JOIN "Parent_images" AS "Parent_images_1" ON parents.id = "Parent_images_1".parent_id JOIN children ON children.id = "Parent_images_1".child_id 
WHERE lower(parents.name) LIKE lower(?) OR lower(children.name) LIKE lower(?)

The subquery

q = (session.query(Parent)
            .filter(sa.or_(Parent.name.ilike('%foo%'),
                            Parent.colours.any(Child_s3.name.ilike('%foo%')))))

generates this SQL:

SELECT parents.id AS parents_id, parents.name AS parents_name            
FROM parents                                                                                                                        
WHERE lower(parents.name) LIKE lower(?) OR (EXISTS (SELECT 1                                                                        
FROM "Parent_images", children                                                                                                      
WHERE parents.id = "Parent_images".parent_id AND children.id = "Parent_images".child_id AND lower(children.name) LIKE lower(?)))

The script produces this output from the sample data:

Join
Parent(name=baz) [Child_s3(name=foobar)]
Parent(name=boofoo) [Child_s3(name=spam)]

Subquery
Parent(name=boofoo) [Child_s3(name=spam)]
Parent(name=baz) [Child_s3(name=foobar)]

Upvotes: 5

Related Questions