PostgreSQL: Using AND statement in LEFT JOIN is not working as expected

非 Y 不嫁゛ 提交于 2020-12-13 03:10:05

问题


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;

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;

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 ?


回答1:


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

  • SQL / PostgreSQL left join ignores "on = constant" predicate, on left table

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:

  • Sample Query to show Cardinality estimation error in PostgreSQL

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




回答2:


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.



来源:https://stackoverflow.com/questions/40793422/postgresql-using-and-statement-in-left-join-is-not-working-as-expected

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!