Может ли SQL возвращать информацию о различных условиях, оцененных в запросе?Или я должен разбить на несколько запросов? - PullRequest
0 голосов
/ 27 марта 2012

Я пишу код, в котором я использую SQL для проверки нескольких различных условий, прежде чем в конечном итоге решить, что делать. Я пытаюсь выбрать между написанием одного составного запроса MySQL или написанием одного запроса для каждого условия, при этом PHP обрабатывает логику их объединения. Мои вопросы:

  1. Есть ли хорошие практики или подводные камни, которые должны направить меня в любом направлении?
  2. Если я реализую всю логику в одном запросе SQL, могу ли я получить его, чтобы он возвращал информацию о том, как получилось каждое условие? (см. объяснение ниже.)

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

  1. Какие электронные письма следует отправлять в зависимости от времени, прошедшего с последнего посещения пользователя?
  2. Для каждого из этих писем, пользователь не получил это письмо раньше?
  3. Получал ли пользователь не письма от нас за последние 3 дня?

Вот SQL-запрос, который проверяет все 3 из них. (этот запрос выполняется для каждого пользователя; он использует PDO, а переменные, начинающиеся с :, являются связанными параметрами):

SELECT message_id, message_content FROM emailtemplates
WHERE time_before_reminder < :days_since_visit
AND message_id not in 
    (SELECT message_id FROM sentemails WHERE user = :userid)
AND :userid not in 
    (SELECT userid FROM sentemails WHERE date_add(timesent, interval 3 day) >= now())
ORDER BY time_before_reminder asc
LIMIT 1;

Возвращает ноль строк, если какое-либо из условий не выполняется, или одну строку, если они все проходят.

С другой стороны, вот код, который выполняет несколько отдельных запросов. Это намного уродливее, но может дать мне объяснения о том, почему мы не пишем по электронной почте данному пользователю. ( Я переключился на псевдокод, чтобы вам не приходилось читать zillion $stmt->bindParam($params) строк ):

//run the query for condition 1:
query("SELECT message_id, message_content FROM emailtemplates WHERE time_before_reminder < :days_since_visit;")
if 0 rows returned:
    echo("no appropriate emails found for this participant")
    return false

//then for condition 2:
query("SELECT message_id from sentemails where message_id = :msgid AND user = :userid")
if 1 or more rows returned:
    echo("user $userid qualified for message $msgid, but they already received it.");
    return false

//then for condition 3:
query("SELECT userid FROM sentemails WHERE user = :userid AND date_add(timesent, interval 3 day) >= now();")
if 1 or more rows returned:
    echo("user $userid qualified for message $msgid, but they already received an email from us in the past 3 days. No email sent.")
    return false

// if we get here, all tests were passed. Send the message that was found in the first query.
send_reminder($userid, $msgid)
echo("successfully sent message $msgid to user $userid!")
return true

Второй пример кажется действительно громоздким, но мне нравится выяснять, какое именно условие не удалось и почему. Есть ли способ, чтобы один запрос возвращал такую ​​информацию? (Я имею в виду возвращение информации, которая может быть проанализирована в объяснении, конечно же, если бы MySQL не писал для меня английские предложения.) Или есть другой совет по обработке запросов, которые проверяют несколько независимых условий, подобных этому?

1 Ответ

1 голос
/ 27 марта 2012

Вероятно, вы можете (протестировано на MySQL 5.5 без связанных параметров) использовать ваши условия в предложении SELECT, они должны выходить 0 (неудачно) или 1 (пройдено):

SELECT
    message_id, 
    message_content, 
    (time_before_reminder < :days_since_visit) AS time, 
    (message_id NOT IN (SELECT message_id FROM sentemails WHERE user = :userid)) AS already, 
    (:userid NOT IN (SELECT userid FROM sentemails WHERE date_add(timesent, interval 3 day) >= now())) AS recent 
FROM emailtemplates
WHERE time_before_reminder < :days_since_visit
AND message_id not in 
    (SELECT message_id FROM sentemails WHERE user = :userid)
AND :userid not in 
    (SELECT userid FROM sentemails WHERE date_add(timesent, interval 3 day) >= now())
ORDER BY time_before_reminder asc
LIMIT 1;

Однако, я полагаю, это означает вдвое больше работы для СУБД. Также должно быть возможно использовать некоторые LEFT JOIN и посмотреть, какие поля имеют значение null ... и это было бы более элегантно и, возможно, быстрее.

Еще одна вещь - хотя и не имеет значения для вашего вопроса - какой смысл иметь ORDER BY, за которым следует LIMIT 1?

Редактировать : запрос на основе LEFT JOIN должен выглядеть примерно так (скажите, если он работает, я не могу проверить его, пока не создаю БД ... и мне лень ^^ ):

SELECT
  et.message_id AS messageId, 
  et.message_content AS messageContent, 
  se1.message_id AS nullIfNotSent, 
  se2.userid AS nullIfMoreThan3Days 
FROM 
  (SELECT :userid AS userid) AS param
    LEFT JOIN sentemails AS se2 
      ON param.userid=se2.userid AND date_add(se2.timesent, interval 3 days) >= now(),
  emailtemplates AS et
    LEFT JOIN sentemails AS se1 
      ON et.message_id=se1.message_id AND se1.userid=param.userid
WHERE 
  time_before_reminder < :days_since_visit
ORDER BY time_before_reminder 
LIMIT 1;

Ожидаемое поведение : возвращение нулевых строк означает, что он не прошел первый тест (time_before_reminder), в противном случае, если nullIfNotSent не равно нулю (следовательно, допустимо message_id), это означает, что он не прошел второй тест (сообщения такого рода уже были отправлены когда-нибудь), и если nullIfMoreThan3days не равно нулю (действительный идентификатор пользователя), это означает, что пользователь уже получил сообщение за последние 3 дня (т.е. не прошел третий тест). Таким образом, если вы получите строку с nullIfNotSent и nullIfMoreThan3Days, это означает, что вы можете отправить сообщение, используя возвращенные messageId и messageContent.

Пожалуйста, скажите, работает ли оно! (или нет);)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...