Запрос выбора в запросе выбора?

#mysql #sql

#mysql #sql

Вопрос:

Итак, я хочу сравнить новых пользователей с возвращающимися пользователями в таблице по месяцам. У меня есть таблица, которая содержит каждое действие с именем пользователя и отметкой даты.

Я могу легко вытащить пользователей, которые выполнили действие, например, в январе 2011 года. Чтобы увидеть, является ли каждый пользователь новым, мне нужно затем запустить их имя пользователя против всех предыдущих записей (до января 2011 года).

В моих попытках я придумал следующее:

   SELECT ini.username,
         MIN(ini.datetime) AS firstAction,
         COUNT(ini.datetime) AS numMonth,
         (SELECT COUNT(*) 
            FROM tableActions tot
           WHERE tot.username = ini.username
             AND tot.datetime < '201101%' 
             AND tot.datetime > '201001%') AS numTotal
    FROM tableActions ini
   WHERE DATETIME >= '201101%' 
     AND DATETIME < '201102%'
GROUP BY ini.username
ORDER BY firstAction
  

Это не ошибка, но и не завершается. Кажется, довольно интенсивным.

Комментарии:

1. В чем ваш вопрос? Как это «исправить»? Укажите ваши требования.

2. Тип данных datetime столбца … varchar ? плохая идея. Ваш запрос должен быть медленным. Если тип данных столбца datetime datetime равен, то я не понимаю, что это за сравнение >= '201101%' ?

3. Согласен, что такое объявление столбца для «datetime»?

4. Столбец datetime — varchar , я могу это исправить, спасибо, что указали на это.

5. @Tomalakgeret’kal вопросы в том, как можно сначала запросить все действия в январе 2011 года, а затем сравнить это число со всеми предыдущими действиями. По сути, я ищу новых пользователей, выполняющих действия в январе, устраняя всех, кто ранее выполнял действие. Надеюсь, так будет лучше.

Ответ №1:

Вы можете переписать запрос, который будет (при условии, что tableactions.datetime это DATETIME тип данных):

    SELECT ini.username,
          MIN(ini.datetime) AS firstAction,
          COUNT(ini.datetime) AS numMonth,
          x.numTotal
     FROM tableActions ini
LEFT JOIN (SELECT tot.username,
                  COUNT(*) AS numTotal
             FROM tableActions tot
            WHERE tot.datetime > '2010-01-01'
              AND tot.datetime < '2011-01-01'
         GROUP BY tot.username) x ON x.username = ini.username
    WHERE ini.datetime BETWEEN '2011-01-01' AND '2011-01-31'
 GROUP BY ini.username
 ORDER BY firstAction
  

Может помочь включить индекс username как минимум, хотя стоит рассмотреть использование покрывающего индекса username datetime .

datetime Сравнение выглядит подозрительно — LIKE единственное, что поддерживает подстановочные знаки.

Комментарии:

1. была такая же мысль — количество (дата-время)?

2. datetime — это varchar . Должен ли я сначала исправить это, а затем попытаться выполнить запрос? Спасибо.

3. Не могу сказать, будет ли запрос работать как есть, но идея правильная. Кстати, вы можете использовать COUNT(1) вместо COUNT(*) .

4. @Tim Cutting: Да — нетривиально что-то менять, когда у вас есть данные, но правильный ввод данных упростит поиск / улучшит производительность.

Ответ №2:

Я думаю, что простого соединения таблицы с самим собой с подходящим предложением where будет достаточно (этот запрос прямо из моей головы, не проверен):

 SELECT    curr_activity.username, COUNT(prev_activity.username) AS did_something_in_the_past
FROM      tableActions AS curr_activity
LEFT JOIN tableActions AS prev_activity ON curr_activity.username = prev_activity.username 
WHERE     curr_activity.datetime >= '2011-01-01' AND curr_activity.datetime < '2011-02-01'
AND       prev_activity.datetime <  '2011-01-01' 
GROUP BY  curr_activity.username
  

Индексы имеют значение. Вы должны проиндексировать столбец username and datetime , а datetime столбец должен быть a datetime или аналогичным типом данных.

Ответ №3:

 SELECT username,
MIN(datetime) AS firstAction,
MAX(datetime) AS numMonth,
COUNT(*) AS numTotal
GROUP BY ini.username
HAVING numTotal > 1 
WHERE DATETIME between '201001%' AND '201102%'
ORDER BY username
* I think this collapsed version is what you need ?  
  

Комментарии:

1. Существуют разные критерии даты — может быть опечатка, в противном случае я согласен

Ответ №4:

Я думаю, вы можете заменить

 SELECT COUNT(*) 
        FROM tableActions tot
       WHERE tot.username = ini.username
         AND tot.datetime < '201101%' 
         AND tot.datetime > '201001%'
  

с

 SELECT 1
        FROM tableActions tot
       WHERE tot.username = ini.username
         AND tot.datetime < '201101%' 
         AND tot.datetime > '201001%' LIMIT 1
  

, поэтому ему не нужно перебирать все записи и подсчитывать их.

Комментарии:

1. Да, TOP предназначен для SQL Server, поэтому вместо этого вам нужно использовать LIMIT 1 в конце подзапрос. Я не большой эксперт в MySQL, поэтому просто предположим, что это сработает.