СОЕДИНЕНИЕ ПО ЛЕВОМУ КРАЮ возвращает не все значения

#sql #sql-server #datetime #left-join #where-clause

#sql #sql-сервер #дата и время #соединение по левому краю #where-предложение

Вопрос:

У меня есть следующие таблицы:

 create table Cars
(
  CarID int,
  CarType varchar(50)
);


create table Maintenances
(
  CarID int,
  MaintenanceDate date,
  MaintenanceCost money
);

create table Repairs
(
  CarID int,
  RepairDate date,
  RepairCost money
);
 

Я пробовал это:

 SELECT C.CarType,
SUM(MaintenanceCost),
SUM(RepairCost)
FROM Cars AS C 
LEFT JOIN Maintenances AS M ON M.CarID=C.CarID
LEFT JOIN Repairs AS R ON R.CarID=C.CarID
WHERE M.MaintenanceDate BETWEEN '2020-12-01' AND '2020-12-31' AND
R.RepairDate BETWEEN '2020-12-01' AND '2020-12-31'
GROUP BY C.CarType
 

Но оно не возвращает значения для всех типов карт. Смотрите эту скрипку: Скрипка

Комментарии:

1. Вы WHERE превращаете свои внешние соединения во внутренние соединения

Ответ №1:

Переместите условия в LEFT JOIN таблицах редактирования из WHERE предложения в ON предложение соединений — в противном случае они становятся обязательными, и несоответствующие строки в любой таблице отфильтровываются.

Итак:

 SELECT C.CarType, SUM(M.MaintenanceCost), SUM(R.RepairCost)
FROM Cars AS C 
LEFT JOIN Maintenances M 
    ON  M.CarID = C.CarID 
    AND M.MaintenanceDate BETWEEN '20201201' AND '20201231'
LEFT JOIN Repairs R 
    ON  R.CarID = C.CarID 
    AND R.RepairDate BETWEEN '20201201' AND '20201231'
GROUP BY C.CarType
 

Примечания:

  • Я поставил перед столбцами в SUM() таблице, к которой они принадлежат (мне пришлось сделать предположение): это хорошая практика, которая делает запрос понятным и однозначным
  • YYYYMMDD поддерживается во всех версиях SQL Server в качестве литерала даты, независимо от региональных настроек, поэтому это может быть лучшим выбором, чем YYYY-MM-DD

Ответ №2:

Это потому, что вы фильтруете по дате таблицы обслуживания и ремонта. Только в carId 3 есть запись для вашего заданного диапазона дат в обеих таблицах.

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

     SELECT C.CarType,
    ISNULL(SUM(MaintenanceCost),0) AS MaintenanceCost,
    ISNULL(SUM(RepairCost),0) AS RepairCost
     Cars AS C 
     JOIN Maintenances AS M 
       M.CarID = C.CarID
       M.MaintenanceDate BETWEEN '2020-12-01' AND '2020-12-31'
     JOIN Repairs AS R 
       R.CarID = C.CarID
       R.RepairDate BETWEEN '2020-12-01' AND '2020-12-31'
     BY C.CarType
 

Ответ №3:

Если вы хотите SUM() , чтобы s было правильным, я бы предложил написать эту логику с использованием коррелированных подзапросов. Проблема в том, что в итоге вы получаете декартово произведение для каждого Carid — и это декартово произведение означает, что сумма вычисляется правильно:

Если CarType является уникальным в Cars , вы можете использовать:

 SELECT C.CarType,
       (SELECT SUM(M.MaintenanceCost)
        FROM Maintenances M 
        WHERE M.CarID = C.CarID AND
              M.MaintenanceDate BETWEEN '20201201' AND '20201231' 
       ),
       (SELECT SUM(R.RepairCost)
        FROM Repairs R
        WHERE R.CarID = R.CarID AND
              R.RepairDate BETWEEN '20201201' AND '20201231' 
       )
FROM Cars C ;
 

В противном случае это выглядит немного сложнее, потому что вам нужно CarType в каждом подзапросе:

 SELECT C.CarType,
       (SELECT SUM(M.MaintenanceCost)
        FROM Maintenances M JOIN
             Cars C
             ON M.CarID = C.CarID
        WHERE C.CarType = CT.CarType AND
              M.MaintenanceDate BETWEEN '20201201' AND '20201231' 
       ),
       (SELECT SUM(R.RepairCost)
        FROM Repairs R JOIN
             Cars C
             ON R.CarID = C.CarID
        WHERE C.CarType = CT.CarType AND
              R.RepairDate BETWEEN '20201201' AND '20201231' 
       )
FROM (SELECT DISTINCT CarType FROM Cars C) CT