[Solved] Query planner not using filter to limit calculations in high-cost join

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 Selecting Unnecessary Columns (query line: 2): Avoid selecting all columns with the '*' wildcard, unless you intend to use them all. Selecting redundant columns may result in unnecessary performance degradation.
  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. Use UNION ALL instead of UNION (query line: 63): Always use UNION ALL unless you need to eliminate duplicate records. By using UNION ALL, you'll avoid the expensive distinct operation the database applies when using a UNION clause.
Optimal indexes for this query:
CREATE INDEX tassignments_idx_licence_id ON "test"."tAssignments" ("licence_id");
CREATE INDEX tlicences_idx_id ON "tLicences" ("id");
CREATE INDEX tpurchases_idx_id ON "tPurchases" ("id");
The optimized query:
SELECT
        * 
    FROM
        test.tPurchases AS tPurchases 
    INNER JOIN
        test.tLicences 
            ON tLicences.purchase_id = tPurchases.id 
    LEFT JOIN
        (
            SELECT
                purchase_id,
                SUM(CASE assignment_newer_id IS NOT NULL 
                    WHEN true THEN 1 
                    WHEN false THEN 0 END) AS prchs_quantity_assigned,
SUM(CASE assignment_newer_id IS NULL 
    AND current_timestamp BETWEEN licence_availability_start AND licence_availability_end 
    WHEN true THEN 1 
    WHEN false THEN 0 END) AS prchs_quantity_notAssignedAndCanBeAssigned,
SUM(CASE assignment_newer_id IS NULL 
    AND current_timestamp < licence_availability_start 
    WHEN true THEN 1 
    WHEN false THEN 0 END) AS prchs_quantity_notAssignedAndCannotBeAssigned 
FROM
(SELECT
    tPurchases.id AS purchase_id,
    tPurchases.date_ AS purchase_date,
    tLicences.id AS licence_id,
    GREATEST(tPurchases.date_,
    older.end_,
    older.start + '1 day'::interval) AS licence_availability_start,
    CASE 
        WHEN newer.id IS NULL THEN 'infinity' 
        ELSE newer.start - '1 day'::interval END AS licence_availability_end,
COALESCE(newer.start,
'infinity') AS licence_availability_uninstallBy,
older.id AS assignment_older_id,
older.start AS assignment_older_start,
older.end_ AS assignment_older_end,
newer.id AS assignment_newer_id,
newer.start AS assignment_newer_start,
newer.end_ AS assignment_newer_end 
FROM
test.tLicences 
INNER JOIN
test.tPurchases 
    ON tPurchases.id = tLicences.purchase_id 
LEFT JOIN
test.tAssignments AS older 
    ON (
        NOT older.deleted 
        AND older.licence_id = tLicences.id
    ) 
LEFT JOIN
test.tAssignments AS newer 
    ON (
        NOT newer.deleted 
        AND newer.id <> older.id 
        AND newer.licence_id = older.licence_id
    ) 
WHERE
NOT tLicences.deleted 
UNION
SELECT
tPurchases.id AS purchase_id,
tPurchases.date_ AS purchase_date,
tLicences.id AS licence_id,
tPurchases.date_ AS licence_availability_start,
oldest.start - '1 day'::interval AS licence_availability_end,
oldest.start AS licence_availability_uninstallBy,
NULL AS assignment_older_id,
NULL AS assignment_older_start,
NULL AS assignment_older_end,
oldest.id AS assignment_newer_id,
oldest.start AS assignment_newer_start,
oldest.end_ AS assignment_newer_end 
FROM
test.tLicences 
INNER JOIN
test.tPurchases 
    ON tPurchases.id = tLicences.purchase_id 
INNER JOIN
test.tAssignments AS oldest 
    ON oldest.licence_id = tLicences.id 
WHERE
NOT tLicences.deleted 
AND NOT oldest.deleted
) AS periodsOfAvailability_start 
WHERE
(
assignment_newer_id IS NULL 
OR assignment_newer_end IS NULL
) 
GROUP BY
purchase_id) AS purchase_quantities_assignnments 
ON purchase_quantities_assignnments.purchase_id = tPurchases.id 
WHERE
tLicences.id = 19

Related Articles



* original question posted on StackOverflow here.