Предложение WHERE преобразует ЛЕВОЕ СОЕДИНЕНИЕ с visiting_addresses
во ВНУТРЕННЕЕ СОЕДИНЕНИЕ. И поскольку это самая правая таблица в цепочке LEFT-JOIN, все объединения будут преобразованы в INNER JOINS. Чтобы предотвратить это, вы должны переместить соответствующие условия из предложения WHERE в предложение ON:
select ar.id as area_id, ar.name as area,
sum(case when va.status = 1 then 1 else 0 end) as visited,
sum(case when va.status = 2 then 1 else 0 end) as pending,
count(va.id) as total
from areas ar
left join territories t on t.area_id=ar.id
left join addresses a on a.territory_id=t.id
left join visiting_addresses va
on va.address_id=a.id
and month(va.date) = '01'
and year(va.date)='2020'
group by ar.id
Но поскольку у вас много строк, я бы предпочел выполнить два запроса. Сначала получите только области с адресами из прошлого месяца, используя внутренние соединения. Однако вы должны изменить свои условия на va.date
, чтобы использовать индекс:
select ar.id as area_id, ar.name as area,
sum(case when va.status = 1 then 1 else 0 end) as visited,
sum(case when va.status = 2 then 1 else 0 end) as pending,
count(va.id) as total
from areas ar
join territories t on t.area_id=ar.id
join addresses a on a.territory_id=t.id
join visiting_addresses va on va.address_id=a.id
where va.date >= '2020-01-01'
and va.date < '2020-02-01'
group by ar.id
Убедитесь, что у вас есть индекс на visiting_addresses(date)
или даже лучше на visiting_addresses(date, address_id, status)
.
Тогда получите все области с простым
select ar.id as area_id, ar.name as area
from areas ar
и добавьте пропущенные области к первому результату при установке visited
, pending
и total
на ноль (в коде приложения).
INNER JOIN должен быть намного быстрее, потому что теперь движок может начать читать только необходимые строки из visiting_addresses
, используя индекс для условий WHERE.
Вы также можете использовать более сложный, но одиночный запрос. Идея состоит в том, чтобы использовать LEFT JOIN с предварительно агрегированным подзапросом:
select ar.id as area_id, ar.name as area,
coalesce(visited, 0) as visited,
coalesce(pending, 0) as pending,
coalesce(total, 0) as total
from areas ar
left join (
select t.area_id
sum(case when va.status = 1 then 1 else 0 end) as visited,
sum(case when va.status = 2 then 1 else 0 end) as pending,
count(va.id) as total
from territories t
join addresses a on a.territory_id=t.id
join visiting_addresses va on va.address_id=a.id
where va.date >= '2020-01-01'
and va.date < '2020-02-01'
group by t.area_id
) x on x.area_id = ar.id