Ok, so yes I see those queries for the two examples you posted.
For my original model (first post), with dynamic it takes about 6sec to
load ~70,000 amount of data, going by the first post then this means all
residents and pets from an address. Which I would like to speed up.
So I tried the eager load selectin (because I thought dynamic affects all()
- which you mentioned it does not), and I get the query from example 2. But
this one is slower because there are more queries - I used a profiler
(sqltap) and it shows the query count as being 3 times as much.
So ideally, I'd like to somehow query/join to get those ~70,000 records
within a faster amount of time, rather than 6 secs. Is there a proper way
to do this with eager loading or perhaps another way?
On Wednesday, May 22, 2019 at 4:49:58 PM UTC-7, Mike Bayer wrote:
>
>
>
> On Wed, May 22, 2019, at 6:44 PM, bvbav wrote:
>
> Thanks for replying, but your response doesn't really help. Perhaps my
> post wasn't clear...
>
>
> not really sure, I answered what I thought I was seeing, and having gone
> through your new response, I am still seeing the same thing being asked, so
> what we do is we start using code to answer the question rather than
> words. that way there's no ambiguity.
>
>
> I'm aware the "dynamic" relationship returns a query, and not a
> collection. It's mainly used for large collections and when someone wants
> to perform filters, etc instead of getting the collection as a list.
>
> Because of the nature of dynamic, when I do that join (re-posted here), it
> emits a SELECT statement for every attribute in the relationship. Correct
> me if I'm wrong on this. So the join query is slow for extremely large
> amount.
>
> db.session.query(Address, Resident, Pet).filter(Address.id == 1).join(
> Address.residents).join(Resident.pets).all()
>
>
> So above, is "residents" the relationship with lazy="dynamic"
> configured? Whether or not it is, "Address.residents" is at the class
> level. This returns a SQL expression object. It does not invoke any kind
> of dynamic query behavior at query time. That is, when the "all()" above
> has completed, there will be exactly one "SELECT" being emitted. If that
> is not what you are seeing, then there is something much more elaborate
> going on in your case like an event listener. See Example 1 at the end of
> the email which illustrates this.
>
> Now when we go beyond the call to all(), and we have a whole list of
> objects that you now iterate through, and Address.residents is in fact the
> "dynamic" relationship, and you access it at the instance level, it will
> run a new Query all over again. This is the expected behavior if
> Address.residents is lazy="dynamic" and there is no way to change that
> except to not use lazy="dynamic".
>
> So are we on the same page? Otherwise if you see something different we
> have to clear that part up first.
>
>
>
>
> Here I'm basically getting all residents and pets that live at an address.
>
> Because it's slow, I thought switching to eager loading would work. I made
> two relationships to keep the dynamic an option - shouldn't have included
> as that caused confusion. So ignore "residents_dynamic" etc.
>
> But with the "selectin" loading, it resulted in 3x the number of queries
> while using that same join above.
>
>
> So when you say 3x the number of queries, I don't know what we are
> multiplying by three since the previous thing you describe is not what
> happens based on your description, "SELECT IN" does not emit a query per
> object withn the scope of the parent Query. You might be seeing 1. per
> object lazy (or dynamic?) loads that aren't actually "selectin" loads, or
> 2. "selectin"loads that are a secondary effect of each of the per-object
> loads in #1. In both cases the problem is the same, that it seems you are
> seeing per-object loads going on that aren't "selectin" loads.
>
> Example 2 below illustrates what I think is the usage you are describing.
> At the end, even though there are 30 objects there are only four queries.
> The main one, then two SELECT IN queries for A->B->C, then another SELECT
> IN query that I think is due to B->C, because it queries for A and B at the
> top level, which is slightly more queries than we'd prefer but still
> nothing dramatic.
>
> the way to get this worked out is to please provide a self-contained,
> short proof of concept that creates the most minimal mapping possible,
> installs the most minimal amount of test data possible into a sqlite://
> memory database, then illustrates the query you are running (this is also
> known as an MCVE). Because I don't really know what you are seeing.
> Please confirm you are also on the SQLAlchemy 1.3 series.
>
>
>
>
> Example 1 - dynamic relationship has no effect on all():
>
> from sqlalchemy import *
> from sqlalchemy.orm import *
> from sqlalchemy.ext.declarative import declarative_base
> from sqlalchemy.ext.declarative import declared_attr
>
> Base = declarative_base()
>
>
> class A(Base):
> __tablename__ = 'a'
>
> id = Column(Integer, primary_key=True)
> data = Column(String)
> bs = relationship("B", lazy="dynamic")
>
>
> class B(Base):
> __tablename__ = 'b'
> id = Column(Integer, primary_key=True)
> a_id = Column(ForeignKey("a.id"))
> data = Column(String)
> cs = relationship("C", lazy="dynamic")
>
> class C(Base):
> __tablename__ = 'c'
> id = Column(Integer, primary_key=True)
> b_id = Column(ForeignKey("b.id"))
> data = Column(String)
>
> e = create_engine("sqlite://", echo=True)
> Base.metadata.create_all(e)
>
> s = Session(e)
>
> s.add_all([
> A(bs=[B(cs=[C(), C()]), B(cs=[C(), C()]), B(cs=[C(), C()])]),
> A(bs=[B(cs=[C(), C()]), B(cs=[C(), C()]), B(cs=[C(), C()])]),
> A(bs=[B(cs=[C(), C()]), B(cs=[C(), C()]), B(cs=[C(), C()])]),
> ])
> s.commit()
> s.close()
>
>
> s.query(A, B, C).filter(A.id == 1).join(A.bs).join(B.cs).all()
>
>
> single query at the end after the INSERTs:
>
> SELECT a.id AS a_id, a.data AS a_data, b.id AS b_id, b.a_id AS b_a_id,
> b.data AS b_data, c.id AS c_id, c.b_id AS c_b_id, c.data AS c_data
> FROM a JOIN b ON a.id = b.a_id JOIN c ON b.id = c.b_id
> WHERE a.id = ?
>
>
>
> Example 2: selectin is one query per class-level relationship, not per
> instance:
>
> from sqlalchemy import *
> from sqlalchemy.orm import *
> from sqlalchemy.ext.declarative import declarative_base
> from sqlalchemy.ext.declarative import declared_attr
>
> Base = declarative_base()
>
>
> class A(Base):
> __tablename__ = 'a'
>
> id = Column(Integer, primary_key=True)
> data = Column(String)
> bs_dyn = relationship("B", lazy="dynamic")
> bs = relationship("B", lazy="selectin")
>
> class B(Base):
> __tablename__ = 'b'
> id = Column(Integer, primary_key=True)
> a_id = Column(ForeignKey("a.id"))
> data = Column(String)
> cs_dyn = relationship("C", lazy="dynamic")
> cs = relationship("C", lazy="selectin")
>
> class C(Base):
> __tablename__ = 'c'
> id = Column(Integer, primary_key=True)
> b_id = Column(ForeignKey("b.id"))
> data = Column(String)
>
> e = create_engine("sqlite://", echo=True)
> Base.metadata.create_all(e)
>
> s = Session(e)
>
> s.add_all([
> A(bs=[B(cs=[C(), C()]), B(cs=[C(), C()]), B(cs=[C(), C()])]),
> A(bs=[B(cs=[C(), C()]), B(cs=[C(), C()]), B(cs=[C(), C()])]),
> A(bs=[B(cs=[C(), C()]), B(cs=[C(), C()]), B(cs=[C(), C()])]),
> ])
> s.commit()
> s.close()
>
>
> s.query(A, B, C).filter(A.id == 1).join(A.bs).join(B.cs).all()
>
>
> queries at the end which are, the s.query(), the two SELECT IN for A.bs ->
> B.cs, then a SELECT IN for B->cs since it is specified at the top level as
> well:
>
> SELECT a.id AS a_id, a.data AS a_data, b.id AS b_id, b.a_id AS b_a_id,
> b.data AS b_data, c.id AS c_id, c.b_id AS c_b_id, c.data AS c_data
> FROM a JOIN b ON a.id = b.a_id JOIN c ON b.id = c.b_id
> WHERE a.id = ?
> 2019-05-22 19:39:25,156 INFO sqlalchemy.engine.base.Engine (1,)
> 2019-05-22 19:39:25,160 INFO sqlalchemy.engine.base.Engine SELECT b.a_id
> AS b_a_id, b.id AS b_id, b.data AS b_data
> FROM b
> WHERE b.a_id IN (?) ORDER BY b.a_id
> 2019-05-22 19:39:25,160 INFO sqlalchemy.engine.base.Engine (1,)
> 2019-05-22 19:39:25,161 INFO sqlalchemy.engine.base.Engine SELECT c.b_id
> AS c_b_id, c.id AS c_id, c.data AS c_data
> FROM c
> WHERE c.b_id IN (?, ?, ?) ORDER BY c.b_id
> 2019-05-22 19:39:25,162 INFO sqlalchemy.engine.base.Engine (1, 2, 3)
> 2019-05-22 19:39:25,163 INFO sqlalchemy.engine.base.Engine SELECT c.b_id
> AS c_b_id, c.id AS c_id, c.data AS c_data
> FROM c
> WHERE c.b_id IN (?, ?, ?) ORDER BY c.b_id
> 2019-05-22 19:39:25,164 INFO sqlalchemy.engine.base.Engine (1, 2, 3)
>
>
>
>
>
>
>
>
>
>
> So I was wondering if anyone knows of a way to do that query so it is
> fast. Perhaps the 3x number of queries is because it eager loads
> Address.residents and Resident.pets, then since I'm also joining, it adds
> onto that? Or perhaps I need to change my query to work with "selectin"?
>
>
> On Wednesday, May 22, 2019 at 1:33:11 PM UTC-7, Mike Bayer wrote:
>
>
>
> On Wed, May 22, 2019, at 3:56 PM, bvbav wrote:
>
> Hi
>
> I was wondering if I can get some advice on this...
>
> Let's say I have these models:
>
> class Pet(db.Model):
> id = db.Column(..., primary_key=True)
> name = db.Column(...)
>
>
> resident_pet = db.Table('resident_pet', db.Column('resident_id', db.
> Integer, db.ForeignKey('resident.id'), primary_key=True), db.Column
> ('pet_id', db.Integer, db.ForeignKey('pet.id'), primary_key=True)
>
> class Resident(db.Model):
> id = db.Column(..., primary_key=True)
> name = db.Column(...)
> pets = db.relationship('Resident', secondary=resident_pet,
> lazy='dynamic')
> resident_address = db.Table('resident_address', db.Column('resident_id',
> db.Integer, db.ForeignKey('resident.id'), primary_key=True), db.Column
> ('address_id', db.Integer, db.ForeignKey('address.id'), primary_key=True)
>
>
> class Address(db.Model):
> ...
> residents = db.relationship('Resident', secondary=resident_address,
> lazy='dynamic')
>
> Now I want to query tables Address, Resident, Pet like this:
>
> db.session.query(Address, Resident, Pet).filter(Address.id ==
> 1).join(Address.residents).join(Resident.pets).all()
>
> Now, let's say hypothetically an address (represents a home) can have a
> large number of residents (e.g 10,000+), and each resident can have the
> same number of pets (e.g 10,000+). This is the reason why I used
> lazy='dynamic', so I can do quick filters etc. But this causes a problem
> when I do joins like the one above. Because with the dynamic loading,
> individual SELECT queries will be emitted for every pet for every resident
> which can result in a low performance? Is my assumption correct?
>
> I thought about switching to 'selectin' while keeping 'dynamic', so there
> would be two relationships
>
> class Address(db.Model):
> ...
> residents = db.relationship('Resident', secondary=resident_address,
> lazy='selectin')
> residents_dynamic = db.relationship('Resident',
> secondary=resident_address,
> lazy='dynamic')
>
> However, doing this (while keeping the same JOIN query above, results in a
> lot more queries and take a longer amount of time to finish. I also figured
> since I'm querying everything, there's no point in eager loading
> Resident.pets, because it's not like I'm getting one instance of Resident
> and expect to have the pets collection already there.
>
> How can I get better performance for this kind of join?
>
>
> the mechanism of "dynamic" loading is that your ORM instance contains a
> link to a special Query object, and it does not store any Python list of
> items. This is entirely different from what relationship does in all
> other cases, which is that your ORM instance has a Python list, set, or
> other kind of collection that simply stores the related objects in memory
> once loaded. So it is not possible for a single relationship to perfom in
> both a "dynamic" as well as a fixed "eager load" style of use.
>
> Therefore, for normal use of your objects when you don't need to add
> additional filter criteria, you should use the "residents" relatoinship
> that does not use "dynamic" loading. Only when you need to make a special
> SQL query that is essentially Address.residents + extra SQL filtering,
> would the "residents_dynamic" relationship be used, as it by definition has
> to query the database on each use (else it wouldn't be "dynamic"). hope
> this helps.
>
>
>
> --
> SQLAlchemy -
> The Python SQL Toolkit and Object Relational Mapper
>
> http://www.sqlalchemy.org/
>
> To post example code, please provide an MCVE: Minimal, Complete, and
> Verifiable Example. See http://stackoverflow.com/help/mcve for a full
> description.
> ---
> You received this message because you are subscribed to the Google Groups
> "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> To post to this group, send email to [email protected].
> Visit this group at https://groups.google.com/group/sqlalchemy.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/sqlalchemy/ab7a84b6-225a-40ec-8615-1ecfe7cf4ce9%40googlegroups.com
>
> <https://groups.google.com/d/msgid/sqlalchemy/ab7a84b6-225a-40ec-8615-1ecfe7cf4ce9%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
> For more options, visit https://groups.google.com/d/optout.
>
>
>
> --
> SQLAlchemy -
> The Python SQL Toolkit and Object Relational Mapper
>
> http://www.sqlalchemy.org/
>
> To post example code, please provide an MCVE: Minimal, Complete, and
> Verifiable Example. See http://stackoverflow.com/help/mcve for a full
> description.
> ---
> You received this message because you are subscribed to the Google Groups
> "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected] <javascript:>.
> To post to this group, send email to [email protected]
> <javascript:>.
> Visit this group at https://groups.google.com/group/sqlalchemy.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/sqlalchemy/cd7b05b0-97ee-49e6-8159-2bd6aff34763%40googlegroups.com
>
> <https://groups.google.com/d/msgid/sqlalchemy/cd7b05b0-97ee-49e6-8159-2bd6aff34763%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
> For more options, visit https://groups.google.com/d/optout.
>
>
>
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
http://www.sqlalchemy.org/
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable
Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
To view this discussion on the web visit
https://groups.google.com/d/msgid/sqlalchemy/9824f5b0-b4f2-463a-b6ff-256aef5f6b49%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.