MySQL LEFT JOIN с WHERE вызов функции дает неверный результат - PullRequest
0 голосов
/ 27 сентября 2018

В 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 ed), почему в мире передача его функции как 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 ]

0 голосов
/ 28 сентября 2018

Изменение вызова функции в предложении WHERE на чтение

jfn_rent_valid_email(r.MailTo, IFNULL(a.AgentEmail, NULL), IFNULL(co.Email, NULL)) IS NOT NULL

решает проблему.

Похоже, оптимизатор считает, что он может неправильно догадаться, что функция будетвернуть NULL в случае несоответствия LEFT JOIN, если в качестве любого параметра передана plain ссылка на a.AgentEmail.Но если ссылка на столбец находится внутри какого-либо выражения , оптимизатор отключается.Таким образом, для восстановления правильного поведения достаточно обернуть его внутри «фиктивного», казалось бы, бессмысленного IFNULL(column, NULL).

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

Тем не менее, полное признание связано с публикацией @laakso здесь в этом разделе для анализа проблемы.Обратите внимание, что он заявляет, что поведение было исправлено / изменено в MySQL 8, так что этот обходной путь не требуется, поэтому он может быть необходим только в MySQL 5.7 или более ранней версии.

0 голосов
/ 27 сентября 2018

Скорее всего, это проблема оптимизатора, превращающего 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
...