MySQL извлекает столбцы из подзапроса, чтобы добавить его к основным столбцам

#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. Достигнут лимит символов; пожалуйста, смотрите Редактирование моего первоначального вопроса.