将contains_eager与过滤器一起使用但不过滤掉没有匹配子项的父项

时间:2017-05-08 22:23:29

标签: python sqlalchemy

阅读SQLAlchemy文档:

http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#using-contains-eager-to-load-a-custom-filtered-collection-result

  

上述查询将仅加载至少包含Address对象的User对象,该对象包含子字符串' ed'在其电子邮件领域。

我正在尝试做类似的事情,但我也想要检索所有没有匹配地址的用户对象。

为了更好地解释自己,我修改了这段代码,这是SQLAlchemy文档中的一个例子:

from sqlalchemy.ext.declarative import as_declarative, declared_attr
from sqlalchemy import create_engine, Integer, Column, \
                    String, and_ 
from sqlalchemy.orm import Session, relationship, foreign, remote, backref, joinedload, contains_eager
from sqlalchemy import event


@as_declarative()
class Base(object):
    """Base class which provides automated table name
    and surrogate primary key column.

    """
    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()
    id = Column(Integer, primary_key=True)

class Address(Base):
    """The Address class.

    This represents all address records in a
    single table.

    """
    street = Column(String)
    city = Column(String)
    zip = Column(String)

    discriminator = Column(String)
    """Refers to the type of parent."""

    parent_id = Column(Integer)
    """Refers to the primary key of the parent.

    This could refer to any table.
    """

    @property
    def parent(self):
        """Provides in-Python access to the "parent" by choosing
        the appropriate relationship.

        """
        return getattr(self, "parent_%s" % self.discriminator)

    def __repr__(self):
        return "%s(street=%r, city=%r, zip=%r)" % \
            (self.__class__.__name__, self.street,
            self.city, self.zip)

class HasAddresses(object):
    """HasAddresses mixin, creates a relationship to
    the address_association table for each parent.

    """

@event.listens_for(HasAddresses, "mapper_configured", propagate=True)
def setup_listener(mapper, class_):
    name = class_.__name__
    discriminator = name.lower()
    class_.addresses = relationship(Address,
                        primaryjoin=and_(
                                        class_.id == foreign(remote(Address.parent_id)),
                                        Address.discriminator == discriminator
                                    ),
                        backref=backref(
                                "parent_%s" % discriminator,
                                primaryjoin=remote(class_.id) == foreign(Address.parent_id)
                                )
                        )
    @event.listens_for(class_.addresses, "append")
    def append_address(target, value, initiator):
        value.discriminator = discriminator

class Customer(HasAddresses, Base):
    name = Column(String)

class Supplier(HasAddresses, Base):
    company_name = Column(String)

engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)

session = Session(engine)

session.add_all([
    Customer(
        name='customer 1',
        addresses=[
            Address(
                    street='123 anywhere street',
                    city="New York",
                    zip="10110"),
            Address(
                    street='40 main street',
                    city="San Francisco",
                    zip="95732")
        ]
    ),
    Customer(
        name='customer 2',
        addresses=[
            Address(
                    street='88 hello yes',
                    city="Old Potato",
                    zip="10110"),
            Address(
                    street='123 banana street',
                    city="New Texas",
                    zip="23422"),
            Address(
                    street='40 dsfasadfmain street',
                    city="San Cocisfran",
                    zip="10110")
        ]
    ),
    Customer(
        name='customer 3',
        addresses=[
            Address(
                    street='123 orange street',
                    city="New Mew",
                    zip="23422"),
        ]
    ),
    Supplier(
        company_name="Ace Hammers",
        addresses=[
            Address(
                    street='2569 west elm',
                    city="Detroit",
                    zip="56785")
        ]
    ),
])

session.commit()

query = (
    session.query(Customer)
    .outerjoin(Customer.addresses)
    .filter(Address.zip == "10110")
    .options(contains_eager(Customer.addresses))
)
for customer in query.all():
    print(customer.name)
    print(str(customer.addresses))

此代码使用邮政编码10110正确打印客户1和2及其地址,但我也想获得客户3,即使它没有完全匹配的地址。

ie:我想获得所有客户的列表,无论他们的地址是什么,并且对于每个客户,我希望他们的地址被填充,但只有他们的邮政编码为10110。

理想情况下,这将成为JOIN的附加条件,而不是WHERE。结果查询可能看起来像这样,我只是不知道如何表达"它在SQLAlchemy术语中:

... LEFT OUTER JOIN address ON customer.id = address.parent_id AND address.discriminator = 'customer' AND address.zip = '10110'

1 个答案:

答案 0 :(得分:3)

您可以更改加入条件:

query = session.query(Customer) \
               .outerjoin(Address, and_(Address.parent_id == Customer.id,
                                        Address.discriminator == "customer",
                                        Address.zip == "10110")) \
               .options(contains_eager(Customer.addresses))