New Features and Improvements - ORM
===================================
+.. _change_4340:
+
+selectin loading no longer uses JOIN for simple one-to-many
+------------------------------------------------------------
+
+The "selectin" loading feature added in 1.2 introduced an extremely
+performant new way to eagerly load collections, in many cases much faster
+than that of "subquery" eager loading, as it does not rely upon restating
+the original SELECT query and instead uses a simple IN clause. However,
+the "selectin" load still relied upon rendering a JOIN between the
+parent and related tables, since it needs the parent primary key values
+in the row in order to match rows up. In 1.3, a new optimization
+is added which will omit this JOIN in the most common case of a simple
+one-to-many load, where the related row already contains the primary key
+of the parent row expressed in its foreign key columns. This again provides
+for a dramatic performance improvement as the ORM now can load large numbers
+of collections all in one query without using JOIN or subqueries at all.
+
+Given a mapping::
+
+ class A(Base):
+ __tablename__ = 'a'
+
+ id = Column(Integer, primary_key=True)
+ bs = relationship("B", lazy="selectin")
+
+
+ class B(Base):
+ __tablename__ = 'b'
+ id = Column(Integer, primary_key=True)
+ a_id = Column(ForeignKey("a.id"))
+
+In the 1.2 version of "selectin" loading, a load of A to B looks like:
+
+.. sourcecode:: sql
+
+ SELECT a.id AS a_id FROM a
+ SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id
+ FROM a AS a_1 JOIN b ON a_1.id = b.a_id
+ WHERE a_1.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ORDER BY a_1.id
+ (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+With the new behavior, the load looks like:
+
+.. sourcecode:: sql
+
+
+ SELECT a.id AS a_id FROM a
+ SELECT b.a_id AS b_a_id, b.id AS b_id FROM b
+ WHERE b.a_id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ORDER BY b.a_id
+ (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+The behavior is being released as automatic, using a similar heuristic that
+lazy loading uses in order to determine if related entities can be fetched
+directly from the identity map. However, as with most querying features,
+the feature's implementation became more complex as a result of advanced
+scenarios regarding polymorphic loading. If problems are encountered,
+users should report a bug, however the change also incldues a flag
+:paramref:`.relationship.omit_join` which can be set to False on the
+:func:`.relationship` to disable the optimization.
+
+
+:ticket:`4340`
+
.. _change_4257:
info dictionary added to InstanceState
--- /dev/null
+.. change::
+ :tags: feature, orm
+ :tickets: 4340
+
+ The "selectin" loader strategy now omits the JOIN in the case of a simple
+ one-to-many load, where it instead relies loads only from the related
+ table, relying upon the foreign key columns of the related table in order
+ to match up to primary keys in the parent table. This optimization can be
+ disabled by setting the :paramref:`.relationship.omit_join` flag to False.
+ Many thanks to Jayson Reis for the efforts on this.
+
+ .. seealso::
+
+ :ref:`change_4340`
:meth:`.Query.yield_per`, provided the database driver supports simultaneous
cursors.
+Overall, especially as of the 1.3 series of SQLAlchemy, selectin loading
+is the most simple and efficient way to eagerly load collections of objects
+in most cases. The only scenario in which selectin eager loading is not feasible
+is when the model is using composite primary keys, and the backend database
+does not support tuples with IN, which includes SQLite, Oracle and
+SQL Server.
+
.. versionadded:: 1.2
"Select IN" eager loading is provided using the ``"selectin"`` argument
WHERE users.name = ? OR users.name = ?
('jack', 'ed')
SELECT
- users_1.id AS users_1_id,
addresses.id AS addresses_id,
addresses.email_address AS addresses_email_address,
addresses.user_id AS addresses_user_id
- FROM users AS users_1
- JOIN addresses ON users_1.id = addresses.user_id
- WHERE users_1.id IN (?, ?)
- ORDER BY users_1.id, addresses.id
+ FROM addresses
+ WHERE addresses.user_id IN (?, ?)
+ ORDER BY addresses.user_id, addresses.id
(5, 7)
-Above, the second SELECT refers to ``users_1.id IN (5, 7)``, where the
+Above, the second SELECT refers to ``addresses.user_id IN (5, 7)``, where the
"5" and "7" are the primary key values for the previous two ``User``
objects loaded; after a batch of objects are completely loaded, their primary
key values are injected into the ``IN`` clause for the second SELECT.
+Because the relatonship between ``User`` and ``Address`` provides that the
+primary key values for ``User`` can be derived from ``Address.user_id``, the
+statement has no joins or subqueries at all.
+
+.. versionchanged:: 1.3 selectin loading can omit the JOIN for a simple
+ one-to-many collection.
+
+In the case where the primary key of the parent object isn't present in
+the related row, "selectin" loading will also JOIN to the parent table so that
+the parent primary key values are present:
+
+.. sourcecode:: python+sql
+
+ >>> session.query(Address).\
+ ... options(selectinload('user')).all()
+ {opensql}SELECT
+ addresses.id AS addresses_id,
+ addresses.email_address AS addresses_email_address,
+ addresses.user_id AS addresses_user_id
+ FROM addresses
+ SELECT
+ addresses_1.id AS addresses_1_id,
+ users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
+ users.password AS users_password
+ FROM addresses AS addresses_1
+ JOIN users ON users.id = addresses_1.user_id
+ WHERE addresses_1.id IN (?, ?)
+ ORDER BY addresses_1.id
+ (1, 2)
"Select IN" loading is the newest form of eager loading added to SQLAlchemy
as of the 1.2 series. Things to know about this kind of loading include:
* "selectin" loading, unlike joined or subquery eager loading, always emits
its SELECT in terms of the immediate parent objects just loaded, and
not the original type of object at the top of the chain. So if eager loading
- many levels deep, "selectin" loading still uses exactly one JOIN in the statement.
+ many levels deep, "selectin" loading still uses no more than one JOIN,
+ and usually no JOINs, in the statement. In comparison,
joined and subquery eager loading always refer to multiple JOINs up to
the original parent.
as :term:`query options` which give additional instructions to the :class:`.Query` on how
we would like various attributes to be loaded, via the :meth:`.Query.options` method.
-Subquery Load
+Selectin Load
-------------
In this case we'd like to indicate that ``User.addresses`` should load eagerly.
A good choice for loading a set of objects as well as their related collections
-is the :func:`.orm.subqueryload` option, which emits a second SELECT statement
+is the :func:`.orm.selectinload` option, which emits a second SELECT statement
that fully loads the collections associated with the results just loaded.
-The name "subquery" originates from the fact that the SELECT statement
-constructed directly via the :class:`.Query` is re-used, embedded as a subquery
-into a SELECT against the related table. This is a little elaborate but
-very easy to use:
+The name "selectin" originates from the fact that the SELECT statement
+uses an IN clause in order to locate related rows for multiple objects
+at once:
.. sourcecode:: python+sql
- >>> from sqlalchemy.orm import subqueryload
+ >>> from sqlalchemy.orm import selectinload
{sql}>>> jack = session.query(User).\
- ... options(subqueryload(User.addresses)).\
+ ... options(selectinload(User.addresses)).\
... filter_by(name='jack').one()
SELECT users.id AS users_id,
users.name AS users_name,
FROM users
WHERE users.name = ?
('jack',)
- SELECT addresses.id AS addresses_id,
- addresses.email_address AS addresses_email_address,
- addresses.user_id AS addresses_user_id,
- anon_1.users_id AS anon_1_users_id
- FROM (SELECT users.id AS users_id
- FROM users WHERE users.name = ?) AS anon_1
- JOIN addresses ON anon_1.users_id = addresses.user_id
- ORDER BY anon_1.users_id, addresses.id
- ('jack',)
+ SELECT addresses.user_id AS addresses_user_id,
+ addresses.id AS addresses_id,
+ addresses.email_address AS addresses_email_address
+ FROM addresses
+ WHERE addresses.user_id IN (?)
+ ORDER BY addresses.user_id, addresses.id
+ (5,)
{stop}>>> jack
<User(name='jack', fullname='Jack Bean', password='gjffdd')>
>>> jack.addresses
[<Address(email_address='jack@google.com')>, <Address(email_address='j25@yahoo.com')>]
-.. note::
-
- :func:`.subqueryload` when used in conjunction with limiting such as
- :meth:`.Query.first`, :meth:`.Query.limit` or :meth:`.Query.offset`
- should also include :meth:`.Query.order_by` on a unique column in order to
- ensure correct results. See :ref:`subqueryload_ordering`.
Joined Load
-----------
strategy, based on object identity, to the returned entities. This is specifically
so that joined eager loading can be applied without affecting the query results.
-While :func:`.joinedload` has been around for a long time, :func:`.subqueryload`
-is a newer form of eager loading. :func:`.subqueryload` tends to be more appropriate
+While :func:`.joinedload` has been around for a long time, :func:`.selectinload`
+is a newer form of eager loading. :func:`.selectinload` tends to be more appropriate
for loading related collections while :func:`.joinedload` tends to be better suited
for many-to-one relationships, due to the fact that only one row is loaded
-for both the lead and the related object.
+for both the lead and the related object. Another form of loading,
+:func:`.subqueryload`, also exists, which can be used in place of
+:func:`.selectinload` when making use of composite primary keys on certain
+backends.
.. topic:: ``joinedload()`` is not a replacement for ``join()``
def iter_for_shard(shard_id):
context.attributes['shard_id'] = context.identity_token = shard_id
result = self._connection_from_session(
- mapper=self._mapper_zero(),
+ mapper=self._bind_mapper(),
shard_id=shard_id).execute(
context.statement,
self._params)
if isinstance(expr, Bundle):
_BundleEntity(self, expr)
else:
- _ColumnEntity(self, expr, namespace=self)
+ _ColumnEntity(self, expr)
self.supports_single_entity = self.bundle.single_entity
@property
def mapper(self):
- return self.entity_zero.mapper
+ ezero = self.entity_zero
+ if ezero is not None:
+ return ezero.mapper
+ else:
+ return None
@property
def entities(self):
bake_queries=True,
_local_remote_pairs=None,
query_class=None,
- info=None):
+ info=None,
+ omit_join=None):
"""Provide a relationship between two mapped classes.
This corresponds to a parent-child or associative table relationship.
the full set of related objects, to prevent modifications of the
collection from resulting in persistence operations.
+ :param omit_join:
+ Allows manual control over the "selectin" automatic join
+ optimization. Set to ``False`` to disable the "omit join" feature
+ added in SQLAlchemy 1.3.
+
+ .. versionadded:: 1.3
+
"""
super(RelationshipProperty, self).__init__()
self.doc = doc
self.active_history = active_history
self.join_depth = join_depth
+ self.omit_join = omit_join
self.local_remote_pairs = _local_remote_pairs
self.extension = extension
self.bake_queries = bake_queries
@properties.RelationshipProperty.strategy_for(lazy="selectin")
class SelectInLoader(AbstractRelationshipLoader, util.MemoizedSlots):
__slots__ = (
- 'join_depth', '_parent_alias', '_in_expr', '_parent_pk_cols',
- '_zero_idx', '_bakery'
+ 'join_depth', 'omit_join', '_parent_alias', '_in_expr',
+ '_pk_cols', '_zero_idx', '_bakery'
)
_chunksize = 500
def __init__(self, parent, strategy_key):
super(SelectInLoader, self).__init__(parent, strategy_key)
self.join_depth = self.parent_property.join_depth
+
+ if self.parent_property.omit_join is not None:
+ self.omit_join = self.parent_property.omit_join
+ else:
+ lazyloader = self.parent_property._get_strategy(
+ (("lazy", "select"),))
+ self.omit_join = self.parent._get_clause[0].compare(
+ lazyloader._rev_lazywhere,
+ use_proxies=True,
+ equivalents=self.parent._equivalent_columns
+ )
+ if self.omit_join:
+ self._init_for_omit_join()
+ else:
+ self._init_for_join()
+
+ def _init_for_omit_join(self):
+ pk_to_fk = dict(
+ self.parent_property._join_condition.local_remote_pairs
+ )
+ pk_to_fk.update(
+ (equiv, pk_to_fk[k])
+ for k in list(pk_to_fk)
+ for equiv in self.parent._equivalent_columns.get(k, ())
+ )
+
+ self._pk_cols = fk_cols = [
+ pk_to_fk[col]
+ for col in self.parent.primary_key if col in pk_to_fk]
+ if len(fk_cols) > 1:
+ self._in_expr = sql.tuple_(*fk_cols)
+ self._zero_idx = False
+ else:
+ self._in_expr = fk_cols[0]
+ self._zero_idx = True
+
+ def _init_for_join(self):
self._parent_alias = aliased(self.parent.class_)
pa_insp = inspect(self._parent_alias)
- self._parent_pk_cols = pk_cols = [
+ self._pk_cols = pk_cols = [
pa_insp._adapt_element(col) for col in self.parent.primary_key]
if len(pk_cols) > 1:
self._in_expr = sql.tuple_(*pk_cols)
for state, overwrite in states
]
- pk_cols = self._parent_pk_cols
- pa = self._parent_alias
+ pk_cols = self._pk_cols
+ in_expr = self._in_expr
+
+ if self.omit_join:
+ # in "omit join" mode, the primary key column and the
+ # "in" expression are in terms of the related entity. So
+ # if the related entity is polymorphic or otherwise aliased,
+ # we need to adapt our "_pk_cols" and "_in_expr" to that
+ # entity. in non-"omit join" mode, these are against the
+ # parent entity and do not need adaption.
+ insp = inspect(effective_entity)
+ if insp.is_aliased_class:
+ pk_cols = [
+ insp._adapt_element(col)
+ for col in pk_cols
+ ]
+ in_expr = insp._adapt_element(in_expr)
+ pk_cols = [insp._adapt_element(col) for col in pk_cols]
q = self._bakery(
lambda session: session.query(
), self
)
+ if self.omit_join:
+ # the Bundle we have in the "omit_join" case is against raw, non
+ # annotated columns, so to ensure the Query knows its primary
+ # entity, we add it explictly. If we made the Bundle against
+ # annotated columns, we hit a performance issue in this specific
+ # case, which is detailed in issue #4347.
+ q.add_criteria(lambda q: q.select_from(effective_entity))
+ else:
+ # in the non-omit_join case, the Bundle is against the annotated/
+ # mapped column of the parent entity, but the #4347 issue does not
+ # occur in this case.
+ pa = self._parent_alias
+ q.add_criteria(
+ lambda q: q.select_from(pa).join(
+ getattr(pa, self.parent_property.key).of_type(
+ effective_entity)
+ )
+ )
+
q.add_criteria(
- lambda q: q.select_from(pa).join(
- getattr(pa,
- self.parent_property.key).of_type(effective_entity)).
- filter(
- self._in_expr.in_(
- sql.bindparam('primary_keys', expanding=True))
- ).order_by(*pk_cols)
- )
+ lambda q: q.filter(
+ in_expr.in_(
+ sql.bindparam("primary_keys", expanding=True))
+ ).order_by(*pk_cols))
orig_query = context.query
)
if self.parent_property.order_by:
- def _setup_outermost_orderby(q):
- # imitate the same method that
- # subquery eager loading does it, looking for the
- # adapted "secondary" table
- eagerjoin = q._from_obj[0]
- eager_order_by = \
- eagerjoin._target_adapter.\
- copy_and_process(
- util.to_list(
- self.parent_property.order_by
+ if self.omit_join:
+ eager_order_by = self.parent_property.order_by
+ if insp.is_aliased_class:
+ eager_order_by = [
+ insp._adapt_element(elem) for elem in
+ eager_order_by
+ ]
+ q.add_criteria(
+ lambda q: q.order_by(*eager_order_by)
+ )
+ else:
+ def _setup_outermost_orderby(q):
+ # imitate the same method that subquery eager loading uses,
+ # looking for the adapted "secondary" table
+ eagerjoin = q._from_obj[0]
+ eager_order_by = \
+ eagerjoin._target_adapter.\
+ copy_and_process(
+ util.to_list(self.parent_property.order_by)
)
- )
- return q.order_by(*eager_order_by)
-
- q.add_criteria(
- _setup_outermost_orderby
- )
+ return q.order_by(*eager_order_by)
+ q.add_criteria(
+ _setup_outermost_orderby
+ )
uselist = self.uselist
_empty_result = () if uselist else None
class SelectinloadRegressionTest(fixtures.DeclarativeMappedTest):
+ """test #4175
+ """
@classmethod
def setup_classes(cls):
Base = cls.DeclarativeBasic
{"primary_keys": [2]}
),
CompiledSQL(
- "SELECT anon_1.a_id AS anon_1_a_id, c.id AS c_id, "
- "c.a_sub_id AS c_a_sub_id FROM (SELECT a.id AS a_id, a.adata "
- "AS a_adata, a.type AS a_type, asub.id AS asub_id, "
- "asub.asubdata AS asub_asubdata FROM a JOIN asub "
- "ON a.id = asub.id) AS anon_1 JOIN c "
- "ON anon_1.asub_id = c.a_sub_id "
- "WHERE anon_1.a_id IN ([EXPANDING_primary_keys]) "
- "ORDER BY anon_1.a_id",
+ # note this links c.a_sub_id to a.id, even though
+ # primaryjoin is to asub.id. this is because the
+ # cols a.id / asub.id are listed in the mapper's
+ # equivalent_columns so they are guaranteed to store
+ # the same value.
+ "SELECT c.a_sub_id AS c_a_sub_id, "
+ "c.id AS c_id "
+ "FROM c WHERE c.a_sub_id "
+ "IN ([EXPANDING_primary_keys]) ORDER BY c.a_sub_id",
{"primary_keys": [2]}
),
),
CompiledSQL(
- "SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
- "FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
- "WHERE a_1.id IN ([EXPANDING_primary_keys]) ORDER BY a_1.id",
+ "SELECT b.a_id AS b_a_id, b.id AS b_id FROM b "
+ "WHERE b.a_id IN ([EXPANDING_primary_keys]) "
+ "ORDER BY b.a_id",
{"primary_keys": [1, 2]}
)
)
{}
),
CompiledSQL(
- "SELECT companies_1.company_id AS companies_1_company_id, "
+ "SELECT people.company_id AS people_company_id, "
"people.person_id AS people_person_id, "
- "people.company_id AS people_company_id, "
"people.name AS people_name, people.type AS people_type "
- "FROM companies AS companies_1 JOIN people "
- "ON companies_1.company_id = people.company_id "
- "WHERE companies_1.company_id IN ([EXPANDING_primary_keys]) "
- "ORDER BY companies_1.company_id, people.person_id",
+ "FROM people WHERE people.company_id "
+ "IN ([EXPANDING_primary_keys]) "
+ "ORDER BY people.company_id, people.person_id",
{"primary_keys": [1, 2]}
),
AllOf(
{"primary_language_1": "java"}
),
CompiledSQL(
- "SELECT people_1.person_id AS people_1_person_id, "
+ "SELECT paperwork.person_id AS paperwork_person_id, "
"paperwork.paperwork_id AS paperwork_paperwork_id, "
- "paperwork.description AS paperwork_description, "
- "paperwork.person_id AS paperwork_person_id "
- "FROM people AS people_1 JOIN paperwork "
- "ON people_1.person_id = paperwork.person_id "
- "WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
- "ORDER BY people_1.person_id, paperwork.paperwork_id",
+ "paperwork.description AS paperwork_description "
+ "FROM paperwork WHERE paperwork.person_id "
+ "IN ([EXPANDING_primary_keys]) "
+ "ORDER BY paperwork.person_id, paperwork.paperwork_id",
[{'primary_keys': [1]}]
)
)
"description_1": "tps report #2"}
),
CompiledSQL(
-
- "SELECT people_1.person_id AS people_1_person_id, "
+ "SELECT paperwork.person_id AS paperwork_person_id, "
"paperwork.paperwork_id AS paperwork_paperwork_id, "
- "paperwork.description AS paperwork_description, "
- "paperwork.person_id AS paperwork_person_id "
- "FROM people AS people_1 JOIN paperwork "
- "ON people_1.person_id = paperwork.person_id "
- "WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
- "ORDER BY people_1.person_id, paperwork.paperwork_id",
+ "paperwork.description AS paperwork_description "
+ "FROM paperwork WHERE paperwork.person_id "
+ "IN ([EXPANDING_primary_keys]) "
+ "ORDER BY paperwork.person_id, paperwork.paperwork_id",
[{'primary_keys': [1]}]
)
)
"engineers.engineer_id ORDER BY engineers.primary_language "
"DESC LIMIT :param_1"),
CompiledSQL(
- "SELECT people_1.person_id AS people_1_person_id, "
+ "SELECT paperwork.person_id AS paperwork_person_id, "
"paperwork.paperwork_id AS paperwork_paperwork_id, "
- "paperwork.description AS paperwork_description, "
- "paperwork.person_id AS paperwork_person_id "
- "FROM people AS people_1 "
- "JOIN paperwork ON people_1.person_id = paperwork.person_id "
- "WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
- "ORDER BY people_1.person_id, paperwork.paperwork_id",
+ "paperwork.description AS paperwork_description "
+ "FROM paperwork WHERE paperwork.person_id "
+ "IN ([EXPANDING_primary_keys]) "
+ "ORDER BY paperwork.person_id, paperwork.paperwork_id",
[{'primary_keys': [1]}]
)
)
"ORDER BY anon_1.engineers_primary_language DESC "
"LIMIT :param_1"),
CompiledSQL(
- "SELECT people_1.person_id AS people_1_person_id, "
+ "SELECT paperwork.person_id AS paperwork_person_id, "
"paperwork.paperwork_id AS paperwork_paperwork_id, "
- "paperwork.description AS paperwork_description, "
- "paperwork.person_id AS paperwork_person_id "
- "FROM people AS people_1 JOIN paperwork "
- "ON people_1.person_id = paperwork.person_id "
- "WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
- "ORDER BY people_1.person_id, paperwork.paperwork_id",
+ "paperwork.description AS paperwork_description "
+ "FROM paperwork WHERE paperwork.person_id "
+ "IN ([EXPANDING_primary_keys]) "
+ "ORDER BY paperwork.person_id, paperwork.paperwork_id",
[{'primary_keys': [1]}]
)
)
"ON people_1.person_id = engineers_1.engineer_id "
"ORDER BY engineers_1.primary_language DESC LIMIT :param_1"),
CompiledSQL(
- "SELECT people_1.person_id AS people_1_person_id, "
+ "SELECT paperwork.person_id AS paperwork_person_id, "
"paperwork.paperwork_id AS paperwork_paperwork_id, "
- "paperwork.description AS paperwork_description, "
- "paperwork.person_id AS paperwork_person_id "
- "FROM people AS people_1 JOIN paperwork "
- "ON people_1.person_id = paperwork.person_id "
- "WHERE people_1.person_id IN ([EXPANDING_primary_keys]) "
- "ORDER BY people_1.person_id, paperwork.paperwork_id",
+ "paperwork.description AS paperwork_description "
+ "FROM paperwork WHERE paperwork.person_id "
+ "IN ([EXPANDING_primary_keys]) "
+ "ORDER BY paperwork.person_id, paperwork.paperwork_id",
[{'primary_keys': [1]}]
)
selectinload(Manager.golf_swing),
).one()
+ # NOTE: we *MUST* do a SQL compare on this one because the adaption
+ # is very sensitive
def go():
eq_(company.employees[0].golf_swing.name, "clubs")
{}
),
CompiledSQL(
- "SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
- "FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
- "WHERE a_1.id IN ([EXPANDING_primary_keys]) "
- "ORDER BY a_1.id, b.id",
+ "SELECT b.a_id AS b_a_id, b.id AS b_id "
+ "FROM b WHERE b.a_id IN "
+ "([EXPANDING_primary_keys]) ORDER BY b.a_id, b.id",
{"primary_keys": list(range(1, 48))}
),
CompiledSQL(
- "SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
- "FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
- "WHERE a_1.id IN ([EXPANDING_primary_keys]) "
- "ORDER BY a_1.id, b.id",
+ "SELECT b.a_id AS b_a_id, b.id AS b_id "
+ "FROM b WHERE b.a_id IN "
+ "([EXPANDING_primary_keys]) ORDER BY b.a_id, b.id",
{"primary_keys": list(range(48, 95))}
),
CompiledSQL(
- "SELECT a_1.id AS a_1_id, b.id AS b_id, b.a_id AS b_a_id "
- "FROM a AS a_1 JOIN b ON a_1.id = b.a_id "
- "WHERE a_1.id IN ([EXPANDING_primary_keys]) "
- "ORDER BY a_1.id, b.id",
+ "SELECT b.a_id AS b_a_id, b.id AS b_id "
+ "FROM b WHERE b.a_id IN "
+ "([EXPANDING_primary_keys]) ORDER BY b.a_id, b.id",
{"primary_keys": list(range(95, 101))}
)
)
{'type_1': 'employer'}
),
CompiledSQL(
- 'SELECT user_1.id AS user_1_id, role.id AS role_id, '
- 'role.user_id AS role_user_id FROM "user" AS user_1 '
- 'JOIN role ON user_1.id = role.user_id WHERE user_1.id IN '
- '([EXPANDING_primary_keys]) '
- 'AND user_1.type IN (:type_1) ORDER BY user_1.id',
- {'primary_keys': [1], 'type_1': 'employer'}
+ "SELECT role.user_id AS role_user_id, role.id AS role_id "
+ "FROM role WHERE role.user_id "
+ "IN ([EXPANDING_primary_keys]) ORDER BY role.user_id",
+ {'primary_keys': [1]}
),
- )
\ No newline at end of file
+ )