INNER JOIN COUNT on same table - mysql

here's what I am currently trying to achieve.
I have a users table which I want to join with itself.
So I query all users with no ParentUserId and also want an additional field called ChildrenCount where I can find a count of all users with the ParentUserId = UserId.
SELECT p.*, COUNT(*) as ChildrenCount FROM users p
LEFT JOIN users q
on q.userid = p.ParentUserId
WHERE p.ParentUserId = 0 AND p.CategoryId = 2 ORDER BY p.userid DESC LIMIT 10 OFFSET 0
I am not getting any error but the query isn't working as I expected. I do not get the count of all related children.
Database is MYSQL.

If I understood you correctly,
you can use a correlated query :
SELECT p.*,
(SELECT count(*) FROM users s
WHERE s.parentUserId = p.userid) as ChildrenCount
FROM users p
WHERE p.ParentUserId = 0 AND p.CategoryId = 2
ORDER BY p.userid DESC LIMIT 10 OFFSET 0

I think you don't need join, because you do not select anything from child table:
SELECT p.*, (SELECT COUNT(*) FROM `users` q WHERE q.userid = p.ParentUserId) as ChildrenCount
FROM `users` p
WHERE p.ParentUserId = 0 AND p.CategoryId = 2
ORDER BY p.userid DESC LIMIT 10 OFFSET 0

Related

How to properly join these three tables in SQL?

I'm currently creating a small application where users can post a text which can be commented and the post can also be voted (+1 or -1).
This is my database:
Now I want to select all information of all posts with status = 1 plus two extra columns: One column containing the count of comments and one column containing the sum (I call it score) of all votes.
I currently use the following query, which correctly adds the count of the comments:
SELECT *, COUNT(comments.fk_commented_post) as comments
FROM posts
LEFT JOIN comments
ON posts.id_post = comments.fk_commented_post
AND comments.status = 1
WHERE posts.status = 1
GROUP BY posts.id_post
Then I tried to additionally add the sum of the votes, using the following query:
SELECT *, COUNT(comments.fk_commented_post) as comments, SUM(votes_posts.type) as score
FROM posts
LEFT JOIN comments
ON posts.id_post = comments.fk_commented_post
AND comments.status = 1
LEFT JOIN votes_posts
ON posts.id_post = votes_posts.fk_voted_post
WHERE posts.status = 1
GROUP BY posts.id_post
The result is no longer correct for either the votes or the comments. Somehow some of the values seem to be getting multiplied...
This is probably simpler using correlated subqueries:
select p.*,
(select count(*)
from comments c
where c.fk_commented_post = p.id_post and c.status = 1
) as num_comments,
(select sum(vp.type)
from votes_posts vp
where c.fk_voted_post = p.id_post
) as num_score
from posts p
where p.status = 1;
The problem with join is that the counts get messed up because the two other tables are not related to each tother -- so you get a Cartesian product.
You want to join comments counts and votes counts to the posts. So, aggregate to get the counts, then join.
select
p.*,
coalesce(c.cnt, 0) as comments,
coalesce(v.cnt, 0) as votes
from posts p
left join
(
select fk_commented_post as id_post, count(*) as cnt
from comments
where status = 1
group by fk_commented_post
) c on c.id_post = p.id_post
left join
(
select fk_voted_post as id_post, count(*) as cnt
from votes_posts
group by fk_voted_post
) v on v.id_post = p.id_post
where p.status = 1
order by p.id_post;

Retrieve last 10 results ASC Left Join MySql

I'm trying to retrieve the last 10 posts from a posts table ordered ASC, but the last left joined query doesn't retrieve anything.
Basic: it retrieves results ordered DESC
SELECT
p.post, p.id_post, u.name
FROM
posts p
LEFT JOIN
users u ON u.id_user = p.id_user
WHERE
p.id_user = 4
ORDER BY
p.date DESC
LIMIT 10
Ordered ASC: it doesn't work at all:
SELECT
num.*
FROM
(SELECT
p.post, p.id_post, u.name
FROM
posts p
LEFT JOIN
users u ON u.id_user = p.id_user
WHERE
p.id_user = 4
ORDER BY
p.date DESC
LIMIT 10) num
ORDER BY
p.date ASC
What am I doing wrong?
In my php variables I use $row['id_post'] $row['post'] $row['name']. I don't want to use array_reverse() just plain sql
You need to return the date in the subquery:
SELECT pu.post, pu.id_post, pu.name
FROM (SELECT p.*, u.name
FROM posts p LEFT JOIN
users u
ON u.id_user = p.id_user
WHERE p.id_user = 4
ORDER BY p.date DESC
LIMIT 10
) pu
ORDER BY pu.date ASC;
Your second query should have returned an error message to the effect that date is not recognized as a column. You should be capturing and reading error messages, if you want to write an effective application.

Error in my MYSQL query?

Trying some queries on the database, but I have a problem. Can somebody help me with proper query?
This is what I need
SELECT
IdNews, Caption, NewsText, NumberOfViews, NumberOfComments,
PublishDate, CoinValue, category.IdNewsCategory,
NewsCategory, country.IdCountry, CountryName, news.IdUser,
FirstName, LastName, Picture, DateTimeUpCoin
FROM
news
INNER JOIN
category ON category.IdNewsCategory = news.IdNewsCategory
INNER JOIN
country ON news.IdCountry = country.IdCountry
INNER JOIN
user ON user.IdUser = news.IdUser
WHERE
news.IdCountry = 1
LIMIT 0, 10
ORDER BY
NumberOfViews DESC
You have written wrong query for Order by.
In query, LIMIT comes always last.
You need to write corrected query as per below :
SELECT
IdNews,Caption,NewsText,NumberOfViews,NumberOfComments,
PublishDate,CoinValue,category.IdNewsCategory,
NewsCategory,country.IdCountry,CountryName,news.IdUser,
FirstName, LastName,Picture, DateTimeUpCoin
FROM news
INNER JOIN category ON category.IdNewsCategory = news.IdNewsCategory
INNER JOIN country ON news.IdCountry = country.IdCountry
INNER JOIN user ON user.IdUser = news.IdUser
WHERE news.IdCountry = 1
ORDER BY NumberOfViews DESC
LIMIT 0, 10
IdCountry column is present in both the tables news and country. That's why it is showing error. Prepend table name before IdCountry in where clause. Put ORDER BY before LIMIT.
SELECT IdNews,Caption,NewsText,NumberOfViews,NumberOfComments,PublishDate,CoinValue,category.IdNewsCategory, NewsCategory,country.IdCountry,CountryName,news.IdUser,FirstName, LastName,Picture, DateTimeUpCoin
FROM news
INNER JOIN category ON category.IdNewsCategory = news.IdNewsCategory
INNER JOIN country ON news.IdCountry = country.IdCountry
INNER JOIN user ON user.IdUser = news.IdUser
WHERE news.IdCountry =1
ORDER BY NumberOfViews DESC
LIMIT 0 , 10

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';

Load three posts by user

I am looking to load all posts, sorted by newest first, but also limit them to three per user. I have no idea how to do that though! Here's the SQL I have currently to create the table and select the posts.
SELECT p.title, u.firstname, u.lastname
FROM post p
JOIN user u
ON p.user_id=u.id
ORDER
BY u.id, p.ctime DESC
#LIMIT TO 3 by user
;
This is what you can do
select
u.*,
p.*
from
user u
left join
(
select
p1.*
FROM
post p1
where
(
select
count(*)
from
post p2
WHERE
p1.user_id = p2.user_id
AND p1.id <= p2.id
) <= 3
order by p1.id desc
) p ON u.id = p.user_id
order by u.id
Took help from MySQL Limit LEFT JOIN Subquery after joining
Something like this might work
SELECT u.*,up.* FROM user u LEFT JOIN
(
SELECT `p`.`title`, `u`.`firstname`, `u`.`lastname`
FROM `post` `p`
ORDER
BY `p`.`ctime` DESC
LIMIT 3
)
up on up.user_id = u.id