diff options
| author | Simon Charette <charette.s@gmail.com> | 2020-10-25 16:04:21 -0400 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2020-10-28 07:22:00 +0100 |
| commit | 8593e162c9cb63a6c0b06daf045bc1c21eb4d7c1 (patch) | |
| tree | 96f5b36abb2935c37af1506cdd69ddb541d12ac1 /django/db/models/sql | |
| parent | bbf141bcdc31f1324048af9233583a523ac54c94 (diff) | |
Fixed #32143 -- Used EXISTS to exclude multi-valued relationships.
As mentioned in the pre-existing split_exclude() docstring EXISTS is
easier to optimize for query planers and circumvents the IN (NULL)
handling issue.
Diffstat (limited to 'django/db/models/sql')
| -rw-r--r-- | django/db/models/sql/query.py | 32 |
1 files changed, 14 insertions, 18 deletions
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 8d76b436ee..abb545eaa4 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -23,7 +23,9 @@ from django.core.exceptions import ( from django.db import DEFAULT_DB_ALIAS, NotSupportedError, connections from django.db.models.aggregates import Count from django.db.models.constants import LOOKUP_SEP -from django.db.models.expressions import BaseExpression, Col, F, OuterRef, Ref +from django.db.models.expressions import ( + BaseExpression, Col, Exists, F, OuterRef, Ref, ResolvedOuterRef, +) from django.db.models.fields import Field from django.db.models.fields.related_lookups import MultiColSource from django.db.models.lookups import Lookup @@ -1765,12 +1767,12 @@ class Query(BaseExpression): filters in the original query. We will turn this into equivalent of: - WHERE NOT (pk IN (SELECT parent_id FROM thetable - WHERE name = 'foo' AND parent_id IS NOT NULL)) - - It might be worth it to consider using WHERE NOT EXISTS as that has - saner null handling, and is easier for the backend's optimizer to - handle. + WHERE NOT EXISTS( + SELECT 1 + FROM child + WHERE name = 'foo' AND child.parent_id = parent.id + LIMIT 1 + ) """ filter_lhs, filter_rhs = filter_expr if isinstance(filter_rhs, OuterRef): @@ -1786,17 +1788,9 @@ class Query(BaseExpression): # the subquery. trimmed_prefix, contains_louter = query.trim_start(names_with_path) - # Add extra check to make sure the selected field will not be null - # since we are adding an IN <subquery> clause. This prevents the - # database from tripping over IN (...,NULL,...) selects and returning - # nothing col = query.select[0] select_field = col.target alias = col.alias - if self.is_nullable(select_field): - lookup_class = select_field.get_lookup('isnull') - lookup = lookup_class(select_field.get_col(alias), False) - query.where.add(lookup, AND) if alias in can_reuse: pk = select_field.model._meta.pk # Need to add a restriction so that outer query's filters are in effect for @@ -1810,9 +1804,11 @@ class Query(BaseExpression): query.where.add(lookup, AND) query.external_aliases[alias] = True - condition, needed_inner = self.build_filter( - ('%s__in' % trimmed_prefix, query), - current_negated=True, branch_negated=True, can_reuse=can_reuse) + lookup_class = select_field.get_lookup('exact') + lookup = lookup_class(col, ResolvedOuterRef(trimmed_prefix)) + query.where.add(lookup, AND) + condition, needed_inner = self.build_filter(Exists(query)) + if contains_louter: or_null_condition, _ = self.build_filter( ('%s__isnull' % trimmed_prefix, True), |
