Like I suggested and explained on your subject , I would use UNION ALL instead of FULL JOIN in an external SELECT .
WITH hi AS ( SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct FROM evidensapp_seniangcbr fh JOIN evidensapp_polystructures ps ON ST_Intersects(fh.geom, ps.geom) WHERE fh.hazard = 'High' GROUP BY 1, 2, 3 ) , med AS ( SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct FROM evidensapp_seniangcbr fh JOIN evidensapp_polystructures ps ON ST_Intersects(fh.geom, ps.geom) LEFT JOIN hi USING (brgy_locat, municipali) WHERE fh.hazard = 'Medium' AND hi.brgy_locat IS NULL GROUP BY 1, 2, 3 ) TABLE hi UNION ALL TABLE med UNION ALL SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct FROM evidensapp_seniangcbr fh JOIN evidensapp_polystructures ps ON ST_Intersects(fh.geom, ps.geom) LEFT JOIN hi USING (brgy_locat, municipali) LEFT JOIN med USING (brgy_locat, municipali) WHERE fh.hazard = 'Low' AND hi.brgy_locat IS NULL AND med.brgy_locat IS NULL GROUP BY 1, 2, 3;
This applies only to the highest level of danger for each set of lines with identical (brgy_locat, municipali) . The result is only lines that actually intersect with any line of the corresponding hazard level in evidensapp_seniangcbr . In addition, the counter only counts lines that actually intersect. There may be more lines with the same (brgy_locat, municipali) in evidensapp_polystructures , simply not intersecting with the same level of danger and, therefore, ignored.
Choose one of the standard methods to exclude lines for which you have already found a match at a higher hazard level at the lower levels.
LEFT JOIN / IS NULL should use the index on id and work very well here. Of course, faster than using EXCEPT based on the whole row, which cannot use the index.
Index
You do not need to add the bounding_box geometry column to the table, as suggested by another answer. In modern versions, PostGIS uses (index constraint) frame constraint automatically . PostGIS Documentation:
This function call automatically includes a bounding box comparison, which will use any indexes available in the geometry.
In fact, we already see the index scan in explaining the output you posted.
Your existing GiST index evidensapp_polystructures_geom_id should complete the query quickly.
In addition, the index name must be evidensapp_polystructures_geom_idx .
Also, create an index on (brgy_locat, municipali) if you don't already have one:
CREATE INDEX foo_idx ON evidensapp_polystructures (brgy_locat, municipali);
Alternative with LATERAL join
Since you only have 6 lines in evidensapp_seniangcbr , LATERAL can be faster:
WITH hi AS ( SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct FROM evidensapp_seniangcbr fh , LATERAL ( SELECT ps.brgy_locat, ps.municipali FROM evidensapp_polystructures ps WHERE ST_Intersects(fh.geom, ps.geom) ) ps WHERE fh.hazard = 'High' GROUP BY 1, 2, 3 ) , med AS ( SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct FROM evidensapp_seniangcbr fh , LATERAL ( SELECT ps.brgy_locat, ps.municipali FROM evidensapp_polystructures ps LEFT JOIN hi USING (brgy_locat, municipali) WHERE hi.brgy_locat IS NULL AND ST_Intersects(fh.geom, ps.geom) ) ps WHERE fh.hazard = 'Medium' GROUP BY 1, 2, 3 ) TABLE hi UNION ALL TABLE med UNION ALL SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct FROM evidensapp_seniangcbr fh , LATERAL ( SELECT ps.id, ps.brgy_locat, ps.municipali FROM evidensapp_polystructures ps LEFT JOIN hi USING (brgy_locat, municipali) LEFT JOIN med USING (brgy_locat, municipali) WHERE hi.brgy_locat IS NULL AND med.brgy_locat IS NULL AND ST_Intersects(fh.geom, ps.geom) ) ps WHERE fh.hazard = 'Low' GROUP BY 1, 2, 3;
About LATERAL joins: