pypika
pypika copied to clipboard
Subquery failing when used in outer where clause
I wrote the 2 unit tests below to illustrate an issue I am facing with subqueries used within the where clause of an outer query.
My expectation is that I should be able to use a subquery anywhere I can use any other field or function. However, use within the where clause is failing to produce a viable query.
class SubQueryTests(unittest.TestCase):
table_a, table_b, table_c, table_d = Tables("a", "b", "c", "d")
subquery_select_column = Field("selected_from_subquery", table=table_a)
subquery_join_column = Field("id", table=table_a)
outer_query_join_column = Field("a_id", table=table_b)
subquery = (
Query.from_(table_a).where(subquery_join_column == outer_query_join_column).select(subquery_select_column).as_("a")
)
# #1 - results in AttributeError: 'bool' object has no attribute 'fields_'
def test_subquery_in_outer_where_clause(self):
outer_query = Query.from_(self.table_b).where(self.subquery == 1).select("id")
self.assertEqual(
'SELECT "id" FROM "b" WHERE "(SELECT "a"."selected_from_subquery" FROM "a" WHERE "a"."id"="b"."a_id")"=1', str(outer_query)
)
# #2 - Field class wrapper helps but results in a malformed query because the subquery is missing parens
def test_subquery_in_outer_where_clause_field_wrapped(self):
outer_query = Query.from_(self.table_b).where(Field(self.subquery) == 1).select("id")
self.assertEqual(
'SELECT "id" FROM "b" WHERE "(SELECT "a"."selected_from_subquery" FROM "a" WHERE "a"."id"="b"."a_id")"=1', str(outer_query)
)
# #3 - results in AttributeError: 'bool' object has no attribute 'fields_'
def test_subquery_in_outer_where_clause_get_sql_method(self):
outer_query = Query.from_(self.table_b).where(self.subquery.get_sql(subquery=True) == 1).select("id")
self.assertEqual(
'SELECT "id" FROM "b" WHERE "(SELECT "a"."selected_from_subquery" FROM "a" WHERE "a"."id"="b"."a_id")"=1', str(outer_query)
)
# #4 successful when subquery=True passed and the subquery is wrapped in Field class
def test_subquery_in_outer_where_clause_get_sql_method_field_wrapped(self):
outer_query = Query.from_(self.table_b).where(Field(self.subquery.get_sql(subquery=True)) == 1).select("id")
self.assertEqual(
'SELECT "id" FROM "b" WHERE "(SELECT "a"."selected_from_subquery" FROM "a" WHERE "a"."id"="b"."a_id")"=1', str(outer_query)
)
# #5 an example where subquery works fine as a selected field
def test_subquery_in_outer_select(self):
outer_query = Query.from_(self.table_b).select(Field(self.subquery))
self.assertEqual(
'SELECT (SELECT "a"."selected_from_subquery" FROM "a" WHERE "a"."id"="b"."a_id") FROM "b"', str(outer_query)
)
# #6 example where subquery works fine within a function
def test_subquery_in_function(self):
outer_query = Query.from_(self.table_b).select(functions.Upper(self.subquery))
self.assertEqual(
'SELECT UPPER((SELECT "a"."selected_from_subquery" FROM "a" WHERE "a"."id"="b"."a_id")) FROM "b"',
str(outer_query),
)
My current workaround is to override the get_sql
method inside my project and useQuery
and QueryBuilder
as superclasses like the below examples. I would prefer to not do this and can try to help with a solution that works better than this.
class SubQuery(Query):
@classmethod
def _builder(cls):
return SubQueryBuilder()
class SubQueryBuilder(QueryBuilder):
def get_sql(self, with_alias=False, subquery=False, **kwargs):
_subquery = True
return super().get_sql(with_alias=with_alias, subquery=_subquery, **kwargs)