SQL Multiple table JOINS, GROUP BY and HAVING - mysql

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:
which are not verified
which belongs to a user who has no, i.e 0, verified emails
which belongs to a user who has no, i.e 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, i.e. returns all emails which are not verified
u implements the 2nd and 3rd condition, i.e. 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

Related

Normalise ID where 2 FK exist in row

I am trying to create a view to normalise "Sports Fixtures", however I have the ID of both the home team and the away team in a row in the fixtures table. When trying to normalise these, how can I get the team name of the relevant team?
select cast(`f`.`datetime` as date) AS `date`
, cast(`f`.`datetime` as time) AS `time`
, (select `t`.`name`
from (`fixturef_testing`.`teams` `t`
join `fixturef_testing`.`fixtures` `f`
on((`f`.`hometeamid` = `t`.`id`)))
where (`t`.`id` = `f`.`hometeamid`)) AS `hometeam`
, (select `t`.`name`
from (`fixturef_testing`.`teams` `t`
join `fixturef_testing`.`fixtures` `f`
on((`f`.`awayteamid` = `t`.`id`)))
where (`t`.`id` = `f`.`awayteamid`)) AS `awayteam`
, `u`.`name` AS `referee`,`c`.`name` AS `competition`
from ((`fixturef_testing`.`fixtures` `f`
left join `fixturef_testing`.`users` `u`
on((`u`.`id` = `f`.`refereeid`)))
left join `fixturef_testing`.`competition` `c`
on((`c`.`id` = `f`.`competitionid`)))
where (`f`.`active` = 1)
Fixtures has hometeamid and awayteamid
Teams has id and name
I have tried a a subquery but it returns multiple results.
Any help/advise is appreciated.
Teams
Fixtures
You need to join to teams twice, once for home team, once for away.
Think of it this way: for each foreign key you have to the same table, you need another join.
Though I'm not sure why you have the multiple joins to fixturef_testing one for user and one for completion...
Also, I'm not a fan of putting everything in the back ticks. I'd rather only use that when I have a reserved/keyword to make it more readable.
SELECT cast(f.`datetime` as date) AS `date`
, cast(f.`datetime` as time) AS `time`
, HT.Name hometeam
, AT.Name awayteam
, u.name AS referee
, c.name AS competition
FROM fixturef_testing.fixtures f
--Are the next 2 joins really needed?--
LEFT JOIN fixturef_testing.users u
on u.id = f.refereeid
LEFT JOIN fixturef_testing.competition c
on c.id = f.competitionid
--Not sure what the above joins are for...
--Get the table Teams record for the home team
LEFT JOIN Teams HT
on HT.ID = f.hometeamID
--Get the table Teams record for the away team
LEFT JOIN Teams AT
on AT.ID = f.awayTeamID
WHERE f.active = 1

MySQL query on two tables with conditions

I have two tables:
users and users_img.
I need to create one query to select all users from users table that have zip column not empty AND have img column empty in users_images (both tables have user ids: users.id and users_img.user_id so those could be joined).
users
id
name
zip
last_time
users_ids
id
user_id (same as id in users)
img
I tried this:
SELECT * FROM `users` JOIN `users_ids` on users.id = users_ids.user_id
WHERE `zip` != '' AND `img` = '' ORDER BY `last_time` DESC
with no luck. I know is supposed to be quite simple.
it will also work for you
SELECT * FROM `users` as `u`
JOIN `users_ids` as `uid` on `u`.`id` = `uid`.`user_id`
WHERE `u`.`zip` IS NOT NULL
AND `uid`.`img` IS NULL
ORDER BY `u`.`last_time` DESC
I have modified your query to check for Null values also in OR
Try this:
SELECT * FROM `users`
LEFT JOIN `users_ids` on users.id = users_ids.user_id
WHERE (TRIM(`zip`) != '' OR `zip` is not null) AND
(TRIM(`img`) = '' OR `img` is null) ORDER BY `last_time` DESC
This worked:
SELECT * FROM `users` as `u`
LEFT JOIN `users_ids` as `uid` on `u`.`id` = `uid`.`user_id`
WHERE `u`.`zip` != ''
AND `uid`.`img` IS NOT NULL
ORDER BY `u`.`last_time` DESC

MySQL Invalid Use of Group Function

I'm getting Error Code: 1111. Invalid use of group function when trying to build a query in MySQL. Apparently MySQL doesn't support WITH, which is what I'm more comfortable using.
SELECT DISTINCT `UserID`
FROM `user`
INNER JOIN `message`
ON `user`.`Message` = `message`.`Recipient`
WHERE MAX(`TotalSize`) IN (
SELECT SUM(`message`.`Size`) as `TotalSize`
FROM `message`
INNER JOIN `category`
ON `message`.`CatID` = `category`.`CatID`
WHERE `category`.`CatName` = 'Inbox'
GROUP BY `Recipient`);
SELECT `UserID`, MAX(SUM(message.Size)) as TotalSize
FROM `user`
INNER JOIN `message`
ON `user`.`Message` = `message`.`Recipient`
INNER JOIN category
ON message.CatID = category.CatID
WHERE category.CatName = 'Inbox'
GROUP BY UserID
You need to use HAVING clause instead of WHERE MAX(TotalSize)
SELECT DISTINCT `UserID`
FROM `user`
INNER JOIN `message`
ON `user`.`Message` = `message`.`Recipient`
GROUP BY `UserID`
HAVING MAX(`message`.`Size`) IN (
SELECT SUM(`message`.`Size`) as `TotalSize`
FROM `message`
INNER JOIN `category`
ON `message`.`CatID` = `category`.`CatID`
WHERE `category`.`CatName` = 'Inbox'
GROUP BY `Recipient`);
Group functions are not accessible in WHERE clause , HAVING can filter on aggregates.

MySQL Joined SELECT COUNT()

I wish to select results across several tables, but I only want to return rows based on the COUNT() result of joined SELECT query.
Here's how the query looks at the moment:
SELECT `s`.`venue_id` AS `id`,
CONCAT(`u`.`First_name`, ' ', `u`.`Surname`) AS `user_name`,
`u`.`avatar` AS `avatar`,
`u`.`facebookId` AS `fid`,
`x`.`imgs` AS `num_imgs`
FROM `new_shortlists_venues` `s`
INNER JOIN `new_shortlists` ON `new_shortlists`.`id` = `s`.`list_id`
INNER JOIN `users` `u` ON `u`.`id` = `new_shortlists`.`bride_id`
LEFT JOIN (SELECT `listing_id`, COUNT(*) `imgs` FROM `listingsImages`) `x` ON `s`.`venue_id` = `x`.`listing_id`
WHERE `new_shortlists`.`venues` > 4
AND `new_shortlists`.`bride_id` != 0
GROUP BY `s`.`list_id`
ORDER BY `s`.`date_added` DESC
LIMIT 6
For some reason, the query returns NULL for num_imgs. Essentially, I'd like to select only records which have at least 4 records in the listingsImages table.
Please note that this is for a legacy system, and I didn't design the DB! As a result, I have now option to change the schema.
You left off the GROUP BY of your subquery. Your current query is returning COUNT(*) associated with a random listing_id. Add GROUP BY listing_id and you should return the correct counts.
SELECT `s`.`venue_id` AS `id`,
CONCAT(`u`.`First_name`, ' ', `u`.`Surname`) AS `user_name`,
`u`.`avatar` AS `avatar`,
`u`.`facebookId` AS `fid`,
`x`.`imgs` AS `num_imgs`
FROM `new_shortlists_venues` `s`
INNER JOIN `new_shortlists` ON `new_shortlists`.`id` = `s`.`list_id`
INNER JOIN `users` `u` ON `u`.`id` = `new_shortlists`.`bride_id`
LEFT JOIN (SELECT `listing_id`, COUNT(*) `imgs`
FROM `listingsImages`
GROUP BY `listing_id`) `x` ON `s`.`venue_id` = `x`.`listing_id`
WHERE `new_shortlists`.`venues` > 4
AND `new_shortlists`.`bride_id` != 0
GROUP BY `s`.`list_id`
ORDER BY `s`.`date_added` DESC
LIMIT 6
And to return those with at least 4 records, just add that constraint to your WHERE criteria:
AND `x`.`imgs` >= 4
This might be the culprit:
ON `s`.`venue_id` = `x`.`listing_id`

Combine conditions with AND in Mysql ON clause

I have the following query, that I use to filter rows based on software_id and level.
I've put the conditions in the ON-Clause since I still want rows returned, where there are no corresponding rows in the JobadvertsSoftware Table.
SELECT `Jobadvert`.`id` FROM `jobadverts` AS `Jobadvert`
LEFT JOIN `users` AS `User` ON (`Jobadvert`.`user_id` = `User`.`id`)
LEFT JOIN `jobadverts_softwares` AS `JobadvertsSoftware_0` ON
(`Jobadvert`.`id` = 'JobadvertsSoftware_0.jobadvert_id' AND
(`JobadvertsSoftware_0`.`software_id` = '32' AND
`JobadvertsSoftware_0`.`level` IN ('1', 4)))
WHERE `Jobadvert`.`active` = 1 AND `User`.`premium` = '1' AND
Jobadvert`.`department_id` = (5)
GROUP BY `Jobadvert`.`id`
The problem is that it also returns JobadvertsSoftware-rows where level is e.g. 2
Again, if I put that in the WHERE clause it will filter out the rows where there are not JobadvertsSoftware which it shouldn't do.
How can I tell MySQL to return all rows of Jobadvert, where the given software_id AND the level matches or are NULL?
Try this:
SELECT `Jobadvert`.`id`, `JobadvertsSoftware_0`.`level`
FROM `jobadverts` AS `Jobadvert`
LEFT JOIN `users` AS `User` ON (`Jobadvert`.`user_id` = `User`.`id`)
INNER JOIN `jobadverts_softwares` AS `JobadvertsSoftware_0` ON
(`Jobadvert`.`id` = 'JobadvertsSoftware_0.jobadvert_id' AND
(`JobadvertsSoftware_0`.`software_id` = '32' AND
`JobadvertsSoftware_0`.`level` IN ('1', 4)))
WHERE `Jobadvert`.`active` = 1 AND `User`.`premium` = '1' AND
Jobadvert`.`department_id` = (5)
GROUP BY `Jobadvert`.`id`
Saludos!
Try this( it's a bit unclear if some fields are numeric on string, it might be corrected):
SELECT distinct(`Jobadvert`.`id`) FROM `jobadverts` AS `Jobadvert`
LEFT JOIN `users` AS `User` ON (`Jobadvert`.`user_id` = `User`.`id`)
LEFT JOIN `jobadverts_softwares` AS `JobadvertsSoftware_0`
ON `Jobadvert`.`id` = `JobadvertsSoftware_0.jobadvert_id`
WHERE
`Jobadvert`.`active` = 1
AND `User`.`premium` = '1'
AND Jobadvert`.`department_id` = (5)
AND JobadvertsSoftware_0`.`software_id` = '32'
AND (`JobadvertsSoftware_0`.`level` IN (1, 4) OR `JobadvertsSoftware_0`.`level` is NULL)
Assuming the level parameters in your ON clause is not needed for the join you can do a nested SELECT on your Software table to clear out the data you do not need first:
SELECT * FROM jobadverts_softwares
WHERE
(`software_id` = 32 OR `software_id` IS NULL) --Select all software_id that are 32 or null
AND
`level` IN (1, 4)
Then you can incorporate this as a nested statement in your main SQL query so you only join on the data which is filtered in your LEFT JOIN but keep any null values that you needed:
SELECT `Jobadvert`.`id`
FROM `jobadverts` AS `Jobadvert`
LEFT JOIN `users` AS `User`
ON `Jobadvert`.`user_id` = `User`.`id`
LEFT JOIN
( --Software Subquery
SELECT `jobadvert_id`, `level` FROM jobadverts_softwares
WHERE
(`software_id` = 32 OR `software_id` IS NULL) --Select all software_id that are 32 or null
AND
`level` IN (1, 4)
) AS `software_subquery`
ON `Jobadvert`.`id` = `software_subquery`.`jobadvert_id`
WHERE
`Jobadvert`.`active` = 1
AND
`User`.`premium` = '1'
AND
`Jobadvert`.`department_id` = 5
ORDER BY `Jobadvert`.`id` --Changed GROUP BY to ORDER BY as not needed
This is untested but try it out and see if this will help.
Try this:
SELECT j.id
FROM jobadverts j
LEFT JOIN User u ON (j.user_id = u.id)
LEFT JOIN jobadverts_softwares AS js ON
(j.id = js.jobadvert_id)
WHERE j.active = 1
AND u.premium = '1'
AND j.department_id = (5)
AND js.software_id` = '32'
AND js.level IN ('1', 4)))
You won't need a GROUP BY unless summing data in some way.