2012-06-20 5 views
1

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

В принципе, у меня есть две таблицы: users и resumes. Ниже приведены фрагменты их схем:

users: 
    id signup_time 
resumes: 
    id user_id modified_time 

Теперь, мне нужно, чтобы принести общее количество всех пользователей без резюме в указанный пользователем период времени (все даты UNIX временные метки), сгруппированы по дням, неделям, или месяц - по дате, когда у них не было резюме, загруженного, вообще говоря. Это то, что меня беспокоит больше всего, потому что если не для группировки, запрос может выглядеть следующим образом:

SELECT u.id FROM `jb_users` u WHERE 
    u.id NOT IN (
     SELECT r.user_id FROM `jb_resumes` r 
     WHERE (r.modified_time BETWEEN 1330581600 AND 1335848399) 
    ) AND u.signup_time >= 1330581600 

Так, например, давайте рассмотрим некоторые примеры. Надеюсь, это будет легче понять.

Предположим, что у нас есть данные:

users 
    id signup_time 
    --------------- 
    1 1340214369 (20.06.2012) 
    2 1330754400 (03.03.2012) 
    3 1329285600 (15.02.2012) 
    4 1324447200 (21.12.2011) 
resumes 
    id user_id modified_time 
    -------------------------- 
    1 1  1340214369 (20.06.2012) 
    2 2  1330840800 (04.03.2012) 
    3 2  1340214369 (20.06.2012) 
    4 3  1334506920 (15.04.2012) 
    5 3  1334638800 (17.04.2012) 
    6 2  1334638800 (17.04.2012) 
    7 3  1336798800 (12.05.2012) 

За период времени 01.03.2012 00:00:00 - 30.04.2012 23:59:59 (сгруппированы по месяцам) он должен вернуть:

count user_ids time 
2  3,4   1330840800 (03.2012 - can be any date in the month, in fact) 
1  4   1334506920 (04.2012 - can be any date in the month, in fact) 

за тот же период времени, но сгруппированных ежедневно, он должен вернуть:

count user_ids time 
2  3,4   1330840800 (04.03.2012) 
2  2,4   1334506920 (15.04.2012) 
1  4   1334638800 (17.04.2012) 

Я надеюсь, что этот вопрос достаточно ясен. Если нет, сообщите мне.

Данные обрабатываются с помощью PHP, поэтому, если этого не может быть достигнуто с использованием одного запроса (даже с подзапросами), то также нормально обрабатывать данные с помощью PHP.

спасибо.

+0

Я полностью спутать этим утверждением: > по дате, когда они не имеют резюме закачано Так, гм, вам это нужно, сгруппированные по дата, когда они не представили резюме? –

+0

Вы хотите, чтобы группа была днем ​​или месяцем? Различные запросы или в одном запросе? –

+0

@SomnathMuluk - Мне нужно сгруппировать его по дням, неделям и месяцам. – Pateman

ответ

1

Вот решение, которое я придумал для группировки по месяцам. Я использовал свои данные в моей локальной установки MySQL, чтобы проверить результаты:

SELECT 
    COUNT(*) AS cnt, 
    GROUP_CONCAT(b.id ORDER BY b.id) AS user_ids, 
    a.monthgroup 

FROM 
(
    SELECT MONTH(FROM_UNIXTIME(modified_time)) AS monthgroup 
    FROM jb_resumes 
    WHERE modified_time BETWEEN 
     UNIX_TIMESTAMP('2012-03-01 00:00:00') 
     AND UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    GROUP BY monthgroup 
) a 
CROSS JOIN 
    jb_users b 
LEFT JOIN 
    jb_resumes c ON 
     b.id = c.user_id 
     AND a.monthgroup = MONTH(FROM_UNIXTIME(modified_time)) 
WHERE 
    b.signup_time < UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    AND c.user_id IS NULL 
GROUP BY 
    a.monthgroup 
ORDER BY 
    a.monthgroup 

Result Set

Это немного неуклюжим, поэтому я буду видеть, если я могу придумать более элегантное решение ,

Раствор для дня группировки:

SELECT 
    COUNT(*) AS cnt, 
    GROUP_CONCAT(b.id ORDER BY b.id) AS user_ids, 
    a.daygroup 

FROM 
(
    SELECT MAKEDATE(YEAR(FROM_UNIXTIME(modified_time)), DAYOFYEAR(FROM_UNIXTIME(modified_time))) AS daygroup 
    FROM jb_resumes 
    WHERE modified_time BETWEEN 
     UNIX_TIMESTAMP('2012-03-01 00:00:00') 
     AND UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    GROUP BY daygroup 
) a 
CROSS JOIN 
    jb_users b 
LEFT JOIN 
    jb_resumes c ON 
     b.id = c.user_id 
     AND a.daygroup = MAKEDATE(YEAR(FROM_UNIXTIME(modified_time)), DAYOFYEAR(FROM_UNIXTIME(modified_time))) 
WHERE 
    b.signup_time < UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    AND c.user_id IS NULL 
GROUP BY 
    a.daygroup 
ORDER BY 
    a.daygroup 

Edit: Объяснение месяца группирования запроса:

Поскольку вы попросили для объяснения решения, вот как я понял это :

Что мы должны в первую очередь сделать, это извлечь группы месяцев из всех modified_time с в течение периода времени:

SELECT MONTH(FROM_UNIXTIME(modified_time)) AS monthgroup 
FROM jb_resumes 
WHERE modified_time BETWEEN 
    UNIX_TIMESTAMP('2012-03-01 00:00:00') 
    AND UNIX_TIMESTAMP('2012-04-30 23:59:59') 
GROUP BY monthgroup 

Результирующее в:

Step 1

Тогда для того, чтобы сравнить сочетание каждого monthgroup и каждый пользователь, чтобы выяснить, какие пользователь не измененный раза в monthgroup, мы должны сделать декартовой продукции между monthgroup и всеми пользователями. Так как запрос выше уже использует GROUP BY, мы не можем присоединиться непосредственно в этом запросе, но вместо этого должны обернуть его в подвыборки идти в FROM пункте:

SELECT 
    a.monthgroup, 
    b.* 
FROM 
(
    SELECT MONTH(FROM_UNIXTIME(modified_time)) AS monthgroup 
    FROM jb_resumes 
    WHERE modified_time BETWEEN 
     UNIX_TIMESTAMP('2012-03-01 00:00:00') 
     AND UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    GROUP BY monthgroup 
) a 
CROSS JOIN 
    jb_users b 
-- 
ORDER BY a.monthgroup, b.id #for clarity's sake 

Результирующее в:

Step 2

Теперь у нас есть комбинация monthgroup s и все id s, но мы не хотим включать пользователей, у которых есть signup_time, позже, чем временной диапазон, поэтому мы отфильтровываем их, вводя первое условие в нашем WHERE clau себе:

SELECT 
    a.monthgroup, 
    b.* 
FROM 
(
    SELECT MONTH(FROM_UNIXTIME(modified_time)) AS monthgroup 
    FROM jb_resumes 
    WHERE modified_time BETWEEN 
     UNIX_TIMESTAMP('2012-03-01 00:00:00') 
     AND UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    GROUP BY monthgroup 
) a 
CROSS JOIN 
    jb_users b 
WHERE 
    b.signup_time < UNIX_TIMESTAMP('2012-04-30 23:59:59') 
-- 
ORDER BY a.monthgroup, b.id #for clarity's sake 

Результирующее в:

Step 3

Уведомление id1 отброшено. Теперь мы можем сделать наше сравнение с помощью LEFT JOIN:

SELECT 
    a.monthgroup, 
    b.*, 
    c.* 
FROM 
(
    SELECT MONTH(FROM_UNIXTIME(modified_time)) AS monthgroup 
    FROM jb_resumes 
    WHERE modified_time BETWEEN 
     UNIX_TIMESTAMP('2012-03-01 00:00:00') 
     AND UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    GROUP BY monthgroup 
) a 
CROSS JOIN 
    jb_users b 
LEFT JOIN 
    jb_resumes c ON 
     b.id = c.user_id 
     AND a.monthgroup = MONTH(FROM_UNIXTIME(modified_time)) 
WHERE 
    b.signup_time < UNIX_TIMESTAMP('2012-04-30 23:59:59') 
-- 
ORDER BY a.monthgroup, b.id #for clarity's sake 

Результирующее в:

Step 4

Здесь мы LEFT JOIN ИНГ при условии, что пользователь имеет модификацию резюме в jb_resumesИ, что изменение произошло в течение месяца в размере monthgroup. Если у пользователя нет модификации возобновления в этом месяце, LEFT JOIN возвращает NULL для значений в таблице.Мы ХОТИТЕ тех пользователей, где условия не удовлетворяют, таким образом, мы должны поместить наше второе условие в WHERE пункте:

SELECT 
    a.monthgroup, 
    b.*, 
    c.* 
FROM 
(
    SELECT MONTH(FROM_UNIXTIME(modified_time)) AS monthgroup 
    FROM jb_resumes 
    WHERE modified_time BETWEEN 
     UNIX_TIMESTAMP('2012-03-01 00:00:00') 
     AND UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    GROUP BY monthgroup 
) a 
CROSS JOIN 
    jb_users b 
LEFT JOIN 
    jb_resumes c ON 
     b.id = c.user_id 
     AND a.monthgroup = MONTH(FROM_UNIXTIME(modified_time)) 
WHERE 
    b.signup_time < UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    AND c.user_id IS NULL 
-- 
ORDER BY a.monthgroup, b.id #for clarity's sake 

Результирующее в:

Step 5

Наконец, мы можем сгруппировать по monthgroup поля и положить в нашей COUNT() и GROUP_CONCAT() функции:

SELECT 
    COUNT(*) AS cnt, 
    GROUP_CONCAT(b.id ORDER BY b.id) AS user_ids, 
    a.monthgroup 

FROM 
(
    SELECT MONTH(FROM_UNIXTIME(modified_time)) AS monthgroup 
    FROM jb_resumes 
    WHERE modified_time BETWEEN 
     UNIX_TIMESTAMP('2012-03-01 00:00:00') 
     AND UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    GROUP BY monthgroup 
) a 
CROSS JOIN 
    jb_users b 
LEFT JOIN 
    jb_resumes c ON 
     b.id = c.user_id 
     AND a.monthgroup = MONTH(FROM_UNIXTIME(modified_time)) 
WHERE 
    b.signup_time < UNIX_TIMESTAMP('2012-04-30 23:59:59') 
    AND c.user_id IS NULL 
GROUP BY 
    a.monthgroup 
ORDER BY 
    a.monthgroup 

Предоставление нам желаемого результата:

Result Set

+0

Пока это выглядит и работает ** ОЧЕНЬ ХОРОШО **! Если бы вы могли объяснить запрос или даже просто его, это было бы более чем идеально. Если нет, сообщите мне, и я все равно приму ваш ответ. – Pateman

+0

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

+0

Мне удалось выяснить, как избежать использования коррелированного подзапроса 'NOT EXISTS' и заменить его на' LEFT JOIN' - Решение отредактировано. –

0

Попробуйте это:

SELECT count(u.id) FROM `jb_users` u WHERE 
     u.id NOT IN (
      SELECT distinct r.user_id FROM `jb_resumes` r 
      WHERE (r.modified_time BETWEEN 1330581600 AND 1335848399) 
) AND u.signup_time >= 1330581600 GROUP BY FROM_UNIXTIME(u.signup_time) ORDER BY u.signup_time 

FROM_UNIXTIME вернет метку времени в формате даты.

Он вернет количество пользователей в пределах определенной группы времени по дате. Вы можете конвертировать формат даты в соответствии с вашим требованием.

Я добавил DISTINCT ключевое слово во внутреннем запросе выбора, поскольку один пользователь может обновлять резюме более одного раза, так что иначе вы можете получить эту запись, которая даже не попадает в этот диапазон дат.

+0

Спасибо, Нишу, но как этот запрос должен группироваться по дате? – Pateman

+0

Я думаю, что его вопрос на самом деле о том, как сделать группировки для внешнего запроса. Кстати, Патман, я думаю, что «порядок» в подзаголовке нужно удалить - это не полезно и может замедлить запрос. – ametren

+0

@ametren, да, вы правы. Я просто экспериментировал и забыл выбросить его. – Pateman

0

Не уверен, что это сработает, но вы можете попробовать присоединиться к if.

SELECT DISTINCT 
if(r.modified_time NOT BETWEEN 1330581600 AND 1335848399, u.id, null) as UID 
FROM `jb_users` u 
Left Join `jb_resumes` r ON u.id = r.user_id 
WHERE 
u.signup_time >= 1330581600 
+0

@SuperMykEI, пожалуйста, посмотрите на желаемый результат, который я ожидаю. – Pateman

Смежные вопросы