2012-06-20 64 views
1

我知道标题听起来像已经有几十个类似的问题,但我认为这个有点不同。不过,如果已经有类似的问题,请给我指出。从一个表中获取不在另一个表中的记录

基本上,我有两个表格:usersresumes。下面是他们的模式的片段:

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 

编辑:为期一个月的分组查询的说明:

既然你问了解决方案的说明,这里是我想通了:

我们首先要做的是在一段时间内从所有modified_time s中提取月份分组:

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子句中:

Step 2

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 

,导致

现在我们有monthgroup s和所有id s的组合,但我们不想包含晚于时间范围的signup_time的用户,所以我们通过在我们的WHERE clau中引入第一个条件来过滤它们SE:

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。我们WANT那些条件不满足的用户,因此,我们必须把我们的第二个条件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将返回Unix时间戳为日期格式。

它会按日期返回特定时间范围内的总用户数。您可以根据您的要求转换日期格式。

我加了DISTINCT关键字在内部选择查询中,因为一个用户可以更新一次以上的简历,否则你可以得到那个甚至不在该日期范围之间的记录。

+0

谢谢,Nishu,但那个查询应该如何按日期分组? – Pateman

+0

我认为他的问题实际上是关于如何对外部查询进行分组。顺便说一下,Pateman,我认为在子查询中'order by'需要被删除 - 这是没有用的,可能会减慢查询速度。 – 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

相关问题