MySQL - LEFT JOIN and NOT IN - mysql

I try to avoid subqueries due to the fact they usually have much lower performance than a proper join.
This is my current NOT working query:
SELECT
a.`email_list_id`, a.`category_id`, a.`name`
FROM
`email_lists`AS a
LEFT JOIN `email_autoresponders` AS b
ON ( a.`website_id` = b.`website_id` )
WHERE
a.`website_id` = [...]
AND a.`category_id` <> 0
AND a.`email_list_id` <> b.`email_list_id`
GROUP BY
a.`email_list_id`
ORDER BY a.`name`
This query works:
SELECT
`email_list_id`, `category_id`, `name`
FROM
`email_lists`
WHERE
`website_id` = [...]
AND `category_id` <> 0
AND `email_list_id` NOT IN (
SELECT
`email_list_id`
FROM
`email_autoresponders`
WHERE `website_id` = [...]
)
GROUP BY
`email_list_id`
ORDER BY
`name`
Is there any way to do this with a left join? I've tried a number of different options.

After rethinking it a bit, this might work i believe:
SELECT
a.`email_list_id`, a.`category_id`, a.`name`
FROM
`email_lists`AS a
LEFT JOIN `email_autoresponders` AS b
ON ( a.`website_id` = b.`website_id` and a.`email_list_id` = b.`email_list_id` )
WHERE
a.`website_id` = [...]
AND a.`category_id` <> 0
AND b.`email_list_id` is NULL
GROUP BY
a.`email_list_id`
ORDER BY a.`name`

For starters, add the inequality check on email_list_id to the join criteria, instead of having it in your WHERE clause:
LEFT JOIN `email_autoresponders` AS b
ON ( a.`website_id` = b.`website_id`
AND a.`email_list_id` <> b.`email_list_id` )
Though I'm not sure the scenario you mention calls for vast optimizations, this is a way to use a join rather than a subquery...

Related

How use custom alias field from select fields in join?

How use custom alias field from select fields in join?
SELECT LOWER(CONCAT(`ufn`.`value`, '.', `uln`.`value`, CAST(RAND()*1e6 AS UNSIGNED))) AS `newlogin`
FROM `user_firstname` AS `ufn`
INNER JOIN `user_lastname` AS `uln`
LEFT JOIN `user` AS `u` ON `u`.`login` = `newlogin`
WHERE `ufn`.`sex` = 'male' AND `u`.`id` IS NULL
ORDER BY RAND()
LIMIT 1
Result: sql-error.png
EDIT:
My solution, but, ...:
SELECT #newlogin
FROM (
SELECT #newlogin := LOWER(CONCAT(`ufn`.`value`, '.', `uln`.`value`, CAST(RAND()*1e6 AS UNSIGNED)))
FROM `user_firstname` AS `ufn`
INNER JOIN `user_lastname` AS `uln`
WHERE `ufn`.`sex` = 'male'
ORDER BY RAND()
LIMIT 1
) AS `ufnX`
LEFT JOIN `user` AS `u` ON `u`.`login` = #newlogin
WHERE `u`.`id` IS NULL
Create the new login in a subquery and join it in:
SELECT LOWER(CONCAT(`ufn`.`value`, '.', `uln`.`value`, nl.num)) AS newlogin
FROM (SELECT CAST(RAND()*1e6 AS UNSIGNED))) AS num
) nl CROSS JOIN
`user_firstname` ufn CROSS JOIN
`user_lastname` uln LEFT JOIN
`user` u
ON `u`.`login` = LOWER(CONCAT(ufn.`value`, '.', uln.`value`, nl.num))
WHERE `ufn`.`sex` = 'male' AND `u`.`id` IS NULL
ORDER BY RAND()
LIMIT 1;
Note I replaced the INNER JOIN with CROSS JOIN. INNER JOIN with no ON makes no sense.
EDIT:
You seem to want to test multiple values to get a match. You could try putting the comparison in the HAVING clause. That clause can reference columns in the SELECT:
SELECT LOWER(CONCAT(ufn.`value`, '.', uln.`value`, CAST(RAND()*1e6 AS UNSIGNED))) AS newlogin
FROM user_firstname` ufn CROSS JOIN
`user_lastname` uln
WHERE `ufn`.`sex` = 'male'
HAVING NOT EXISTS (SELECT 1
FROM user u
WHERE u.login = newlogin
)
ORDER BY RAND()
LIMIT 1;

SUM with CASE Statement in mysql

I have following Mysql query
SELECT c.`id`
,c.`category_name`
,c.`category_type`
,c.bookmark_count
,f.category_id cat_id
,f.unfollow_at
,(
CASE WHEN c.id = f.follower_category_id
THEN (
SELECT count(`user_bookmarks`.`id`)
FROM `user_bookmarks`
WHERE (`user_bookmarks`.`category_id` = cat_id)
AND ((`f`.`unfollow_at` > `user_bookmarks`.`created_at`) || (`f`.`unfollow_at` = '0000-00-00 00:00:00'))
)
ELSE 0 END
) counter
,c.id
,f.follower_category_id follow_id
,c.user_id
FROM categories c
LEFT JOIN following_follower_categories f ON f.follower_category_id = c.id
WHERE c.user_id = 26
ORDER BY `category_name` ASC
and here is output what i am getting after execuation
now i just want to count . here i have field id having value 172 against it i have counter 30,3, 2 and Bookmark_count is 4( i need to include only once)
and i am accepting output for id 172 is 30+3+2+4(bookmark_count only once).
I am not sure how to do this.
Can anybody help me out
Thanks a lot
The following may be the most inefficient query for that purpose, but I added a cover to your query in order to hint at grouping the results.
(I removed the second c.id, and my example may have errors since I couldn't try it.)
SELECT `id`,
`category_name`,
`category_type`,
max(`bookmark_count`),
`cat_id`,
`unfollow_at`,
sum(`counter`)+max(`bookmark_count`) counter,
follow_id`, `user_id`
FROM
(SELECT c.`id`
,c.`category_name`
,c.`category_type`
,c.bookmark_count
,f.category_id cat_id
,f.unfollow_at
,(
CASE WHEN c.id = f.follower_category_id
THEN (
SELECT count(`user_bookmarks`.`id`)
FROM `user_bookmarks`
WHERE (`user_bookmarks`.`category_id` = cat_id)
AND ((`f`.`unfollow_at` > `user_bookmarks`.`created_at`) || (`f`.`unfollow_at` = '0000-00-00 00:00:00'))
)
ELSE 0 END
) counter
,f.follower_category_id follow_id
,c.user_id
FROM categories c
LEFT JOIN following_follower_categories f ON f.follower_category_id = c.id
WHERE c.user_id = 26)
GROUP BY `id`, `category_name`, `category_type`, `cat_id`, `unfollow_at`, `follow_id`, `user_id`
ORDER BY `category_name` ASC

FIltering for Largest Value

I am trying to write a query that will return the highest instance of Site per Operon. Basically each operon can have many sites, but each site has a score. I want to filter the return so that rather than having a given operon listed multiple times (once for each site) it will be listed only once with the highest site.
What I have below seems to return the correct results but it is fairly slow and I wanted to see if there was a faster way of doing this or not.
Sorry if this isn't very clear, MySQL is a totally new world for me and I'm not sure if I'm posing the question in a sensible way.
select `g`.`id` AS `ID`,
`g`.`Name` AS `GENE`,
`o`.`id` AS `OPID`,
`os`.`site` AS `BSID`,
`s`.`Sequence` AS `SITE`,
`s`.`Score` AS `SCORE`
from((((
`METAGENO`.`GENE` `g` )
join `METAGENO`.`OPERON` `o` )
join `METAGENO`.`OPERON_SITE` `os`)
join `METAGENO`.`SITE` `s` )
where(
(`o` .`id` = `g` .`Operon`)
and(`os`.`operon` = `o` .`id` )
and(`s` .`id` = `os`.`site`
and(`s` .`Score` = (select max(`s2`.`Score`)
from(`METAGENO`.`SITE` `s2`)
where(`s2`.`id` = `os`.`site`))))) GROUP BY `o`.`id`
Use
GREATEST()
For two or more arguments.
You could try creating an inline view that calculates all the maxscore for all id and then join.
Select `g`.`id` AS `ID`,
`g`.`Name` AS `GENE`,
`o`.`id` AS `OPID`,
`os`.`site` AS `BSID`,
`s`.`Sequence` AS `SITE`,
`s`.`Score` AS `SCORE`
from((((
`METAGENO`.`GENE` `g` )
join `METAGENO`.`OPERON` `o` )
join `METAGENO`.`OPERON_SITE` `os`)
join `METAGENO`.`SITE` `s` )
INNER JOIN (select max(`s2`.`Score`) as maxScore, `s2`.`id`
from `METAGENO`.`SITE` `s2`
group by `s2`.`id`) maxScore
ON s.Score = maxScore.Score
AND os.site = maxScore.id
where(
(`o` .`id` = `g` .`Operon`)
and(`os`.`operon` = `o` .`id` )
and(`s` .`id` = `os`.`site` ))

Counting rows from a big mysql query (zend)

I a developing in zend and have a rather large mysql query. The query works fine and i get the list I expect. I am doing this using Select->Where.... below is the query.
SELECT DISTINCT `d`.* FROM `deliverable` AS `d` INNER JOIN `groups` AS `g1` ON d.id = g1.deliverable_id INNER JOIN `groupmembers` AS `gm1` ON g1.id = gm1.group_id LEFT JOIN `connection` AS `c` ON d.id = c.downstreamnode_id LEFT JOIN `deliverable` AS `d1` ON c.upstreamnode_id = d1.id INNER JOIN `deliverable` AS `d2` ON CASE WHEN d1.id IS NULL THEN d.id ELSE d1.id END = d2.id INNER JOIN `groups` AS `g` ON d2.id = g.deliverable_id INNER JOIN `groupmembers` AS `gm` ON g.id = gm.group_id WHERE (g1.group_type = 100) AND (gm1.member_id = 1) AND (c.downstreamnode_id IS NULL OR d.restrict_access = 1) AND (g.group_type = 100 OR g.group_type = 110) AND (gm.member_id = 1) AND (d.deliverable_type = 110 OR d.deliverable_type = 100) GROUP BY CASE WHEN c.downstreamnode_id IS NULL THEN d.id ELSE c.downstreamnode_id END
Only problem is when I try to count the rows in a mysql query I only get 1 returned. below is the query
SELECT DISTINCT count(*) AS `rowCount` FROM `deliverable` AS `d` INNER JOIN `groups` AS `g1` ON d.id = g1.deliverable_id INNER JOIN `groupmembers` AS `gm1` ON g1.id = gm1.group_id LEFT JOIN `connection` AS `c` ON d.id = c.downstreamnode_id LEFT JOIN `deliverable` AS `d1` ON c.upstreamnode_id = d1.id INNER JOIN `deliverable` AS `d2` ON CASE WHEN d1.id IS NULL THEN d.id ELSE d1.id END = d2.id INNER JOIN `groups` AS `g` ON d2.id = g.deliverable_id INNER JOIN `groupmembers` AS `gm` ON g.id = gm.group_id WHERE (g1.group_type = 100) AND (gm1.member_id = 1) AND (c.downstreamnode_id IS NULL OR d.restrict_access = 1) AND (g.group_type = 100 OR g.group_type = 110) AND (gm.member_id = 1) AND (d.deliverable_type = 110 OR d.deliverable_type = 100) GROUP BY CASE WHEN c.downstreamnode_id IS NULL THEN d.id ELSE c.downstreamnode_id END
i generate this from by using the same 'select' that generated the first query but I reset the columns and add count in.
$this->getAdapter()->setFetchMode(Zend_Db::FETCH_ASSOC);
$select
->reset( Zend_Db_Select::COLUMNS)
->columns(array('count('.$column.') as rowCount'));
$rowCount = $this->getAdapter()->fetchOne($select);
This method works fine for all my other queries only this one i am having trouble with. I suspect it has something to do the 'CASE' I have in there but it is strange because I am getting the correct rows the the first query. Any ideas. Thanks.
FYI below are two queries that I have working successfully.
SELECT DISTINCT `po`.* FROM `post` AS `po` INNER JOIN `postinfo` AS `p` ON po.postinfo_id = p.id WHERE (p.creator_id = 1) ORDER BY `p`.`date_created` DESC
SELECT DISTINCT count(*) AS `rowCount` FROM `post` AS `po` INNER JOIN `postinfo` AS `p` ON po.postinfo_id = p.id WHERE (p.creator_id = 1) ORDER BY `p`.`date_created` DESC
In this one I have 4 rows returned in the first query and 'int 4' returned for the second one. Does anyone know why it doesnt work for the big query?
Move your DISTINCT.
SELECT COUNT(DISTINCT `po`.*) AS `rowCount` ...
Ok figured it out It was the GROUP BY that was causing only 1 result to be returned. Thanks Interrobang for you help I am sure that using DISTINCT incorrectly will have caused me a headache in the future.
Try using SQL_CALC_FOUND_ROWS in your query?
http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_found-rows
Using SQL_CALC_FOUND_ROWS is mysql-specific, but it's pretty nice for getting a full record count even when your initial query contains a limit. Once you get the count, don't include SQL_CALC_FOUND_ROWS in subsequent queries for extra records since that will cause extra load on your query.
Your initial query would be:
SELECT SQL_CALC_FOUND_ROWS DISTINCT `d`.* FROM `deliverable` AS `d` INNER JOIN `groups` ...
You'll have to do a subsequent call after your initial query executes to get the count by doing a SELECT FOUND_ROWS().
If you do a little searching, you'll find someone who extended Zend_Db_Select to include this ability.

Store the results of a sub-query for use in multiple joins

I have the following MySQL query, which produces the result I want:
SELECT
`l`.`status`,
`l`.`acquired_by`, `a`.`name` AS 'acquired_by_name',
`l`.`researcher`, `r`.`name` AS 'researcher_name',
`l`.`surveyor`, `s`.`name` AS 'surveyor_name'
FROM `leads` `l`
LEFT JOIN (
SELECT '0' AS 'id', 'Unassigned' AS 'name'
UNION ALL
SELECT `id`, `name`
FROM `web_users`
) `r` ON `r`.`id` = `l`.`researcher`
LEFT JOIN (
SELECT '0' AS 'id', 'Unassigned' AS 'name'
UNION ALL
SELECT `id`, `name`
FROM `web_users`
) `s` ON `s`.`id` = `l`.`surveyor`
LEFT JOIN (
SELECT '0' AS 'id', 'Unassigned' AS 'name'
UNION ALL
SELECT `id`, `name`
FROM `web_users`
) `a` ON `a`.`id` = `l`.`acquired_by`
WHERE `l`.`id` = 566
But as you can see, it has the same sub-query in it three times. Is there any way to execute this query once and store the result, so I can LEFT JOIN with the cached results instead of executing the same query three times?
I have tried storing it in a variable:
SET #usercache = (
SELECT '0' AS 'id', 'Unassigned' AS 'name'
UNION ALL
SELECT `id`, `name`
FROM `web_users`
)
...but this gives me an error:
1241 - Operand should contain 1 column(s)
...and some Googling on this error has left me none the wiser.
Does anyone know how I can make this query more efficient? Or am I just worrying about something that doesn't matter anyway?
I am using PHP/MySQLi if it makes any difference.
Do you really need the subqueries? How about this:
SELECT
`l`.`status`,
`l`.`acquired_by`, COALESCE(`a`.`name`, 'Unassigned') AS 'acquired_by_name',
`l`.`researcher`, COALESCE(`r`.`name`, 'Unassigned') AS 'researcher_name',
`l`.`surveyor`, COALESCE(`s`.`name`, 'Unassigned') AS 'surveyor_name'
FROM `leads` `l`
LEFT JOIN `web_users` `r` ON `r`.`id` = `l`.`researcher`
LEFT JOIN `web_users` `s` ON `s`.`id` = `l`.`surveyor`
LEFT JOIN `web_users` `a` ON `a`.`id` = `l`.`acquired_by`
WHERE `l`.`id` = 566
you cannot run it once - you are actually using it three times to get three different results...