[Solved] Postgresql Query Optimization with many left joins

How to optimize this SQL query?

In case you have your own slow SQL query, you can optimize it automatically here.

For the query above, the following recommendations will be helpful as part of the SQL tuning process.
You'll find 3 sections below:

  1. Description of the steps you can take to speed up the query.
  2. The optimal indexes for this query, which you can copy and create in your database.
  3. An automatically re-written query you can copy and execute in your database.
The optimization process and recommendations:
  1. Avoid OFFSET In LIMIT Clause (query line: 120): OFFSET clauses can be very slow when used with high offsets (e.g. with high page numbers when implementing paging). Instead, use the following \u003ca target\u003d"_blank" href\u003d"http://www.eversql.com/faster-pagination-in-mysql-why-order-by-with-limit-and-offset-is-slow/"\u003eseek method\u003c/a\u003e, which provides better and more stable response rates.
  2. Create Optimal Indexes (modified query below): The recommended indexes are an integral part of this optimization effort and should be created before testing the execution duration of the optimized query.
  3. Prefer Inner Join Over Left Join (modified query below): We identified that one or more left joined entities (e.g. `task_status`) are used in the 'where' clause, in a way that allows to replace it with an optimized inner join. Inner joins can be fully optimized by the database, while Left joins apply limitations on the database's optimizer.
  4. Prefer Inner Join Over Left Join (modified query below): We identified that one or more left joined entities (e.g. `fos_user`) are used in the 'where' clause, in a way that allows to replace it with an optimized inner join. Inner joins can be fully optimized by the database, while Left joins apply limitations on the database's optimizer.
  5. Remove Redundant Left Joins (modified query below): Redundant LEFT JOINs (e.g. `task_comment`) were detected in the query. Removing them will result in a performance improvement. In some cases, JOINs become redundant after an optimization is applied, such as when converting OR conditions to a UNION clause.
  6. Remove Redundant Left Joins (modified query below): Redundant LEFT JOINs (e.g. `task_file`) were detected in the query. Removing them will result in a performance improvement. In some cases, JOINs become redundant after an optimization is applied, such as when converting OR conditions to a UNION clause.
  7. Remove Redundant Left Joins (modified query below): Redundant LEFT JOINs (e.g. `task_step`) were detected in the query. Removing them will result in a performance improvement. In some cases, JOINs become redundant after an optimization is applied, such as when converting OR conditions to a UNION clause.
Optimal indexes for this query:
CREATE INDEX company_idx_id ON "company" ("id");
CREATE INDEX fos_user_idx_deleted_id ON "fos_user" ("deleted","id");
CREATE INDEX invoice_idx_deleted_id ON "invoice" ("deleted","id");
CREATE INDEX invoice_status_idx_deleted_id ON "invoice_status" ("deleted","id");
CREATE INDEX opportunity_idx_id ON "opportunity" ("id");
CREATE INDEX priority_idx_deleted_id ON "priority" ("deleted","id");
CREATE INDEX slip_idx_deleted_task_id ON "slip" ("deleted","task_id");
CREATE INDEX task_idx_deleted_status_id ON "task" ("deleted","status_id");
CREATE INDEX task_status_idx_deleted_id ON "task_status" ("deleted","id");
The optimized query:
SELECT
        DISTINCT task.id AS id0,
        task.rate AS rate1,
        task.revised_rate AS revised_rate2,
        task.title AS title3,
        task.points AS points4,
        task.due AS due5,
        task.created AS created6,
        invoice.id AS id7,
        invoice.title AS title8,
        company.id AS id9,
        company.customer_code AS customer_code10,
        taskStatus.id AS id11,
        taskStatus.title AS title12,
        priority.id AS id13,
        taskUser.id AS id14,
        taskUser.firstname AS firstname15,
        taskUser.lastname AS lastname16,
        invoiceUser.id AS id17,
        invoiceUser.firstname AS firstname18,
        invoiceUser.lastname AS lastname19,
        invoiceStatus.id AS id20,
        invoiceStatus.title AS title21,
        opportunity.id AS id22,
        opportunity.name AS name23,
        SUM(slip.stop - slip.start) AS sclr24,
        task.projected_minutes AS projected_minutes25 
    FROM
        task task 
    INNER JOIN
        invoice invoice 
            ON task.invoice_id = invoice.id 
            AND (
                invoice.deleted IS NULL
            ) 
    INNER JOIN
        company company 
            ON invoice.company_id = company.id 
    INNER JOIN
        task_status taskStatus 
            ON task.status_id = taskStatus.id 
            AND (
                taskStatus.deleted IS NULL
            ) 
    LEFT JOIN
        slip slip 
            ON task.id = slip.task_id 
            AND (
                slip.deleted IS NULL
            ) 
    LEFT JOIN
        fos_user taskUser 
            ON task.user_id = taskUser.id 
            AND (
                taskUser.deleted IS NULL
            ) 
    LEFT JOIN
        priority priority 
            ON task.priority_id = priority.id 
            AND (
                priority.deleted IS NULL
            ) 
    LEFT JOIN
        fos_user invoiceUser 
            ON invoice.user_id = invoiceUser.id 
            AND (
                invoiceUser.deleted IS NULL
            ) 
    LEFT JOIN
        invoice_status invoiceStatus 
            ON invoice.status_id = invoiceStatus.id 
            AND (
                invoiceStatus.deleted IS NULL
            ) 
    LEFT JOIN
        opportunity opportunity 
            ON invoice.opportunity_id = opportunity.id 
    INNER JOIN
        fos_user slipUser 
            ON slip.user_id = slipUser.id 
            AND (
                slipUser.deleted IS NULL
            ) 
    WHERE
        (
            taskStatus.id IN (
                3
            ) 
            AND slipUser.id IN (
                605
            )
        ) 
        AND (
            task.deleted IS NULL
        ) 
    GROUP BY
        task.id,
        company.id,
        taskUser.id,
        taskStatus.id,
        invoice.id,
        invoiceUser.id,
        invoiceStatus.id,
        opportunity.id,
        priority.id 
    ORDER BY
        priority.id ASC,
        task.due ASC,
        company.customer_code ASC,
        taskUser.firstname ASC,
        taskUser.lastname ASC,
        task.points ASC,
        task.title ASC,
        taskStatus.title ASC,
        task.created ASC,
        invoice.title ASC,
        invoiceUser.firstname ASC,
        invoiceUser.lastname ASC,
        invoiceStatus.title ASC,
        opportunity.name ASC LIMIT 50 OFFSET 0

Related Articles



* original question posted on StackOverflow here.