Фильтрация строк по дате в запросе полного внешнего соединения -> пропущены некоторые результаты - PullRequest
0 голосов
/ 27 октября 2011

Фон

У меня есть две таблицы с различными типами элементов обратной связи в 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 >= '" & date & "' OR s.createdTime IS NULL)
    AND (l.createdTime >= '" & date & "' 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 >= '" & date & "' OR s.createdTime IS NULL)
  GROUP BY name)
ORDER BY name;

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

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

Ответы [ 2 ]

2 голосов
/ 29 октября 2011

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

  (SELECT name, AVG(l.overallQuality) AS avgLingQual,
    AVG(s.overallSatisfaction) AS avgSvcQual
  FROM (
    SELECT * FROM feedback_linguistic WHERE createdTime >= '" & date & "'
  ) AS l
  LEFT JOIN (
    SELECT * FROM feedback_service WHERE createdTime >= '" & date & "'
  ) 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 >= '" & date & "'
  ) AS l
  RIGHT JOIN (
    SELECT * FROM feedback_service WHERE createdTime >= '" & date & "'
  ) AS s USING(name)
  WHERE l.id IS NULL
  GROUP BY name)
ORDER BY name;

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

1 голос
/ 27 октября 2011

Полное внешнее соединение - это комбинация из 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 >= '" & date & "' OR s.createdTime IS NULL) 
    AND (l.createdTime >= '" & date & "' 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 >= '" & date & "' OR s.createdTime IS NULL) */
    AND (l.createdTime >= '" & date & "' 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 >= '" & date & "' OR s.createdTime IS NULL) 
    /*AND (l.createdTime >= '" & date & "' OR l.createdTime IS NULL) */
  GROUP BY s.name) 
ORDER BY listname; 

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

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

...