简体   繁体   中英

SQL Multiple table JOINS, GROUP BY and HAVING

I've a table structured somewhat similar to this:

CREATE TABLE `user`
 (`id` int, `name` varchar(7));
CREATE TABLE `email`
  (`id` int, `email_address` varchar(50), `verified_flag` tinyint(1),`user_id` int);
CREATE TABLE `social`
 (`id` int,`user_id` int);

INSERT INTO `user`
 (`id`, `name`)
VALUES
 (1,'alex'),
 (2,'jon'),
 (3,'arya'),
 (4,'sansa'),
 (5,'hodor')
;
INSERT INTO `email`
     (`id`,`email_address`,`verified_flag`,`user_id`)
VALUES
 (1,'alex@gmail.com','1',1),
 (2,'jon@gmail.com','0',1),
 (3,'arya@gmail.com','0',3),
 (4,'sansa@gmail.com','1',4),
 (5,'reek@gmail.com','0',3),
 (6,'hodor@gmail.com','0',5),
 (7,'tyrion@gmail.com','0',1)
;
INSERT INTO `social`
     (`id`,`user_id`)
VALUES
 (1,4),
 (2,4),
 (3,5),
 (4,4),
 (5,4)
;

What I want to get is all emails:

  1. which are not verified
  2. which belongs to a user who has no, ie 0, verified emails
  3. which belongs to a user who has no, ie 0, social records

With the below query I'm able to apply the 1st and 3rd condition but not the 2nd one:

SELECT *
FROM `email`
INNER JOIN `user` ON `user`.`id` = `email`.`user_id`
LEFT JOIN `social` ON `user`.`id` = `social`.`user_id`
WHERE `email`.`verified_flag` = 0
GROUP BY `email`.`user_id`,`email`.`email_address`
HAVING COUNT(`social`.`id`) = 0

How can I achieve the result? Here's the sqlfiddle as well

Interesting and tricky one.

I see you've got something going on there. But having and sub queries becomes a VERY bad idea when your tables become large.

See below for an approach. Don't forget to set up your indexes!

SELECT * from email
LEFT JOIN social on email.user_id = social.user_id

 -- tricky ... i'm going back to email table to pick verified emails PER user
LEFT JOIN email email2 on email2.user_id = email.user_id AND email2.verified_flag = 1
WHERE
     -- you got this one going already :)
    email.verified_flag = 0

     -- user does not have any social record
    AND social.id is null

     -- email2 comes in handy here ... we limit resultset to include only users that DOES NOT have a verified email
    AND email2.id is null
ORDER BY email.user_id asc;

You can use the following query:

SELECT e.`id`, e.`email_address`, e.`verified_flag`, e.`user_id`
FROM (
   SELECT `id`,`email_address`,`verified_flag`,`user_id`
   FROM `email`
   WHERE `verified_flag` = 0) AS e
INNER JOIN (
   SELECT `id`, `name`
   FROM  `user` AS t1
   WHERE NOT EXISTS (SELECT 1
                     FROM  `email` AS t2
                     WHERE `verified_flag` = 1 AND t1.`id` = t2.`user_id`)

         AND 

         NOT EXISTS (SELECT 1
                     FROM  `social` AS t3
                     WHERE t1.`id` = t3.`user_id`)
) AS u ON u.`id` = e.`user_id`;

This query uses two derived tables:

  • e implements the first condition, ie returns all emails which are not verified
  • u implements the 2nd and 3rd condition, ie it returns a set of all users that have no verified emails and have no social records.

Performing an INNER JOIN between e and u returns all emails satisfying condition no. 1 which belong to users satisfying conditions no. 2 and 3.

Demo here

You can alternatively use this query:

SELECT *
FROM `email`
WHERE `user_id` IN (
   SELECT `email`.`user_id`
   FROM `email`
   INNER JOIN `user` ON `user`.`id` = `email`.`user_id`
   LEFT JOIN `social` ON `user`.`id` = `social`.`user_id`
   GROUP BY `email`.`user_id`
   HAVING COUNT(`social`.`id`) = 0 AND 
          COUNT(CASE WHEN `email`.`verified_flag` = 1 THEN 1 END) = 0 )

The subquery is used in order to select all user_id satisfying conditions no. 2 and 3. Condition no. 1 is redundant since if the user has no verified emails, then there is no way a verified email is related to this user.

Demo here

Simply run a Union Query:

SELECT `user_id`, `email_address`, `verified_flag`, 'No Email' as `Type` 
FROM `email` RIGHT JOIN `user` ON `user`.`id` = `email`.`user_id` 
WHERE `email`.`user_id` IS NULL

UNION

SELECT `user_id`, `email_address`, `verified_flag`, 'Not Verified' as `Type` 
FROM `email` INNER JOIN `user` ON `user`.`id` = `email`.`user_id` 
WHERE `email`.`verified_flag` = 0

UNION

SELECT `user_id`, `email_address`, `verified_flag`, 'No Social' as `Type` 
FROM `email` INNER JOIN `user` ON `user`.`id` = `email`.`user_id`
LEFT JOIN `social` ON `user`.`id` = `social`.`user_id`  
GROUP BY `user_id`, `email_address`, `verified_flag` 
HAVING COUNT(IFNULL(`social`.`id`, 0)) = 0;
    SELECT
      u.id AS u_id
    , u.name AS u_name
    , e.email_address AS e_email
    , e.verified_flag AS e_verify
    , e.user_id AS e_uid
    , s.id AS s_id
    , s.user_id AS u_id
    , COALESCE(ver_e.ver_email_count,0) as ver_email_count
FROM
    email as e
LEFT OUTER JOIN
    user as u
        ON u.id = e.user_id
LEFT OUTER JOIN
    social AS s
        ON u.id = s.user_id
LEFT OUTER JOIN
    (
        SELECT
            COUNT(email_address) AS ver_email_count
            , user_id 
        FROM
            email
    ) AS ver_e
        ON u.id = ver_e.user_id
GROUP BY
    e.user_id
HAVING e.verified_flag = 0
AND
ver_email_count = 0
AND
ISNULL(s.id)

Uses one derived table to get the number of verified email addresses each user has got

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM