1

This query returns all the elements in the table la and all nulls for fields coming from the lar table which is not what I expected.

SELECT
  la.listing_id,
  la.id,
  lar.*
FROM la
LEFT JOIN lar
ON lar.application_id = la.id AND la.listing_id = 2780;

enter image description here

This query returns correct and expected results but shouldn't both queries do the same thing ?

SELECT
  la.listing_id,
  la.id,
  lar.*
FROM la
LEFT JOIN lar
ON lar.application_id = la.id
WHERE la.listing_id = 2780;

enter image description here

What am I missing here?

I want to make conditional joins as I have noticed that for complex queries Postgresql does the join then do the WHERE clause which is actually very slow. How to make the database filter out some records before doing the JOIN ?

Ahmed H. Saab
  • 395
  • 3
  • 14

2 Answers2

2

The confusion around LEFT JOIN and WHERE clause has been clarified many times:

This interesting question remains:

How to make the database filter out some records before doing the JOIN?

There are no explicit query hints in Postgres. (Which is a matter of ongoing debate.) But there are still various tricks to make Postgres bend your way.

But first, ask yourself: Why did the query planner estimate the chosen plan to be cheaper to begin with? Is your server configuration basically sane? Cost settings adequate? autovacuum running? Postgres version outdated? Are you working around an underlying problem that should really be fixed?

If you force Postgres to do it your way, you should be sure it won't fire back, after a version upgrade or update to the server configuration ... You'd better know what you are doing exactly.

That said, you can force Postgres to "filter out some records before doing the JOIN" with a subquery where you add OFFSET 0 - which is just noise, logically, but prevents Postgres from rearranging it into the form of a regular join. (Query hint after all)

SELECT la.listing_id, la.id, lar.*
FROM  (
   SELECT listing_id, id
   FROM   la
   WHERE  listing_id = 2780
   OFFSET 0
   ) la
LEFT   JOIN lar  ON lar.application_id = la.id;

Or you can use a CTE (less obscure, but more expensive). Or other tricks like setting certain config parameters. Or, in this particular case, I would use a LATERAL join to the same effect:

SELECT la.listing_id, la.id, lar.*
FROM   la
LEFT  JOIN LATERAL (
   SELECT *
   FROM   lar
   WHERE  application_id = la.id
   )  lar ON true
WHERE  la.listing_id = 2780;

Related:

Here is an extensive blog on Query hints by 2ndQuadrant. Five year old but still valid.

Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
0

The LEFT JOIN keyword returns all rows from the left table (table1), with the matching rows in the right table (table2). The result is NULL in the right side when there is no match.

So no matter you try to filter with AND la.listing_id = 2780; you still get all the rows from first table. But only those with la.listing_id = 2780; will have something <> NULL on the right side

The behaviour is different if you try INNER JOIN in that case only the matching columns are created and the AND condition will filter the rows.

So to make the first query work you need add WHERE la.listing_id IS NOT NULL

The problem with second query is will try to JOIN every row and then will filter only the one you need.

Juan Carlos Oropeza
  • 47,252
  • 12
  • 78
  • 118