MySQL LEFT JOIN с WHERE вызов функции дает неверный результат
Из MySQL 5.7 я выполняю LEFT JOIN
и WHERE
Предложение вызывает мою пользовательскую функцию. Не удается найти подходящую строку, которую он должен найти.
[Изначально я немного упростил свой фактический код для целей этого поста. Однако, учитывая предложенный пользователем ответ, я публикую фактический код, так как он может быть уместным.]
Моя пользовательская функция:
CREATE FUNCTION `jfn_rent_valid_email`(
rent_mail_to varchar(1),
agent_email varchar(45),
contact_email varchar(60)
)
RETURNS varchar(60)
BEGIN
IF rent_mail_to = 'A' AND agent_email LIKE '%@%' THEN
RETURN agent_email;
ELSEIF contact_email LIKE '%@%' THEN
RETURN contact_email;
ELSE
RETURN NULL;
END IF
END
Мой запрос:
SELECT r.RentCode, r.MailTo, a.AgentEmail, co.Email,
jfn_rent_valid_email(r.MailTo, a.AgentEmail, co.Email)
AS ValidEmail
FROM rents r
LEFT JOIN contacts co ON r.RentCode = co.RentCode -- this produces one match
LEFT JOIN link l ON r.RentCode = l.RentCode -- there will be no match in `link` on this
LEFT JOIN agents a ON l.AgentCode = a.AgentCode -- there will be no match in `agents` on this
WHERE r.RentCode = 'ZAKC17' -- this produces one match
AND (jfn_rent_valid_email(r.MailTo, a.AgentEmail, co.Email) IS NOT NULL)
Это не производит строк.
Однако. когда a.AgentEmail IS NULL
если я только изменится с
AND (jfn_rent_valid_email(r.MailTo, a.AgentEmail, co.Email) IS NOT NULL)
в
AND (jfn_rent_valid_email(r.MailTo, NULL, co.Email) IS NOT NULL)
это действительно производит соответствующую строку:
RentCode, MailTo, AgentEmail, Email, ValidEmail
ZAKC17, N, <NULL>, name@email, name@email
Так когда a.AgentEmail
является NULL
(из несоответствия LEFT JOIN
Почему в мире передается функция a.AgentEmail
действовать иначе, чем передавать его как буквальный NULL
?
[Кстати: я полагаю, что в прошлом я использовал такую конструкцию под MS SQL-сервером, и она работала так, как я и ожидал. Кроме того, я могу отменить тест AND (jfn_rent_valid_email(r.MailTo, a.AgentEmail, co.Email) IS NOT NULL)
в AND (jfn_rent_valid_email(r.MailTo, a.AgentEmail, co.Email) IS NULL)
все же я до сих пор не могу сравниться. Это как бы любая ссылка на a....
как параметр функции не вызывает совпадения строки...]
2 ответа
Скорее всего, это проблема с оптимизатором поворота LEFT JOIN
в INNER JOIN
, Оптимизатор может сделать это, когда он считает, что WHERE-условие всегда ложно для сгенерированной строки NULL (чего в данном случае нет).
Вы можете взглянуть на план запроса с помощью EXPLAIN
команда, вы, вероятно, увидите другой порядок таблиц в зависимости от варианта запроса.
Если фактическая логика функции заключается в проверке всех электронных писем одним вызовом функции, вам может повезти с использованием функции, которая принимает только один адрес электронной почты в качестве параметра, и используйте ее для каждого столбца электронной почты.
Вы можете попробовать без функции:
SELECT r.RentCode, r.MailTo, a.AgentEmail, co.Email,
jfn_rent_valid_email(r.MailTo, a.AgentEmail, co.Email)
AS ValidEmail
FROM rents r
LEFT JOIN contacts co ON r.RentCode = co.RentCode -- this produces one match
LEFT JOIN link l ON r.RentCode = l.RentCode -- there will be no match in `link` on this
LEFT JOIN agents a ON l.AgentCode = a.AgentCode -- there will be no match in `agents` on this
WHERE r.RentCode = 'ZAKC17' -- this produces one match
AND ((r.MailTo='A' AND a.AgentEmail LIKE '%@%') OR co.Email LIKE '%@%' )
Или оберните функцию в подзапросе:
SELECT q.RentCode, q.MailTo, q.AgentEmail, q.Email, q.ValidEmail
FROM (
SELECT r.RentCode, r.MailTo, a.AgentEmail, co.Email,
jfn_rent_valid_email(r.MailTo, a.AgentEmail, co.Email) AS ValidEmail
FROM rents r
LEFT JOIN contacts co ON r.RentCode = co.RentCode -- this produces one match
LEFT JOIN link l ON r.RentCode = l.RentCode -- there will be no match in `link` on this
LEFT JOIN agents a ON l.AgentCode = a.AgentCode -- there will be no match in `agents` on this
WHERE r.RentCode = 'ZAKC17' -- this produces one match
) as q
WHERE q.ValidEmail IS NOT NULL
Изменение вызова функции в WHERE
пункт для чтения
jfn_rent_valid_email(r.MailTo, IFNULL(a.AgentEmail, NULL), IFNULL(co.Email, NULL)) IS NOT NULL
решает проблему.
Похоже, что оптимизатор чувствует, что может неправильно догадаться, что функция вернется NULL
в несоответствии LEFT JOIN
случай, если простая ссылка на a.AgentEmail
передается как любой параметр. Но если ссылка на столбец находится внутри какого-либо выражения, оптимизатор отключается. Заворачивать его в "пустышку", казалось бы, бессмысленно IFNULL(column, NULL)
Таким образом, достаточно, чтобы восстановить правильное поведение.
Я отмечаю это как принятое решение, потому что это, безусловно, самый простой обходной путь, требующий минимального изменения кода / полного переписывания запроса.
Тем не менее, полная заслуга в публикации @ slaakso здесь в этой теме для анализа проблемы. Обратите внимание, что он заявляет, что поведение было исправлено / изменено в MySQL 8, так что этот обходной путь не требуется, поэтому он может быть необходим только в MySQL 5.7 или более ранней версии.