ЛЕВОЕ НАРУЖНОЕ СОЕДИНЕНИЕ PostgreSQL работает как ВНУТРЕННЕЕ СОЕДИНЕНИЕ? - PullRequest
0 голосов
/ 30 октября 2018

Следующий запрос работает правильно

SELECT "employee"."id",
       "employee"."created",
       (SELECT SUM(minutes) FROM jobs_job AS job WHERE job.employee_id = employee.id AND job.job_date BETWEEN '2018-10-21' AND '2018-10-30')
FROM users_employee AS employee
    INNER JOIN users_user AS users ON (employee.user_id = users.id)
WHERE NOT ("users"."status" = 'F')
GROUP BY
    employee.id

И я получаю правильные данные (NULL для людей без записей в таблице JOB

Однако, если я восстановлю тот же запрос в ВЛЕВО НАРУЖНОЕ СОЕДИНЕНИЕ

SELECT "employee"."id",
       "employee"."created",
       SUM(job.minutes) AS job_minutes
FROM users_employee AS employee
    INNER JOIN users_user AS users ON (employee.user_id = users.id)
    LEFT OUTER JOIN jobs_job AS job on employee.id = job.employee_id
WHERE NOT ("users"."status" = 'F') AND job.job_date BETWEEN '2018-10-21' AND '2018-10-30'
GROUP BY
    employee.id

Я получаю 112 строк вместо 142 в исходном запросе и только записи, которые существуют в таблице заданий

Ответы [ 4 ]

0 голосов
/ 30 октября 2018

Все ответы помогут вам исправить ваш код. Я просто хочу рассказать, почему они будут работать.

Движок SQL сначала оценивает ваши FROM и ваши JOIN, вытягивая ваш начальный набор данных в память. В этот момент, поскольку вы использовали LEFT OUTER JOIN, все ожидаемые строки все еще там.

После этого применяется ваше предложение WHERE. В этом случае ваше предложение WHERE включает job.job_date BETWEEN '2018-10-21' AND '2018-10-30', поэтому в этот момент механизм отфильтровывает все строки, которые не соответствуют этим критериям. Это эффективно делает результаты вашего LEFT JOIN точно такими же, как результаты, которые вы получили бы с INNER JOIN.

Лучший ответ, который предлагался несколько раз, состоит в том, чтобы переместить эти критерии фильтрации в ваше предложение ON. Альтернатива, которая будет работать, и которая демонстрирует, что результаты на самом деле все с самого начала, - это добавление возможности IS NULL к существующему предложению WHERE:

...
WHERE 
  NOT ("users"."status" = 'F') 
  AND
  (
    (job.job_date BETWEEN '2018-10-21' AND '2018-10-30')
    OR
    job.job_date IS NULL
  )

Использование условия на JOIN, тем не менее, будет работать лучше, потому что в первую очередь вы загружаете меньше записей в память.

0 голосов
/ 30 октября 2018

Вам нужно использовать другое условие в ON cluase вместо предложения where

SELECT "employee"."id",
       "employee"."created",
       SUM(job.minutes) AS job_minutes
FROM users_employee AS employee
    INNER JOIN users_user AS users ON (employee.user_id = users.id)
    LEFT OUTER JOIN jobs_job AS job on employee.id = job.employee_id
and "users"."status" <> 'F' AND job.job_date BETWEEN '2018-10-21' AND '2018-10-30'
GROUP BY
    employee.id
0 голосов
/ 30 октября 2018

Необходимо переместить условие в таблице last в предложение on. Фильтрация на втором столе остается в where:

SELECT "employee"."id",
       "employee"."created",
       SUM(job.minutes) AS job_minutes
FROM users_employee employee INNER JOIN
     users_user users
     ON employee.user_id = users.id LEFT OUTER JOIN
     jobs_job job 
     ON employee.id = job.employee_id AND
        job.job_date BETWEEN '2018-10-21' AND '2018-10-30'
WHERE "users"."status" <> 'F' 
GROUP BY employee.id;

Логика работает так, потому что левые внешние объединения дают результаты со значениями NULL из второй таблицы в JOIN. Эти значения NULL могут быть отфильтрованы в предложении WHERE.

0 голосов
/ 30 октября 2018

Это из-за этого укуса:

AND job.job_date BETWEEN '2018-10-21' AND '2018-10-30'    

Вам нужно переместить его в левое соединение, как:

LEFT OUTER JOIN jobs_job AS job on employee.id = job.employee_id
AND job.job_date BETWEEN '2018-10-21' AND '2018-10-30'
...