#mysql #optimization #subquery #conditional #extract
#mysql #оптимизация #подзапрос #условные операторы #извлечь
Вопрос:
Я новичок в MySQL, выполняющий небольшой проект. Я постараюсь изо всех сил описать проблему полностью, чтобы присутствующие здесь профессионалы могли помочь мне не только с кодом, но и с концепцией. Я никогда раньше не выполнял sql, но программировал на Python, Matlab и т.д. (процедурные среды). Без лишних слов,
Следующий код ужасно медленный, и оператор if
if (carts.staffId ='', 'UNASSISTED', 'ASSITED') as EXPERIENCE
не работает, поскольку он выходит, как всегда, БЕЗ ПОСТОРОННЕЙ ПОМОЩИ. Есть идеи, почему это не работает?
Вот код
select
Impressions.session_id,
sum(if(Impressions.impressionAction = 'SENDMAIL', 1, 0)) as EMAIL,
count(if(Impressions.impressionAction = 'TAPPED', 1, NULL)) as SCANS,
TIMESTAMPDIFF(SECOND, min(Impressions.createDate), max(Impressions.createDate)) AS Duration,
if (carts.staffId ='', 'UNASSISTED', 'ASSITED') as EXPERIENCE
from Impressions, carts
where Impressions.session_id in (select carts.session_id from carts where carts.session_id <> '' )
group by Impressions.session_id;
StaffID столбца существует в таблице под названием carts. Все, что я хотел, это извлечь вывод из условия в StaffID в carts, который будет добавлен в список столбцов из таблицы Показов
По сути, я пытаюсь добавить столбец из подзапроса и прикрепить его в конце основных столбцов из основного запроса
ОБНОВЛЕНИЕ: Я разбил проблему на две части, и вот как выглядит код. По сути, ОБЪЕДИНЕНИЕ между двумя выходами создает необходимый конечный результат. Мне просто нужно это за один раз:
/***********************ASSISTED*******************************/
select session_id,
sum(if(impressionAction = 'SENDMAIL', 1, 0)) as EMAIL,
count(if(impressionAction = 'TAPPED', 1, NULL)) as SCANS,
TIMESTAMPDIFF(SECOND, min(createDate), max(createDate)) AS Duration
from Impressions
where session_id in
(select session_id
from carts
where session_id <> '' AND staffId <> '' AND staffId <>'ollie' AND staffId<> 'Laura')
AND createDate >= '2014-06-23'AND createDate < '2014-06-30'
AND HOUR(createDate) >= 10 AND HOUR(createDate) < 21
AND impressionId NOT LIKE '%made.com' AND impressionId NOT LIKE '%cloudtags.com%'
group by session_id;
/***************************UNASSISTED***********************/
...everything is same...
where session_id <> '' AND staffId = '' OR staffId ='ollie' OR staffId= 'Laura' )
...;
2-Е ОБНОВЛЕНИЕ
Есть еще одно ограничение, о котором я забыл упомянуть в случае с ASSISTTED
vs UNASSISTED
. В дополнение к фильтрации приведенных здесь, мне также нужно будет отфильтровать productId = 1902
из таблицы, products
с которой cartID
связан.
Ответ №1:
У вас есть CROSS JOIN
операция между Impressions
и carts
. (Да, ваш запрос выполняет операцию объединения, он просто использует старый школьный синтаксис запятой для указания JOIN
операции.)
И это CROSS JOIN
потому, что нет предиката, который «сопоставляет» строки между двумя таблицами; каждая строка из Impressions
сопоставляется с каждой строкой в carts
.
У вас есть GROUP BY
предложение, которое сворачивает все «совпадающие» строки из carts
в одну строку; MySQL выбирает значения только из одной строки в carts
. И он выбирает одну и ту же строку из carts
для каждой строки в Impressions
.
Похоже, вы хотели бы «сопоставить» строки в carts
со строками в Impressions
, используя значения в session_id
столбце.
Что-то вроде этого:
SELECT i.session_id
, SUM(IF(i.impressionAction = 'SENDMAIL', 1, 0)) AS EMAIL
, COUNT(IF(i.impressionAction = 'TAPPED', 1, NULL)) AS SCANS
, TIMESTAMPDIFF(SECOND, MIN(i.createDate), MAX(i.createDate)) AS Duration
, MIN(IF(c.staffId = '', 'UNASSISTED', 'ASSISTED')) AS EXPERIENCE
FROM Impressions i
JOIN carts c
ON c.session_id = i.session_id
AND c.session_id <> ''
GROUP
BY i.session_id
Если есть несколько строк из carts
с одинаковым соответствием session_id
. MySQL собирается выбрать только одну строку и вычислить IF(c.staff_id
выражение. (Или, вычислит это выражение для каждой строки и выберет одно из результирующих значений для возврата.
Что ЕСЛИ выражение проверяет, что staff_id
равно строке нулевой длины (если staff_id
является числовым, то литерал ''
будет вычислен как числовое значение 0
… но мы только предполагаем о фактическом типе данных staff_id
.) Если staff_id
в этой строке есть значение NULL
, это не будет равно пустой строке.
Я подозреваю (но не зная вашего варианта использования, так что это просто предположение), что если в любом carts
, связанном с session_id, был введен staff_id, что сеанс считается «вспомогательным», вы бы захотели вернуть «ВСПОМОГАТЕЛЬНЫЙ». То есть вы хотели бы возвращать «БЕЗ ПОСТОРОННЕЙ ПОМОЩИ», только если ни водном carts
из не заполнен staff_id. Чтобы получить это, я бы заключил это выражение IF в агрегатную функцию MIN ().
Редактировать
Основываясь на запросах, добавленных к вопросу, я бы сделал что-то вроде этого:
SELECT i.session_id
, SUM(IF(i.impressionAction = 'SENDMAIL', 1, 0)) AS EMAIL
, COUNT(IF(i.impressionAction = 'TAPPED', 1, NULL)) AS SCANS
, TIMESTAMPDIFF(SECOND, MIN(i.createDate), MAX(i.createDate)) AS Duration
, MIN(IF(c.staffId IN ('','ollie','Laura'), 'UNASSISTED', 'ASSISTED')) AS EXPERIENCE
FROM Impressions i
JOIN carts c
ON c.session_id <> ''
AND c.session_id = i.session_id
WHERE i.createDate >= '2014-06-23'
AND i.createDate < '2014-06-30'
AND HOUR(i.createDate) >= 10
AND HOUR(i.createDate) < 21
AND i.impressionId NOT LIKE '%made.com'
AND i.impressionId NOT LIKE '%cloudtags.com%'
GROUP
BY i.session_id;
Обратите внимание, что если staff_id
столбец имеет NULL
значение, выражение IF вернет «ASSISTED», потому что NULL не будет равно ни одному из перечисленных значений. Чтобы изменить это, чтобы значение NULL считалось БЕЗ ПОСТОРОННЕЙ ПОМОЩИ, мы могли бы использовать NOT IN
, MIN(IF(c.staffId NOT IN ('','ollie','Laura'), 'ASSISTED', 'UNASSISTED')) AS EXPERIENCE
Кроме того, запрос не вернет ни одной строки из Impressions
, если в carts
нет (по крайней мере, одной) соответствующей строки.
Мы могли бы указать, OUTER JOIN
если бы мы хотели возвращать строки из Impressions
, даже если в carts
нет соответствующей строки. Мы бы указали это, добавив ключевое слово LEFT
перед JOIN
ключевым словом. Обратите внимание, что если мы добавим внешнее соединение, то staff_id
столбец будет NULL
всякий раз, когда соответствующая строка не найдена в carts
. (Мы просто хотим быть уверены, что соответствующим образом обрабатываем потенциальное NULL
значение в выражении IF.)
ПОСЛЕДУЮЩИЕ ПРИМЕЧАНИЯ
Нам понадобился бы a LEFT [OUTER] JOIN
только в том случае, Impressions
если бы мы хотели вернуть строку из, в session_id
которой есть carts
, не отображаемая ни в одной строке в, в.
Если у нас всегда есть строка в carts
для каждой session_id
, которая появляется в Impressions
, вам [INNER] JOIN
достаточно LEFT JOIN
операции возврата строк из таблицы / rowsource на «левой» стороне соединения, даже если соответствующая строка не найдена в таблице / rowsoruce на «правой» стороне. Порядок таблиц действительно имеет значение только с точки зрения LEFT|RIGHT [OUTER] JOIN
, с точки зрения того, какая таблица должна быть на «левой» стороне. При [INNER] JOIN
порядок таблиц не влияет на результирующий набор.
Комментарии:
1. Это потрясающе! Позвольте мне попробовать это и посмотреть, работает ли это. Большое спасибо за подробное объяснение. Это действительно помогло мне понять мотивацию и эвристику. Например, я не знал, что вы могли
JOIN
по двум вещам. Я привык работать в SAS и выполнять что-тоdata
поэтапно, а затем выполнятьmerge
. SQL — это немного другой способ мышления. Можете ли вы еще раз объяснитьNOT IN
часть? Я не уверен, что понял часть переключения. Также почемуMIN
передIF
стоит a? Я понимаю, что простое предоставлениеIF
не работает. Еще раз спасибо!2. Кроме того, если вы можете направить меня к некоторым полезным ресурсам по получению интуиции, стоящей за
JOIN
s, я буду благодарен. Я лучше всего учусь на реальных примерах и коротких упражнениях. Большинство доступных ресурсов плохо справляются с прокачкой интуиции.3. Если в
carts
для данногоsession_id
элемента окажется две строки, то возможно, что одной корзине будет оказана «ПОМОЩЬ», а другой — «БЕЗ ПОСТОРОННЕЙ помощи»; из-заGROUP BY
этого MySQL выберет одну из них для возврата. Я добавилMIN()
aggregate, чтобы сделать результат детерминированным, в данном случае, чтобы гарантировать, что возвращаемым значением будет «ASSISTED». Запрос работал бы нормально (в MySQL, но не в других СУБД) безMIN()
агрегатной функции; мы просто предоставили бы MySQL решать, какая строка изcarts
возвращается (когда вcarts
для данногоsession_id
имеется более одной строки).4. Немного о
NOT IN
, это касалось только обработки случая, когдаc.staffId
естьNULL
(ваш вопрос не указывает, что нам гарантировано, чтоstaffId
значение будет ненулевым.) Итак, вопрос в том, еслиstaffId
равно NULL, должны ли мы возвращать «ASSISTED» или «БЕЗ ASSISTED». С изменением (использоватьNOT IN
вместоIN
), это просто простой способ вернуть «БЕЗ ПОСТОРОННЕЙ помощи», когдаstaffId
естьNULL
.5. Итак, мы используем
LEFT JOIN
здесь правильно?Impressions
вsession_id
таблице есть несколько строк,carts
ноsession_id
в каждой есть по 1 строке в,,. Должны ли мыcarts
тогдаimpressions
присоединиться? Имеет ли значение порядок? Почему?
Ответ №2:
Вам нужен LEFT JOIN
, и carts.StaffID будет равен нулю, если совпадение отсутствует.
select
Impressions.session_id,
sum(if(Impressions.impressionAction = 'SENDMAIL', 1, 0)) as EMAIL,
count(if(Impressions.impressionAction = 'TAPPED', 1, NULL)) as SCANS,
TIMESTAMPDIFF(SECOND, min(Impressions.createDate), max(Impressions.createDate)) AS Duration,
if (carts.staffId is null, 'UNASSISTED', 'ASSITED') as EXPERIENCE
from Impressions
left join carts on Impressions.session_id = carts.session_id
group by Impressions.session_id;
Вы можете запросить часть 2 с помощью inner join и перенести логику взаимодействия в if
функцию.
select session_id,
sum(if(impressionAction = 'SENDMAIL', 1, 0)) as EMAIL,
count(if(impressionAction = 'TAPPED', 1, NULL)) as SCANS,
TIMESTAMPDIFF(SECOND, min(createDate), max(createDate)) AS Duration,
if(b.staffId in ('', 'ollie', 'Laura'), 'UNASSISTED', 'ASSISTED') EXPERIENCE
from Impressions
where session_id a
join carts b on a.session_id = b.session_id
where a.session_id <> '' AND b.createDate >= '2014-06-23'AND b.createDate < '2014-06-30'
AND HOUR(b.createDate) >= 10 AND HOUR(b.createDate) < 21
AND b.impressionId NOT LIKE '%made.com' AND b.impressionId NOT LIKE '%cloudtags.com%'
group by a.session_id;
Комментарии:
1. Помогите мне, объяснив мыслительный процесс, пожалуйста. Я еще не использовал ОБЪЕДИНЕНИЯ. Вы также запускали это? Решает ли это проблему с инструкцией IF, о которой я упоминал. Заранее спасибо!
2. Мне нужны некоторые образцы данных, чтобы протестировать это. можете ли вы создать [sqlfiddle](sqlfiddle.com )?
3. Позвольте мне поработать над этим. Я никогда не делал sql fiddle. Я запустил код, и он выполняется с моей стороны. Однако, вот что я сделал тем временем. По сути, объединение этих двух частей должно привести к тому же результату, что и у вас, если оно правильное. Как бы вы изменили свой код в соответствии с изменениями (особенно
where
предложения)?4. Достигнут лимит символов; пожалуйста, смотрите Редактирование моего первоначального вопроса.