diff --git a/tortoise/queryset.py b/tortoise/queryset.py index fe7d4a3f7..5dd0526f9 100644 --- a/tortoise/queryset.py +++ b/tortoise/queryset.py @@ -1428,15 +1428,50 @@ def __init__( def _make_query(self) -> None: self.query = copy(self.model._meta.basequery) - if self.capabilities.support_update_limit_order_by and self._limit: - self.query._limit = self.query._wrapper_cls(self._limit) + self.resolve_filters() + + # If joins are detected, rewrite into an IN subquery to avoid JOINs in DELETE + if self._joined_tables: + pk_column = self.model._meta.db_pk_column + + # Create a pristine, completely separate SELECT query builder + subquery = self._db.query_class.from_(self.model._meta.basetable).select( + self.model._meta.basetable[pk_column] + ) + + # Transfer the resolved wheres and havings criteria from our filter resolution + subquery._wheres = self.query._wheres + subquery._havings = self.query._havings + + # Apply joins directly to the subquery builder + subquery._joins = self.query._joins + + # Re-apply limits and sorting to the subquery if needed + if self._limit: + subquery._limit = subquery._wrapper_cls(self._limit) + self.resolve_ordering( model=self.model, table=self.model._meta.basetable, orderings=self._orderings, annotations=self._annotations, ) - self.resolve_filters() + subquery._orderbys = self.query._orderbys + + # Reconstruct clean pristine delete statement pointing to our subquery + self.query = copy(self.model._meta.basequery) + self.query = self.query.where(self.model._meta.basetable[pk_column].isin(subquery)) + else: + # Traditional optimization path if no backward relations/joins exist + if self.capabilities.support_update_limit_order_by and self._limit: + self.query._limit = self.query._wrapper_cls(self._limit) + self.resolve_ordering( + model=self.model, + table=self.model._meta.basetable, + orderings=self._orderings, + annotations=self._annotations, + ) + self.query._delete_from = True return