Фильтрация строк по дате в запросе полного внешнего соединения -> отсутствуют некоторые результаты

#mysql #where-clause #full-outer-join

#mysql #where-предложение #полное внешнее соединение

Вопрос:

Предыстория

У меня есть две таблицы с разными типами элементов обратной связи в MySQL. Я создал запрос для объединения этих таблиц с помощью FULL OUTER JOIN (который на самом деле записывается как два объединения и объединение в MySQL) и для подсчета некоторых средних оценок. Этот запрос, похоже, работает отлично:

   (SELECT name, AVG(l.overallQuality) AS avgLingQual,
    AVG(s.overallSatisfaction) AS avgSvcQual
  FROM feedback_linguistic AS l
  LEFT JOIN feedback_service AS s USING(name)
  GROUP BY name)
UNION ALL
  (SELECT name, AVG(l.overallQuality) AS avgLingQual, 
    AVG(s.overallSatisfaction) AS avgSvcQual
  FROM feedback_linguistic AS l
  RIGHT JOIN feedback_service AS s USING(name)
  WHERE l.id IS NULL
  GROUP BY name)
ORDER BY name;
 

(Это несколько упрощено для удобства чтения, но здесь это не имеет значения)

Проблема

Затем я попытался добавить фильтрацию по дате (т. Е. Учитываются только элементы обратной связи, созданные после определенной даты). С моими навыками SQL и проведенными исследованиями я смог придумать это:

   (SELECT name, AVG(l.overallQuality) AS avgLingQual,
    AVG(s.overallSatisfaction) AS avgSvcQual
  FROM feedback_linguistic AS l
  LEFT JOIN feedback_service AS s USING(name)
  WHERE (s.createdTime >= '" amp; date amp; "' OR s.createdTime IS NULL)
    AND (l.createdTime >= '" amp; date amp; "' OR l.createdTime IS NULL)
  GROUP BY name)
UNION ALL
  (SELECT name, AVG(l.overallQuality) AS avgLingQual, 
    AVG(s.overallSatisfaction) AS avgSvcQual
  FROM feedback_linguistic AS l
  RIGHT JOIN feedback_service AS s USING(name)
  WHERE l.id IS NULL
    AND (s.createdTime >= '" amp; date amp; "' OR s.createdTime IS NULL)
  GROUP BY name)
ORDER BY name;
 

Это почти работает: результаты, которые я получаю, выглядят примерно правильно. Однако отсутствует пара элементов обратной связи. Например, установив дату месяц назад, я насчитал отзывы для 21 разных людей в базе данных, но этот запрос возвращает только 19 человек. Хуже всего то, что я не могу найти никакого сходства между отсутствующими элементами.

Я делаю что-то не так в этом запросе? Я думаю, что WHERE предложение выполняет фильтрацию по дате после JOIN , и в идеале я, вероятно, делал бы это раньше. Опять же, я не знаю, вызывает ли это мою проблему, и я также понятия не имею, как написать этот запрос по-другому.

Ответ №1:

Я принял ответ Йохана, поскольку он проделал хорошую работу, объяснив мне этот материал, и ответ полезен даже в более общем смысле. Тем не менее, я подумал, что также опубликую первое решение, к которому я пришел. Он использовал подзапросы:

   (SELECT name, AVG(l.overallQuality) AS avgLingQual,
    AVG(s.overallSatisfaction) AS avgSvcQual
  FROM (
    SELECT * FROM feedback_linguistic WHERE createdTime >= '" amp; date amp; "'
  ) AS l
  LEFT JOIN (
    SELECT * FROM feedback_service WHERE createdTime >= '" amp; date amp; "'
  ) AS s USING(name)
  GROUP BY name)
UNION ALL
  (SELECT name, AVG(l.overallQuality) AS avgLingQual, 
    AVG(s.overallSatisfaction) AS avgSvcQual
  FROM (
    SELECT * FROM feedback_linguistic WHERE createdTime >= '" amp; date amp; "'
  ) AS l
  RIGHT JOIN (
    SELECT * FROM feedback_service WHERE createdTime >= '" amp; date amp; "'
  ) AS s USING(name)
  WHERE l.id IS NULL
  GROUP BY name)
ORDER BY name;
 

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

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

1. Вы можете использовать explain select для просмотра плана запроса.

Ответ №2:

Полное внешнее соединение представляет собой комбинацию из 3 соединений:

1- внутреннее соединение между A и B
2- соединение с левым исключением между A и B
3- соединение с правым исключением между A и B

Обратите внимание, что комбинация внутреннего и левого исключающего соединения является левым внешним соединением, поэтому обычно вы переписываете запрос как left outer join right exclusion join .
Однако для целей отладки это может быть полезно для union всех 3 объединений и для добавления некоторого маркера относительно того, какое соединение что делает:

   /*inner join*/
  (SELECT
     'inner' as join_type 
     , COALESCE(s.name, l.name) as listname
     , AVG(l.overallQuality) AS avgLingQual
     , AVG(s.overallSatisfaction) AS avgSvcQual 
  FROM feedback_linguistic l 
  INNER JOIN feedback_service s ON (l.name = s.name) 
  WHERE (s.createdTime >= '" amp; date amp; "' OR s.createdTime IS NULL) 
    AND (l.createdTime >= '" amp; date amp; "' OR l.createdTime IS NULL) 
  GROUP BY l.name) 
UNION ALL
  (SELECT
     'left exclusion' as join_type 
     , COALESCE(s.name, l.name) as listname
     , AVG(l.overallQuality) AS avgLingQual
     , AVG(s.overallSatisfaction) AS avgSvcQual 
  FROM feedback_linguistic l 
  LEFT JOIN feedback_service s ON (l.name = s.name) 
  WHERE s.id IS NULL
    /*AND (s.createdTime >= '" amp; date amp; "' OR s.createdTime IS NULL) */
    AND (l.createdTime >= '" amp; date amp; "' OR l.createdTime IS NULL) 
  GROUP BY l.name) 
UNION ALL
  (SELECT 
     'right exclusion' as join_type
     , COALESCE(s.name, l.name) as listname
     , AVG(l.overallQuality) AS avgLingQual 
     , AVG(s.overallSatisfaction) AS avgSvcQual 
  FROM feedback_linguistic l 
  RIGHT JOIN feedback_service s ON (s.name = l.name) 
  WHERE l.id IS NULL
    AND (s.createdTime >= '" amp; date amp; "' OR s.createdTime IS NULL) 
    /*AND (l.createdTime >= '" amp; date amp; "' OR l.createdTime IS NULL) */
  GROUP BY s.name) 
ORDER BY listname; 
 

Я думаю, что предложение WHERE выполняет фильтрацию по дате после объединения, и в идеале я, вероятно, делал бы это раньше.

Если вы хотите выполнить фильтрацию раньше, поместите ее в предложение join .

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

1. Спасибо, теперь я лучше понимаю полное внешнее соединение и даже узнал кое-что новое о SQL. Я не думал об использовании coalesce с именами, и у меня возникли проблемы с использованием JOIN ON вместо JOIN USING…