This is a real design bug. To analyse any query, please run: EXPLAIN ANALYSE yourSQL query i.e. EXPLAIN ANALYSE SELECT min(account_invoice_report.id) AS id, count(account_invoice_report.id) AS categ_id_count,"account_invoice_report".categ_id, sum("account_invoice_report"."nbr") AS nbr, sum("account_invoice_report"."product_qty") AS product_qty, sum("account_invoice_report"."price_total") AS price_total FROM "account_invoice_report" WHERE ((((account_invoice_report."date" <= '2012-05-18') AND (account_invoice_report."date" >= '2012-01-01')) AND ((account_invoice_report."state" not in ('draft','cancel')) OR account_invoice_report."state" IS NULL)) AND ((account_invoice_report."type" = 'out_invoice') OR (account_invoice_report."type" = 'out_refund'))) GROUP BY "account_invoice_report".categ_id On my server, even with on 3000 clients ... the query returns a result in 20 seconds. "HashAggregate (cost=267661920.65..267661925.15 rows=200 width=32) (actual time=20619.984..20619.986 rows=2 loops=1)" " -> GroupAggregate (cost=2363456.42..267585641.15 rows=3051180 width=184) (actual time=20459.610..20617.810 rows=1257 loops=1)" " -> Sort (cost=2363456.42..2371084.37 rows=3051180 width=184) (actual time=20459.384..20459.784 rows=1265 loops=1)" " Sort Key: ail.product_id, ai.date_invoice, ai.id, cr.rate, (to_char((ai.date_invoice)::timestamp with time zone, 'YYYY'::text)), (to_char((ai.date_invoice)::timestamp with time zone, 'MM'::text)), (to_char((ai.date_invoice)::timestamp with time zone, 'YYYY-MM-DD'::text)), ai.partner_id, ai.payment_term, ai.period_id, u.name, ai.currency_id, ai.journal_id, ai.fiscal_position, ai.user_id, ai.company_id, ai.type, ai.state, pt.categ_id, ai.date_due, ai.address_contact_id, ai.address_invoice_id, ai.account_id, ai.partner_bank_id, ai.residual, ai.amount_total, u.uom_type, u.category_id" " Sort Method: quicksort Memory: 701kB" " -> Merge Join (cost=0.00..1492526.05 rows=3051180 width=184) (actual time=11.478..20452.364 rows=1265 loops=1)" " Merge Cond: (ail.invoice_id = ai.id)" " -> Nested Loop Left Join (cost=0.00..1475.72 rows=3084 width=86) (actual time=0.067..25.090 rows=2646 loops=1)" " Join Filter: (u.id = ail.uos_id)" " -> Nested Loop Left Join (cost=0.00..1058.26 rows=3084 width=68) (actual time=0.047..15.025 rows=2646 loops=1)" " -> Index Scan using account_invoice_line_invoice_id_index on account_invoice_line ail (cost=0.00..170.50 rows=3084 width=64) (actual time=0.034..3.749 rows=2646 loops=1)" " -> Index Scan using product_template_pkey on product_template pt (cost=0.00..0.28 rows=1 width=8) (actual time=0.002..0.003 rows=1 loops=2646)" " Index Cond: (pt.id = ail.product_id)" " -> Materialize (cost=0.00..1.14 rows=9 width=26) (actual time=0.000..0.001 rows=9 loops=2646)" " -> Seq Scan on product_uom u (cost=0.00..1.09 rows=9 width=26) (actual time=0.004..0.010 rows=9 loops=1)" " -> Materialize (cost=0.00..1416063.69 rows=1565388 width=102) (actual time=1.818..20416.193 rows=1264 loops=1)" " -> Nested Loop (cost=0.00..1412150.22 rows=1565388 width=102) (actual time=1.812..20415.160 rows=445 loops=1)" " Join Filter: (SubPlan 6)" " -> Index Scan using account_invoice_pkey on account_invoice ai (cost=0.00..145.14 rows=354 width=92) (actual time=0.867..4.586 rows=445 loops=1)" " Filter: ((date_invoice <= '2012-05-18'::date) AND (date_invoice >= '2012-01-01'::date) AND (((state)::text <> ALL ('{draft,cancel}'::text[])) OR (state IS NULL)) AND (((type)::text = 'out_invoice'::text) OR ((type)::text = 'out_refund'::text)))" " -> Materialize (cost=0.00..206.66 rows=8844 width=18) (actual time=0.000..0.798 rows=8763 loops=445)" " -> Seq Scan on res_currency_rate cr (cost=0.00..162.44 rows=8844 width=18) (actual time=0.009..2.100 rows=8763 loops=1)" " SubPlan 6" " -> Limit (cost=0.01..0.86 rows=1 width=4) (actual time=0.005..0.005 rows=1 loops=3899535)" " -> Result (cost=0.01..184.56 rows=216 width=4) (actual time=0.004..0.004 rows=1 loops=3899535)" " One-Time Filter: ((($3 IS NOT NULL) AND ($4 <= $3)) OR (($3 IS NULL) AND ($4 <= now())))" " -> Seq Scan on res_currency_rate cr2 (cost=0.01..184.56 rows=216 width=4) (actual time=0.007..0.007 rows=1 loops=2354355)" " Filter: (currency_id = $2)" " SubPlan 1" " -> Seq Scan on product_uom (cost=0.00..1.14 rows=1 width=3) (never executed)" " Filter: (((uom_type)::text = 'reference'::text) AND (category_id = $0))" " SubPlan 2" " -> Aggregate (cost=16.62..16.63 rows=1 width=4) (actual time=0.009..0.009 rows=1 loops=1257)" " -> Nested Loop (cost=0.00..16.61 rows=2 width=4) (actual time=0.006..0.007 rows=4 loops=1257)" " -> Index Scan using account_invoice_pkey on account_invoice a (cost=0.00..8.27 rows=1 width=4) (actual time=0.003..0.003 rows=1 loops=1257)" " Index Cond: (id = $1)" " -> Index Scan using account_invoice_line_invoice_id_index on account_invoice_line l (cost=0.00..8.32 rows=2 width=8) (actual time=0.002..0.002 rows=4 loops=1257)" " Index Cond: (l.invoice_id = $1)" " SubPlan 3" " -> Aggregate (cost=26.11..26.14 rows=1 width=12) (actual time=0.054..0.054 rows=1 loops=1265)" " -> Nested Loop Left Join (cost=0.00..26.10 rows=6 width=12) (actual time=0.008..0.022 rows=22 loops=1265)" " Join Filter: (a.id = l.invoice_id)" " -> Nested Loop (cost=0.00..17.68 rows=3 width=8) (actual time=0.005..0.009 rows=5 loops=1265)" " -> Index Scan using account_invoice_pkey on account_invoice a (cost=0.00..8.27 rows=1 width=8) (actual time=0.002..0.002 rows=1 loops=1265)" " Index Cond: (id = $1)" " -> Index Scan using account_move_line_move_id_index on account_move_line aml (cost=0.00..9.38 rows=3 width=8) (actual time=0.002..0.003 rows=5 loops=1265)" " Index Cond: (aml.move_id = a.move_id)" " -> Materialize (cost=0.00..8.33 rows=2 width=12) (actual time=0.001..0.001 rows=4 loops=6590)" " -> Index Scan using account_invoice_line_invoice_id_index on account_invoice_line l (cost=0.00..8.32 rows=2 width=12) (actual time=0.002..0.003 rows=4 loops=1260)" " Index Cond: (invoice_id = $1)" " SubPlan 4" " -> Aggregate (cost=26.11..26.14 rows=1 width=8) (actual time=0.037..0.037 rows=1 loops=1265)" " -> Nested Loop Left Join (cost=0.00..26.10 rows=6 width=8) (actual time=0.009..0.021 rows=22 loops=1265)" " Join Filter: (a.id = l.invoice_id)" " -> Nested Loop (cost=0.00..17.68 rows=3 width=12) (actual time=0.005..0.008 rows=5 loops=1265)" " -> Index Scan using account_invoice_pkey on account_invoice a (cost=0.00..8.27 rows=1 width=16) (actual time=0.002..0.002 rows=1 loops=1265)" " Index Cond: (id = $1)" " -> Index Scan using account_move_line_move_id_index on account_move_line aml (cost=0.00..9.38 rows=3 width=4) (actual time=0.002..0.004 rows=5 loops=1265)" " Index Cond: (aml.move_id = a.move_id)" " -> Materialize (cost=0.00..8.33 rows=2 width=4) (actual time=0.001..0.001 rows=4 loops=6590)" " -> Index Scan using account_invoice_line_invoice_id_index on account_invoice_line l (cost=0.00..8.32 rows=2 width=4) (actual time=0.002..0.004 rows=4 loops=1260)" " Index Cond: (invoice_id = $1)" " SubPlan 5" " -> Aggregate (cost=16.62..16.63 rows=1 width=4) (actual time=0.007..0.007 rows=1 loops=1257)" " -> Nested Loop (cost=0.00..16.61 rows=2 width=4) (actual time=0.004..0.006 rows=4 loops=1257)" " -> Index Scan using account_invoice_pkey on account_invoice a (cost=0.00..8.27 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=1257)" " Index Cond: (id = $1)" " -> Index Scan using account_invoice_line_invoice_id_index on account_invoice_line l (cost=0.00..8.32 rows=2 width=8) (actual time=0.001..0.002 rows=4 loops=1257)" " Index Cond: (l.invoice_id = $1)" "Total runtime: 20621.529 ms" Let's try to understand what is going on. account_invoice_report is a view, which definition is: -- View: account_invoice_report CREATE OR REPLACE VIEW account_invoice_report AS SELECT min(ail.id) AS id, ai.date_invoice AS date, to_char(ai.date_invoice::timestamp with time zone, 'YYYY'::text) AS year, to_char(ai.date_invoice::timestamp with time zone, 'MM'::text) AS month, to_char(ai.date_invoice::timestamp with time zone, 'YYYY-MM-DD'::text) AS day, ail.product_id, ai.partner_id, ai.payment_term, ai.period_id, CASE WHEN u.uom_type::text <> 'reference'::text THEN ( SELECT product_uom.name FROM product_uom WHERE product_uom.uom_type::text = 'reference'::text AND product_uom.category_id = u.category_id) ELSE u.name END AS uom_name, ai.currency_id, ai.journal_id, ai.fiscal_position, ai.user_id, ai.company_id, count(ail.*) AS nbr, ai.type, ai.state, pt.categ_id, ai.date_due, ai.address_contact_id, ai.address_invoice_id, ai.account_id, ai.partner_bank_id, sum( CASE WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying, 'in_invoice'::character varying]::text[]) THEN ail.quantity / u.factor::double precision * (-1)::double precision ELSE ail.quantity / u.factor::double precision END) AS product_qty, sum( CASE WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying, 'in_invoice'::character varying]::text[]) THEN ail.quantity * ail.price_unit::double precision * (-1)::double precision ELSE ail.quantity * ail.price_unit::double precision END) / cr.rate::double precision AS price_total, sum( CASE WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying, 'in_invoice'::character varying]::text[]) THEN ai.amount_total * (-1)::numeric ELSE ai.amount_total END) / (( SELECT count(l.id) AS count FROM account_invoice_line l LEFT JOIN account_invoice a ON a.id = l.invoice_id WHERE a.id = ai.id))::numeric / cr.rate AS price_total_tax, CASE WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying, 'in_invoice'::character varying]::text[]) THEN sum(ail.quantity * ail.price_unit::double precision * (-1)::double precision) ELSE sum(ail.quantity * ail.price_unit::double precision) END / CASE WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying, 'in_invoice'::character varying]::text[]) THEN sum(ail.quantity / u.factor::double precision * (-1)::double precision) ELSE sum(ail.quantity / u.factor::double precision) END / cr.rate::double precision AS price_average, cr.rate AS currency_rate, sum(( SELECT date_part('epoch'::text, avg(date_trunc('day'::text, aml.date_created::timestamp with time zone) - date_trunc('day'::text, l.create_date)::timestamp with time zone)) / (24 * 60 * 60)::numeric(16,2)::double precision FROM account_move_line aml LEFT JOIN account_invoice a ON a.move_id = aml.move_id LEFT JOIN account_invoice_line l ON a.id = l.invoice_id WHERE a.id = ai.id)) AS delay_to_pay, sum(( SELECT date_part('epoch'::text, avg(date_trunc('day'::text, a.date_due::timestamp with time zone) - date_trunc('day'::text, a.date_invoice::timestamp with time zone))) / (24 * 60 * 60)::numeric(16,2)::double precision FROM account_move_line aml LEFT JOIN account_invoice a ON a.move_id = aml.move_id LEFT JOIN account_invoice_line l ON a.id = l.invoice_id WHERE a.id = ai.id)) AS due_delay, CASE WHEN ai.type::text = ANY (ARRAY['out_refund'::character varying, 'in_invoice'::character varying]::text[]) THEN ai.residual * (-1)::numeric ELSE ai.residual END / (( SELECT count(l.id) AS count FROM account_invoice_line l LEFT JOIN account_invoice a ON a.id = l.invoice_id WHERE a.id = ai.id))::numeric / cr.rate AS residual FROM account_invoice_line ail LEFT JOIN account_invoice ai ON ai.id = ail.invoice_id LEFT JOIN product_template pt ON pt.id = ail.product_id LEFT JOIN product_uom u ON u.id = ail.uos_id, res_currency_rate cr WHERE (cr.id IN ( SELECT cr2.id FROM res_currency_rate cr2 WHERE cr2.currency_id = ai.currency_id AND (ai.date_invoice IS NOT NULL AND cr.name <= ai.date_invoice OR ai.date_invoice IS NULL AND cr.name <= now()) LIMIT 1)) GROUP BY ail.product_id, ai.date_invoice, ai.id, cr.rate, to_char(ai.date_invoice::timestamp with time zone, 'YYYY'::text), to_char(ai.date_invoice::timestamp with time zone, 'MM'::text), to_char(ai.date_invoice::timestamp with time zone, 'YYYY-MM-DD'::text), ai.partner_id, ai.payment_term, ai.period_id, u.name, ai.currency_id, ai.journal_id, ai.fiscal_position, ai.user_id, ai.company_id, ai.type, ai.state, pt.categ_id, ai.date_due, ai.address_contact_id, ai.address_invoice_id, ai.account_id, ai.partner_bank_id, ai.residual, ai.amount_total, u.uom_type, u.category_id; ALTER TABLE account_invoice_report OWNER TO openerp; First, there are design problems in the view. 1) In a view, you never use conversion to cast a type. to_char(ai.date_invoice::timestamp with time zone, 'YYYY'::text) AS year In PostgreSQL, casting is done automatically in queries, not views. 2) use of case ... will make the database run sequential scans. This should be replaced either with a UNION/INTERSEC or temporary table. There is no possibility to make this query run better. This is really an incledible query, as it tries to achieve something relatively easy but breaks nearly ALL SQL optimization rules.