Use indexes and cache to make search on amounts faster

This optimization relies on the idea that few invoices should stay in the
states where the amount cache fields are empty. It splits the search query in
two parts: one on the cached fields, the other on the computation with the
invoice lines (and tax lines when it applies).

Closes #11919
This commit is contained in:
Nicolas Évrard
2023-05-27 09:53:47 +02:00
parent d894c1f442
commit 91ed1cddc1
+48 -4
View File
@@ -5,9 +5,11 @@ from collections import defaultdict, namedtuple
from decimal import Decimal
from itertools import chain, combinations, groupby
from sql import Literal, Null
from sql.aggregate import Sum
from sql.conditionals import Coalesce
from sql.functions import CharLength, Round
from sql.operators import Exists
from trytond import backend
from trytond.config import config
@@ -303,6 +305,24 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
t,
(t.state, Index.Equality()),
where=t.state.in_(['draft', 'validated', 'posted'])),
Index(t, (t.total_amount_cache, Index.Range())),
Index(
t,
(t.total_amount_cache, Index.Equality()),
include=[t.id],
where=t.total_amount_cache == Null),
Index(t, (t.untaxed_amount_cache, Index.Range())),
Index(
t,
(t.untaxed_amount_cache, Index.Equality()),
include=[t.id],
where=t.untaxed_amount_cache == Null),
Index(t, (t.tax_amount_cache, Index.Range())),
Index(
t,
(t.tax_amount_cache, Index.Equality()),
include=[t.id],
where=t.tax_amount_cache == Null),
})
cls._check_modify_exclude = {
'state', 'alternative_payees', 'payment_lines',
@@ -799,12 +819,22 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
Coalesce(Sum(Round((line.quantity * line.unit_price).cast(
type_name),
currency.digits)), 0).as_('total_amount'),
where=line.invoice.in_(invoice_query),
where=(line.invoice.in_(invoice_query)
& (invoice.total_amount_cache == Null)),
group_by=line.invoice)
| tax.select(tax.invoice.as_('invoice'),
Coalesce(Sum(tax.amount), 0).as_('total_amount'),
where=tax.invoice.in_(invoice_query),
where=(tax.invoice.in_(invoice_query)
& ~Exists(invoice.select(Literal(1),
where=(invoice.total_amount_cache == Null)
& (invoice.id == tax.invoice)))),
group_by=tax.invoice))
union |= invoice.select(
invoice.id.as_('invoice'),
invoice.total_amount_cache.as_('total_amount'),
where=(invoice.id.in_(invoice_query)
& (invoice.total_amount_cache != Null)
& Operator(invoice.total_amount_cache.cast(type_name), value)))
query = union.select(union.invoice, group_by=union.invoice,
having=Operator(Sum(union.total_amount).cast(type_name),
value))
@@ -834,13 +864,18 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
).join(currency,
condition=(currency.id == invoice.currency)
).select(line.invoice,
where=line.invoice.in_(invoice_query),
where=(line.invoice.in_(invoice_query)
& (invoice.untaxed_amount_cache == Null)),
group_by=line.invoice,
having=Operator(Coalesce(Sum(
Round((line.quantity * line.unit_price).cast(
type_name),
currency.digits)), 0).cast(type_name),
value))
query |= invoice.select(invoice.id,
where=invoice.id.in_(invoice_query)
& (invoice.untaxed_amount_cache != Null)
& Operator(invoice.untaxed_amount_cache.cast(type_name), value))
return [('id', 'in', query)]
@classmethod
@@ -848,8 +883,10 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
pool = Pool()
Rule = pool.get('ir.rule')
Tax = pool.get('account.invoice.tax')
Invoice = pool.get('account.invoice')
type_name = cls.tax_amount._field.sql_type().base
tax = Tax.__table__()
invoice = Invoice.__table__()
_, operator, value = clause
invoice_query = Rule.query_get('account.invoice')
@@ -859,10 +896,17 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
value = float(value)
query = tax.select(tax.invoice,
where=tax.invoice.in_(invoice_query),
where=(tax.invoice.in_(invoice_query)
& ~Exists(invoice.select(Literal(1),
where=(invoice.tax_amount_cache == Null)
& (invoice.id == tax.invoice)))),
group_by=tax.invoice,
having=Operator(Coalesce(Sum(tax.amount), 0).cast(type_name),
value))
query |= invoice.select(invoice.id,
where=invoice.id.in_(invoice_query)
& (invoice.tax_amount_cache != Null)
& Operator(invoice.tax_amount_cache.cast(type_name), value))
return [('id', 'in', query)]
def get_allow_cancel(self, name):