Subquery vs join - mysql

SELECT
id
FROM
Posts
WHERE
subject_id = 1
OR subject_id IN (
SELECT related_subject_id
FROM RelatedSubjects
WHERE parent_subject_id = 1);
Trying to select all posts for a current subject but also for any sub-subjects which are stored in another lookup table. The above query works, wondering how to accomplish the same thing with a join

SELECT DISTINCT id
FROM Posts AS p
LEFT JOIN RelatedSubjects AS r
ON p.subject_id = r.related_subject_id AND r.parent_subject_id = 1
WHERE p.subject_id = 1 OR r.related_subject_id IS NOT NULL

Assuming proper indexes, UNION often performs better than JOIN with OR:
SELECT p.id
FROM Posts AS p
WHERE p.subject_id = 1
UNION
SELECT p.id
FROM RelatedSubjects AS r
JOIN Posts AS p
ON p.subject_id = r.related_subject_id
WHERE r.parent_subject_id = 1

select
`Posts`.`id`
From posts as `Posts`
LEFT join RelatedSubjects as `RelatedSubjects`
on (`RelatedSubjects`.`related_subject_id` = `Posts`.`subject_id`)
where `Posts`.`subject_id` = 1

Related

Return intersection of 3 tables with joins

I have 3 tables : news, users_regions_favorites and users_categories_favorites. Each news has a region and a category.
I want to return all news that are related the a user's favorites (by the regions or categories he likes). And never return a news twice, so no UNION.
Is there a way to do this with join?
You can indeed use UNION:
SELECT n.id
FROM news n
INNER JOIN users_categories_favorites c
ON c.id = n.catid
WHERE c.uid = 1 -- your user here
UNION
SELECT n.id
FROM news n
INNER JOIN users_regions_favorites r
ON r.id = n.regid
WHERE r.uid = 1; -- your user here
UNION will deduplicate, only UNION ALL won't.
But you can also do this using only JOINs:
SELECT DISTINCT n.id
FROM news n
LEFT JOIN users_categories_favorites c
ON c.id = n.catid
LEFT JOIN users_regions_favorites r
ON r.id = n.regid
WHERE c.uid = 1 OR r.uid = 1; -- your user here
See this fiddle showing both results.
you could make (schematically) :
select distinct
news where exists users_regions_favorites
union
news where exists users_categories_favorites

mysql query optimization steps or how to optimze query

I don't know much about query optimization but I know the order in which queries get executed
FROM clause
WHERE clause
GROUP BY clause
HAVING clause
SELECT clause
ORDER BY clause
This the query I had written
SELECT
`main_table`.forum_id,
my_topics.topic_id,
(
SELECT MAX(my_posts.post_id) FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id
) AS `maxpostid`,
(
SELECT my_posts.admin_user_id FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id ORDER BY my_posts.post_id DESC LIMIT 1
) AS `admin_user_id`,
(
SELECT my_posts.user_id FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id ORDER BY my_posts.post_id DESC LIMIT 1
) AS `user_id`,
(
SELECT COUNT(my_topics.topic_id) FROM my_topics WHERE my_topics.forum_id = main_table.forum_id ORDER BY my_topics.forum_id DESC LIMIT 1
) AS `topicscount`,
(
SELECT COUNT(my_posts.post_id) FROM my_posts WHERE my_topics.topic_id = my_posts.topic_id ORDER BY my_topics.topic_id DESC LIMIT 1
) AS `postcount`,
(
SELECT CONCAT(admin_user.firstname,' ',admin_user.lastname) FROM admin_user INNER JOIN my_posts ON my_posts.admin_user_id = admin_user.user_id WHERE my_posts.post_id = maxpostid ORDER BY my_posts.post_id DESC LIMIT 1
) AS `adminname`,
(
SELECT forum_user.nick_name FROM forum_user INNER JOIN my_posts ON my_posts.user_id = forum_user.user_id WHERE my_posts.post_id = maxpostid ORDER BY my_posts.post_id DESC LIMIT 1
) AS `nickname`,
(
SELECT CONCAT(ce1.value,' ',ce2.value) AS fullname FROM my_posts INNER JOIN customer_entity_varchar AS ce1 ON ce1.entity_id = my_posts.user_id INNER JOIN customer_entity_varchar AS ce2 ON ce2.entity_id=my_posts.user_id WHERE (ce1.attribute_id = 1) AND (ce2.attribute_id = 2) AND my_posts.post_id = maxpostid ORDER BY my_posts.post_id DESC LIMIT 1
) AS `fullname`
FROM `my_forums` AS `main_table`
LEFT JOIN `my_topics` ON main_table.forum_id = my_topics.forum_id
WHERE (forum_status = '1')
And now I want to know if there is any way to optimize it ? Because all the logic is written in Select section not From, but I don't know how to write the same logic in From section of the query ?
Does it make any difference or both are same ?
Thanks
Correlated subqueries should really be a last resort, they often end up being executed RBAR, and given that a number of your subqueries are very similar, trying to get the same result using joins is going to result in a lot less table scans.
The first thing I note is that all of your subqueries include the table my_posts, and most contain ORDER BY my_posts.post_id DESC LIMIT 1, those that don't have a count with no group by so the order and limit are redundant anyway, so my first step would be to join to my_posts:
SELECT *
FROM my_forums AS f
LEFT JOIN my_topics AS t
ON f.forum_id = t.forum_id
LEFT JOIN
( SELECT topic_id, MAX(post_id) AS post_id
FROM my_posts
GROUP BY topic_id
) AS Maxp
ON Maxp.topic_id = t.topic_id
LEFT JOIN my_posts AS p
ON p.post_id = Maxp.post_id
WHERE forum_status = '1';
Here the subquery just ensures you get the latest post per topic_id. I have shortened your table aliases here for my convenience, I am not sure why you would use a table alias that is longer than the actual table name?
Now you have the bulk of your query you can start adding in your columns, in order to get the post count, I have added a count to the subquery Maxp, I have also had to add a few more joins to get some of the detail out, such as names:
SELECT f.forum_id,
t.topic_id,
p.post_id AS `maxpostid`,
p.admin_user_id,
p.user_id,
t2.topicscount,
maxp.postcount,
CONCAT(au.firstname,' ',au.lastname) AS adminname,
fu.nick_name AS nickname
CONCAT(ce1.value,' ',ce2.value) AS fullname
FROM my_forums AS f
LEFT JOIN my_topics AS t
ON f.forum_id = t.forum_id
LEFT JOIN
( SELECT topic_id,
MAX(post_id) AS post_id,
COUNT(*) AS postcount
FROM my_posts
GROUP BY topic_id
) AS Maxp
ON Maxp.topic_id = t.topic_id
LEFT JOIN my_posts AS p
ON p.post_id = Maxp.post_id
LEFT JOIN admin_user AS au
ON au.admin_user_id = p.admin_user_id
LEFT JOIN forum_user AS fu
ON fu.user_id = p.user_id
LEFT JOIN customer_entity_varchar AS ce1
ON ce1.entity_id = p.user_id
AND ce1.attribute_id = 1
LEFT JOIN customer_entity_varchar AS ce2
ON ce2.entity_id = p.user_id
AND ce2.attribute_id = 2
LEFT JOIN
( SELECT forum_id, COUNT(*) AS topicscount
FROM my_topics
GROUP BY forum_id
) AS t2
ON t2.forum_id = f.forum_id
WHERE forum_status = '1';
I am not familiar with your schema so the above may need some tweaking, but the principal remains - use JOINs over sub-selects.
The next stage of optimisation I would do is to get rid of your customer_entity_varchar table, or at least stop using it to store things as basic as first name and last name. The Entity-Attribute-Value model is an SQL antipattern, if you added two columns, FirstName and LastName to your forum_user table you would immediately lose two joins from your query. I won't get too involved in the EAV vs Relational debate as this has been extensively discussed a number of times, and I have nothing more to add.
The final stage would be to add appropriate indexes, you are in the best decision to decide what is appropriate, I'd suggest you probably want indexes on at least the foreign keys in each table, possibly more.
EDIT
To get one row per forum_id you would need to use the following:
SELECT f.forum_id,
t.topic_id,
p.post_id AS `maxpostid`,
p.admin_user_id,
p.user_id,
MaxT.topicscount,
maxp.postcount,
CONCAT(au.firstname,' ',au.lastname) AS adminname,
fu.nick_name AS nickname
CONCAT(ce1.value,' ',ce2.value) AS fullname
FROM my_forums AS f
LEFT JOIN
( SELECT t.forum_id,
COUNT(DISTINCT t.topic_id) AS topicscount,
COUNT(*) AS postCount,
MAX(t.topic_ID) AS topic_id
FROM my_topics AS t
INNER JOIN my_posts AS p
ON p.topic_id = p.topic_id
GROUP BY t.forum_id
) AS MaxT
ON MaxT.forum_id = f.forum_id
LEFT JOIN my_topics AS t
ON t.topic_ID = Maxt.topic_ID
LEFT JOIN
( SELECT topic_id, MAX(post_id) AS post_id
FROM my_posts
GROUP BY topic_id
) AS Maxp
ON Maxp.topic_id = t.topic_id
LEFT JOIN my_posts AS p
ON p.post_id = Maxp.post_id
LEFT JOIN admin_user AS au
ON au.admin_user_id = p.admin_user_id
LEFT JOIN forum_user AS fu
ON fu.user_id = p.user_id
LEFT JOIN customer_entity_varchar AS ce1
ON ce1.entity_id = p.user_id
AND ce1.attribute_id = 1
LEFT JOIN customer_entity_varchar AS ce2
ON ce2.entity_id = p.user_id
AND ce2.attribute_id = 2
WHERE forum_status = '1';

SQL: Select Foreign Records Using Complex Join

I want to select a "site" record(s) randomly, and then get the related "channels" for it. Here's what I've tried that doesn't work. Please help. Thank you!
SELECT A.*, B.*
FROM (
SELECT companies.company_name, sites.id
FROM sites
INNER JOIN company_sites ON company_sites.site_id = sites.id
INNER JOIN companies ON companies.id = company_sites.company_id
WHERE sites.active = 1
AND sites.stage_id = 5
GROUP BY sites.id
ORDER BY RAND()
LIMIT 1
)A
JOIN (
SELECT
channels.id
FROM channels
WHERE channels.site_id = A.sites_id
) B ON 1 = 1
Try this:
SELECT just the fields you need
FROM (
SELECT companies.company_name, sites.id id
FROM sites
INNER JOIN company_sites ON company_sites.site_id = sites.id
INNER JOIN companies ON companies.id = company_sites.company_id
WHERE sites.active = 1
AND sites.stage_id = 5
GROUP BY sites.id
ORDER BY RAND()
LIMIT 1
)A
join channels on channels.site_id = id
By the way, if you just wanted the list of channels on each site, you can concatenate them into one field (rather than getting them on separate rows). The query would be simpler:
SELECT companies.company_name, sites.id, group_concat(channels.id) as channels
FROM sites
INNER JOIN company_sites ON company_sites.site_id = sites.id
INNER JOIN companies ON companies.id = company_sites.company_id
inner join channels on channels.site_id = sites.id
WHERE sites.active = 1
AND sites.stage_id = 5
GROUP BY sites.id
ORDER BY RAND()
LIMIT 1

Finding similar records based on a connector table with many-to-many relations

I have 3 tables Products,ProductHas,Props. Needless to say each product has more than one prop which is kept in the ProductHas table. I'm trying to find the Product B which is closest to Product A interms of similarities of their props.
Current structure of the tables look like this.
+----------+----------+-----------+
|Products |ProductHas|Props |
+----------+----------+-----------+
|product_id|product_id|prop_id |
+----------+----------+-----------+
| name | prop_id |description|
+----------+----------+-----------+
Another option
SELECT A.Name, B.Name, COUNT(*)
FROM (
SELECT p.name, pp.description
FROM Products p
INNER JOIN ProductHas ph ON ph.product_id = p.product_id
INNER JOIN Props pp ON pp.prop_id = ph.prop_id
) AS A INNER JOIN (
SELECT p.name, pp.description
FROM Products p
INNER JOIN ProductHas ph ON ph.product_id = p.product_id
INNER JOIN Props pp ON pp.prop_id = ph.prop_id
) AS B ON B.description = A.Description
WHERE A.Name = 'A'
GROUP BY
A.name, B.Name
ORDER BY
COUNT(*) DESC
Try:
select h1.product_id, count(h0.prop_id) count_matches, count(*) total_props
from ProductHas h1
left join ProductHas h0
on h0.product_id = ? and h0.prop_id = h1.prop_id and h0.product_id <> h1.product_id
group by h1.product_id
order by 2 desc
limit 1
you could try a fulltext index on the props table, i
CREATE TABLE Props(
prop_id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
description TEXT,
FULLTEXT (description)
) ENGINE=MyISAM;
(I don't know anything about the size of the description but if you know its limit then you should put it like description VARCHAR(200))
SELECT *
FROM Props prod_a_props,
Props prod_b_props,
ProductHas prod_a_rel
WHERE prod_a_rel.product_id = :your_product_A_id
AND prod_a_props.prop_id = prod_a_rel.prop_id
AND MATCH (prod_b_props.description) AGAINST (prod_a_props.description);
Try something like this:
SELECT B.product_id
FROM Products B
INNER JOIN
ProductHas HB
INNER JOIN
ProductHas HA
INNER JOIN
Products A
ON HA.product_id = A.product_id
ON HA.prop_id = HB.prop_id
AND HA.product_id != HB.product_id
ON B.product_id = HB.product_id
WHERE A.product_id = xxx
GROUP BY B.product_id
ORDER BY COUNT(A.product_id) DESC
LIMIT 1

mysql: multiple join problem

Im trying to select a table with multiple joins, one for the number of comments using COUNT and one to select the total vote value using SUM, the problem is that the two joins affect each other, instead of showing:
3 votes 2 comments
I get 3 * 2 = 6 votes and 2 * 3 comments
This is the query I'm using:
SELECT t.*, COUNT(c.id) as comments, COALESCE(SUM(v.vote), 0) as votes
FROM (topics t)
LEFT JOIN comments c ON c.topic_id = t.id
LEFT JOIN votes v ON v.topic_id = t.id
WHERE t.id = 9
What you're doing is an SQL antipattern that I call Goldberg Machine. Why make the problem so much harder by forcing it to be done in a single SQL query?
Here is how I would really solve this problem:
SELECT t.*, COUNT(c.id) as comments
FROM topics t
LEFT JOIN comments c ON c.topic_id = t.id
WHERE t.id = 9;
SELECT t.*, SUM(v.vote) as votes
FROM topics t
LEFT JOIN votes v ON v.topic_id = t.id
WHERE t.id = 9;
As you have found, combining these two into one query results in a Cartesian product. There may be clever and subtle ways to force it to give you the correct answer in one query, but what happens when you need a third statistic? It's much simpler to do it in two queries.
SELECT t.*, COUNT(c.id) as comments, COALESCE(SUM(v.vote), 0) as votes
FROM (topics t)
LEFT JOIN comments c ON c.topic_id = t.id
LEFT JOIN votes v ON v.topic_id = t.id
WHERE t.id = 9
GROUP BY t.id
or perhaps
SELECT `topics`.*,
(
SELECT COUNT(*)
FROM `comments`
WHERE `topic_id` = `topics`.`id`
) AS `num_comments`,
(
SELECT IFNULL(SUM(`vote`), 0)
FROM `votes`
WHERE `topic_id` = `topics`.`id`
) AS `vote_total`
FROM `topics`
WHERE `id` = 9
SELECT t.*, COUNT(DISTINCT c.id) as comments, COALESCE(SUM(v.vote), 0) as votes
FROM (topics t)
LEFT JOIN comments c ON c.topic_id = t.id
LEFT JOIN votes v ON v.topic_id = t.id
WHERE t.id = 9