How to avoiding n+1 queries in Odoo?


✅ What is the N+1 Query Problem?

The N+1 problem occurs when your code executes 1 query for a list, and then 1 additional query per item in that list. This results in many unnecessary database hits, severely affecting performance.



📌 Realistic Example: N+1 Issue in Computed Fields


❌ Inefficient Code (N+1 Issue):


class ResPartner(models.Model):

    _inherit = 'res.partner'

    sale_count = fields.Integer(compute='_compute_sale_count')

    def _compute_sale_count(self):

        for partner in self:

            partner.sale_count = self.env['sale.order'].search_count([

                ('partner_id', '=', partner.id)

            ])


For 100 partners, this will run 100 separate queries, one for each partner.id.


✅ Optimized Code (Avoiding N+1):


def _compute_sale_count(self):

    sale_data = self.env['sale.order'].read_group(

        [('partner_id', 'in', self.ids)],

        ['partner_id'],

        ['partner_id']

    )

    mapped_data = {item['partner_id'][0]: item['partner_id_count'] for item in sale_data}

    for partner in self:

        partner.sale_count = mapped_data.get(partner.id, 0)


  • Uses a single aggregated query.
  • Maps data by partner_id to set values in memory.
  • Significantly improves performance for large datasets.



📌 Another Example: N+1 in Loops


❌ Inefficient:

for order in self.env['sale.order'].search([]):

    print(order.partner_id.name)

  • Each order.partner_id.name triggers an additional query if not prefetched.


✅ Optimized:


orders = self.env['sale.order'].search([], prefetch_fields=False)

orders.with_context(prefetch_fields=['partner_id', 'partner_id.name'])

for order in orders:

    print(order.partner_id.name)

OR simply:

orders = self.env['sale.order'].search([], order='id')

for order in orders:

    print(order.partner_id.name)

  • By default, Odoo ORM does prefetching efficiently for related fields.
  • Avoid using .search() inside a loop — use read_group, mapped, or batch logic.


🔁 Best Practices to Avoid N+1


Use Case

Instead of

Use

Count related records

Loop with search_count()

read_group()

Access relational fields in loop

for rec in ...: rec.related.name

Ensure prefetching

Looping inside computed fields

Per-record query

Grouped/batch queries

Load related records

search() in loop

Use browse() or mapped()



✅ Summary Table


Symptom

Cause

Solution

Many DB hits in logs

search() inside loops

Use read_group()

Slow computed field

Query per record

Prefetch & aggregate queries

Loop with relational fields

Lazy loading per iteration

Prefetch or use .mapped()