使用LEFT JOIN时如何排除行(MySQL)

时间:2016-09-20 07:24:17

标签: mysql sql database activerecord

我有用户,有很多帖子。我想构建一个SQL查询,它将在1个查询(没有子查询)中执行以下操作,并且如果可能的话,希望没有联合。我知道我可以用联盟做到这一点,但我想知道是否可以仅使用连接来完成。

我想获得一个不同的活跃用户列表:

  1. 没有帖子
  2. 没有批准的帖子
  3. 这是我到目前为止所拥有的:

    SELECT DISTINCT u.*
    FROM users u
      LEFT JOIN posts p
        ON p.user_id = u.id
      LEFT JOIN posts p2
        ON p2.user_id = u.id
    WHERE u.status = 'active'
      AND (p.status IS NULL
      OR p2.status != 'approved');
    

    问题是用户有多个帖子且一个是活动的。这仍然会返回我不想要的用户。如果用户有活动帖子,则应将其从结果集中删除。有什么想法吗?

    这是数据的样子:

    mysql> select * from users;
    +----+---------+
    | id | status  |
    +----+---------+
    |  1 | active  |
    |  2 | pending |
    |  3 | pending |
    |  4 | active  |
    |  5 | active  |
    +----+---------+
    5 rows in set (0.00 sec)
    
    mysql> select * from posts;
    +----+---------+----------+
    | id | user_id | status   |
    +----+---------+----------+
    |  1 |       1 | approved |
    |  2 |       1 | pending  |
    |  3 |       4 | pending  |
    +----+---------+----------+
    3 rows in set (0.00 sec)
    

    这里的答案应该只是用户4和5. 4没有批准的帖子,5没有帖子。它不应该包括1,它有一个批准的帖子。

7 个答案:

答案 0 :(得分:2)

不存在:

SELECT u.*
FROM users u
WHERE NOT EXISTS (
   SELECT 1 
   FROM posts p
   WHERE p.user_id = u.id AND p.status = 'approved');

或等效LEFT JOIN

SELECT u.*
FROM users u
LEFT JOIN posts p
   ON p.user_id = u.id AND p.status = 'approved'
WHERE p.user_id IS NULL;

答案 1 :(得分:1)

请解释为什么你不想要JOIN或UNION。如果是因为性能,请考虑以下因素:

CREATE TABLE t ( PRIMARY KEY(user_id) )
    SELECT user_id, MIN(status) AS z
    FROM Posts
    GROUP BY user_id;

SELECT  u.id AS user,
        IFNULL(z, 'no_posts') AS status
    FROM users u
    WHERE u.status = 'active'
    LEFT JOIN t ON t.user_id = u.id
    HAVING status != 'approved';

每个表只会传递一次,因此效率相当高(考虑到查询的复杂性)。

答案 2 :(得分:1)

这个可能会有所帮助:

SELECT DISTINCT u.*
FROM users u
LEFT JOIN posts p ON 1=1
  -- matches only if user has any post
  AND p.user_id = u.id 
  -- matches only if user has any active post
  AND p.status = 'approved'
WHERE 1=1
  -- matches only active users
  AND u.status = 'active'
  -- matches only users with no matches on the LEFT JOIN
  AND p.status IS NULL
;

答案 3 :(得分:1)

根据您的要求并将它们逐字地翻译成SQL,我明白了:

SELECT users.id,
       COUNT(posts.id) as posts_count,
       COUNT(approved_posts.id) as approved_posts_count
FROM users
LEFT JOIN posts ON posts.user_id = users.id
LEFT JOIN posts approved_posts
  ON approved_posts.status = 'approved'
  AND approved_posts.user_id = users.id
WHERE users.status = "active"
GROUP BY users.id
HAVING (posts_count = 0 OR approved_posts_count = 0);

对于上面的测试数据,返回:

4|1|0
5|0|0

即。使用ids 45的用户,其中第一个帖子有1个帖子但没有已批准的帖子,第二个帖子没有帖子。

但是,在我看来,这可以简化,因为任何没有批准帖子的用户也没有帖子,因此条件的结合是不必要的。

在这种情况下,SQL只是:

SELECT users.id,
       COUNT(approved_posts.id) as approved_posts_count
FROM users
LEFT JOIN posts approved_posts
  ON approved_posts.status = 'approved'
  AND approved_posts.user_id = users.id
WHERE users.status = "active"
GROUP BY users.id
HAVING approved_posts_count = 0;

这也会返回相同的两个用户。我错过了什么吗?

答案 4 :(得分:1)

我认为这应该很容易。

SELECT u.`id`, u.`status` FROM `users` u
LEFT OUTER JOIN `post` p ON p.`user_id` = u.`id` AND p.`status` = 'approved'
WHERE u.`status` = 'active' AND p.`id` IS NULL

给出结果为4& 5。

[编辑]只是想补充其中的原因:

  

你。status ='活跃'

这导致排除所有不活跃的用户。

  

p。status ='已批准'

这排除了所有已批准的帖子。

因此,通过使用这两行,我们已经排除了符合条件的所有用户。

[编辑2]

如果您还需要知道有多少待处理和已批准的数量,这里有更新版本:

SELECT u.`id`, u.`status`, SUM(IF(p.`status` = 'approved', 1, 0)) AS `Approved_Posts`, SUM(IF(p.`status` = 'pending', 1, 0)) AS `Pending_Posts`
FROM `test_users` u
LEFT OUTER JOIN `test_post` p ON p.`user_id` = u.`id`
WHERE u.`status` = 'active' 
GROUP BY u.`id`
HAVING SUM(IF(p.`id` IS NOT NULL, 1, 0))

答案 5 :(得分:0)

您可以尝试使用以下查询:

 SELECT DISTINCT u.*
    FROM users u
      LEFT JOIN posts p
        ON p.user_id = u.id
    WHERE 
        u.status = 'active' AND (
        p.user_id IS NULL
        OR p.status != 'approved');

修改

根据更新的问题,上面的查询将包括用户1.如果我们想要阻止它,并且不想使用内部查询,我们可以使用MySQL的group_concat函数来获取所有(不同的状态并查看它是否包含“活动”状态,下面的查询应该给出所需的输出:

SELECT u.id, group_concat(distinct p.status) as statuses
    FROM users u
      LEFT JOIN posts p
        ON u.id = p.user_id
    WHERE 
        u.status = 'active'
group by u.id
having (statuses is null or statuses not like '%approved%');

答案 6 :(得分:0)

试试这个

SELECT DISTINCT u.*
FROM users u LEFT JOIN posts p
    ON p.user_id = u.id
WHERE p.status IS NULL 
  OR p.status != 'approved';