microsoft/mssql-django

Using Django 4.2 with MSSQL 2019 Aggregation Containing Subquery Fails

haldunk opened this issue · 1 comments

There are some features which are not supported yet. Please check the Limitations first to see if your bug is listed.

Software versions

  • Django: 4.2
  • mssql-django: 1.3
  • python: 3.8
  • SQL Server: 2019
  • OS: Linux amd64

Table schema and Model

class Exchange(models.Model):
date = models.DateField()
value = models.FloatField()

class Invoice(models.Model):
date = models.DateField()
gross = models.FloatField()

Database Connection Settings

'default': {
'ENGINE': 'mssql',
'NAME': 'db',
'USER': 'SA',
'PASSWORD': '555',
'HOST': 'mssql\MSSQLSERVER',
'PORT': '1433',
'AUTOCOMMIT': True,
'OPTIONS': {
'driver': 'ODBC Driver 17 for SQL Server',
'unicode_results': True,
'host_is_server': True,
'extra_params': 'tds_version=7.4;',
'use_legacy_datetime': False,
'connection_timeout': 300,
'connection_retries': 10,
'connection_retry_backoff_time': 5,
'query_timeout': 300,
},

Problem description and steps to reproduce

Aggregation in a query employing a subquery expression fails with the following error:

ProgrammingError: ('42000', '[42000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Cannot perform an aggregate function on an expression containing an aggregate or a subquery. (130) (SQLExecDirectW)')

However, the same query works in Django 4.1.10.

exchange = Subquery(Exchange.objects.filter(date__lte=OuterRef('date')).order_by('-date').values('value')[:1])

Invoice.objects.annotate(
exchange=exchange,
gross_currency=F('gross') / F('exchange')
).aggregate(
avg_gross_currency=Avg('gross_currency')
)

Expected behavior and actual behavior

Error message/stack trace


ProgrammingError Traceback (most recent call last)
File /usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py:89, in CursorWrapper._execute(self, sql, params, *ignored_wrapper_args)
88 else:
---> 89 return self.cursor.execute(sql, params)

File /usr/local/lib/python3.8/dist-packages/mssql/base.py:619, in CursorWrapper.execute(self, sql, params)
618 try:
--> 619 return self.cursor.execute(sql, params)
620 except Database.Error as e:

ProgrammingError: ('42000', '[42000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Cannot perform an aggregate function on an expression containing an aggregate or a subquery. (130) (SQLExecDirectW)')

The above exception was the direct cause of the following exception:

ProgrammingError Traceback (most recent call last)
Cell In[10], line 1
----> 1 july_invoices.annotate(net_eur=F('net_lira')/eurtry).aggregate(a_net_eur=Avg('net_eur'))

File /usr/local/lib/python3.8/dist-packages/django/db/models/query.py:592, in QuerySet.aggregate(self, *args, **kwargs)
589 raise TypeError("Complex aggregates require an alias")
590 kwargs[arg.default_alias] = arg
--> 592 return self.query.chain().get_aggregation(self.db, kwargs)

File /usr/local/lib/python3.8/dist-packages/django/db/models/sql/query.py:554, in Query.get_aggregation(self, using, aggregate_exprs)
552 outer_query.select_related = False
553 compiler = outer_query.get_compiler(using, elide_empty=elide_empty)
--> 554 result = compiler.execute_sql(SINGLE)
555 if result is None:
556 result = empty_set_result

File /usr/local/lib/python3.8/dist-packages/django/db/models/sql/compiler.py:1562, in SQLCompiler.execute_sql(self, result_type, chunked_fetch, chunk_size)
1560 cursor = self.connection.cursor()
1561 try:
-> 1562 cursor.execute(sql, params)
1563 except Exception:
1564 # Might fail for server-side cursors (e.g. connection closed)
1565 cursor.close()

File /usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py:67, in CursorWrapper.execute(self, sql, params)
66 def execute(self, sql, params=None):
---> 67 return self._execute_with_wrappers(
68 sql, params, many=False, executor=self._execute
69 )

File /usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py:80, in CursorWrapper._execute_with_wrappers(self, sql, params, many, executor)
78 for wrapper in reversed(self.db.execute_wrappers):
79 executor = functools.partial(wrapper, executor)
---> 80 return executor(sql, params, many, context)

File /usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py:89, in CursorWrapper._execute(self, sql, params, *ignored_wrapper_args)
87 return self.cursor.execute(sql)
88 else:
---> 89 return self.cursor.execute(sql, params)

File /usr/local/lib/python3.8/dist-packages/django/db/utils.py:91, in DatabaseErrorWrapper.exit(self, exc_type, exc_value, traceback)
89 if dj_exc_type not in (DataError, IntegrityError):
90 self.wrapper.errors_occurred = True
---> 91 raise dj_exc_value.with_traceback(traceback) from exc_value

File /usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py:89, in CursorWrapper._execute(self, sql, params, *ignored_wrapper_args)
87 return self.cursor.execute(sql)
88 else:
---> 89 return self.cursor.execute(sql, params)

File /usr/local/lib/python3.8/dist-packages/mssql/base.py:619, in CursorWrapper.execute(self, sql, params)
617 self.last_params = params
618 try:
--> 619 return self.cursor.execute(sql, params)
620 except Database.Error as e:
621 self.connection._on_error(e)

ProgrammingError: ('42000', '[42000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Cannot perform an aggregate function on an expression containing an aggregate or a subquery. (130) (SQLExecDirectW)')

Any other details that can be helpful

mShan0 commented

Thanks for the report. Will look into this