I have 3 tables Users, posts and post_approval
Now I have page where all these posts will appear from posts table but if the post visibility is set to private then the query will check in post_approval table to see if a post status is approvad for a particular user.
So the Output for User ID 2 will be all 3 posts but User 1 & 3 can see only post id 1 and 3.
Is this the right query?
SELECT * FROM posts WHERE visiblity='public' UNION (SELECT posts.* FROM POSTS RIGHT JOIN post_approval ON post_approval.post=posts.post_id WHERE post_approval.status='approved' AND post_approval.user='2')
USER ID will be enter dynamically in the above query
Simple answer
Try this SQL statement.
SELECT * FROM Posts as p, Post_approval as pa
WHERE p.post_id = pa.post OR p.visibility = 'public' AND user_id = 2
You select from Posts and Post approval, and get only the posts which match with the the post id of the user. Furthermore you get all post with visibility public.
Long answer (union)
That's the longer solution. Using a union to combine the private post and public posts for user_id = 2.
SELECT
*
FROM
(
SELECT
u.user_id,
u.username,
p.post_id,
p.post_name,
p.visibility
FROM
Users as u,
Posts as p
WHERE
p.visibility = 'public'
union ALL
SELECT
u.user_id,
u.username,
p.post_id,
p.post_name,
p.visibility
FROM
Post_approval as a,
Users as u,
Posts as p
WHERE
a.user = u.user_id and
a.post = p.post_id and
p.visibility = 'private'
) as x
WHERE
x.user_id = 2
Another small tip:
If you have a foreign key in your tables, like Post_approval.user, name it like the primary key. So in this case Post_approval.user_id
I am going to answer your question before I offer an alternative. You have some spelling errors, an extra ( and you might need to be concerned with case-sensitivity, but otherwise it should work. Your SQL with some minor corrections:
SELECT * FROM Posts WHERE visibility='public'
UNION
SELECT Posts.* FROM Posts RIGHT JOIN Post_approval ON Post_approval.post=Posts.post_id
WHERE Post_approval.status='approved' AND Post_approval.user='2';
An alternative that does not use UNION:
select p.* from Posts p
left join Post_approval p_a on p.post_id = p_a.post and p_a.user = 2
where p.visibility = 'public' or p_a.status = 'approved';
See Db-Fiddle Demo
Another Way:
SELECT * FROM Posts
where visibility = 'Public' or post_id in (
select post from Post_approval where user = 2 and status = 'approved'
)
Related
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;
I'm building a query that should return the last 10 posts (default ordering) by $userId and the users it is following.
SQL Fiddle with a minimal example of the query: https://www.db-fiddle.com/f/i5ByFAXwADj5pfjCTn1g1m/2
The database structure is pretty simple:
posts (id, root, user, content)
users (id, username)
following (user_id, followed)
This is the query I'm currently using to get all posts:
SELECT posts.id, posts.content, users.id AS user_id, users.username
FROM posts
LEFT JOIN users ON posts.user = users.id
WHERE LIMIT 10
The query is working but it is listing posts from every user without distinction.
This is the query I built to exclude posts from users that $userId is not following, but it doesn't include $userId's own posts:
SELECT posts.id, posts.content, users.id AS user_id, users.username
FROM following
LEFT JOIN posts ON posts.user = '$userId' OR posts.user = following.followed
LEFT JOIN users ON posts.user = users.id
WHERE (following.user_id = '$userId' OR following.user_id = NULL) LIMIT 10
I've tried replacing the LEFT JOIN posts with an INNER JOIN and a RIGHT JOIN with no success whatsoever. I'm not able to find the error, why isn't the query including posts made by $userId?
I have also tried selecting from posts and joining followers, but it is returning duplicated content:
SELECT posts.id, posts.content, users.id AS user_id, users.username
FROM posts
LEFT JOIN following ON following.user_id = '$userId'
LEFT JOIN users ON posts.user = users.id
WHERE (posts.user = '$userId' OR posts.user = following.followed)
LIMIT 10;
I was about to post a UNION solution
SELECT
post_id,
content,
user_id,
username
FROM
(SELECT
posts.id post_id,
content,
users.id user_id,
username
FROM
posts INNER JOIN
users
ON user = users.id
UNION SELECT
posts.id,
content,
users.id,
username
FROM
posts INNER JOIN (
following INNER JOIN
users
ON user_id = users.id
) ON user = followed
) p
WHERE
user_id = 1
LIMIT 10;
Then I saw #Gordon Linoff's solution which might be better - more concise, at least - but I don't think it works as posted.
SELECT
posts.id,
content,
users.id,
username
FROM
posts INNER JOIN
users
ON user = users.id
WHERE
users.id = 1
OR EXISTS (
SELECT
*
FROM
following
WHERE
followed = user
AND user_id = 1
)
LIMIT 10;
Get the posts from the table posts under your conditions and join to users:
select p.id, p.content, u.id AS user_id, u.username
from (
select *
from posts
where user = '$user_id'
or user in (select user_id from following where followed = '$user_id')
) p inner join users u on u.id = p.user
order by p.id desc limit 10
Note that as it is your requirement the results may not contain posts by the user '$user_id' if the last 10 posts are from the users that this user follows.
See the demo.
I'm building a query that should return the last 10 posts by $userId and the users it is following.
So, there are two tasks here:
Get first N records per group
Apply query to given user PLUS the same for the related users
I would do something like this (pseudo code):
ids = query('SELECT user_id FROM following WHERE followed = :id)', userId).pluck('user_id');
ids.push(userId);
SELECT x.id, x.user_id, x.content
FROM (
SELECT #num := IF(#id = user_id, #num + 1, 1) AS num,
#id := posts.user_id as x_user_id,
posts.*
FROM
(SELECT #num := null, #id := null) x,
posts
WHERE posts.user_id IN(:ids)
ORDER BY posts.id DESC
) AS x
WHERE x.num <= 10
(https://www.db-fiddle.com/f/aiBUwqDApJp6foyq13ZZ2u/1)
See:
S.O.: get first N records per group
S.O.: prepared statement with WHERE IN
If I understand correctly, you basically want an EXISTS clause:
SELECT p.id, p.content, u.id AS user_id, u.username
FROM posts p JOIN
users u
ON p.user = u.id
WHERE u.id <> ? -- ? is the userid AND
EXISTS (SELECT 1
FROM following f
WHERE f.followed = ? AND
f.user_id = u.id
)
LIMIT 10;
I have 3 tables like so
Table 1: UserInfo
user_id userName
123 userOne
Table 2: Post
user_id postContent
123 This is test message
Table 3: LikePost
user_id likesPostId
123 This is test message
I would like to run a query to get total number of post likes, posts, and user information from those 3 tables.
I can do this for each one such as in Post table:
SELECT COUNT(*) FROM Post WHERE Post.user_id = '123'
and SELECT * FROM UserInfo WHERE UserInfo.user_id = '123'
Is anyone have better solution in just 1 query? Thank you so much!
Use a structured query (with subqueries) something like this.
SELECT u.user_id, u.userName, p.num postcount, l.num likecount
FROM UserInfo u
LEFT JOIN (
SELECT COUNT(*) num,
user_id
FROM Post
GROUP BY user_id
) p ON u.user_id = p.user_id
LEFT JOIN (
SELECT COUNT(*) num,
user_id
FROM LikePost
GROUP BY user_id
) l ON u.user_id = l.user_id
What's going on here? The two subqueries, for example
SELECT COUNT(*) num,
user_id
FROM LikePost
GROUP BY user_id
each generate a virtual table with either zero or one row per user_id, showing a count for each user_id. You then join those virtual tables to your UserInfo table.
Use LEFT JOIN because ordinary innner JOIN will suppress users that lack either posts or likes.
Try This
SELECT ui.userName,Count(p.*),
Count(lp.*) as TotalPostLikes
FROM UserInfo ui
INNER JOIN Post p on p.user_id=ui.user_id
INNER JOIN LikePost lp on lp.user_id=ui.user_id
WHERE ui.user_id = '123'
GROUP BY ui.userName
If you want to select Username, Post and Likes on post, try the following
SELECT ui.userName,p.postContent as PostContent,
(SELECT COUNT(lp.user_id) FROM LikePost lp
WHERE lp.user_id=ui.user_id) as Likes,
(SELECT COUNT(_p .user_id) FROM Post _p
WHERE _p .user_id=ui.user_id) as TotalPosts
FROM UserInfo ui
INNER JOIN Post p on p.user_id=ui.user_id
WHERE ui.user_id = '123'
Yes you can do it within one query using leftjoin on Post and LikePost like below
SELECT COUNT(*),User.userName FROM UserInfo as User
leftjoin Post as Post on Post.user_id = User.user_id
leftjoin LikePost as LikePost on LikePost.user_id = User.user_id
where Post.user_id = 123
group by Post.user_id
SELECT *
FROM post p
JOIN user u ON p.user_id = u.id
JOIN friendships f ON f.friend_id = u.id
WHERE f.user_id = 1 OR u.id = 1
ORDER BY p.created_at DESC;
working on a projects where I'm trying to get all the post of the user as well as the user currently on.
So far i have this query working but is giving me duplicate posts of users.id = 1
is a user self join many to many where each user become friends and each user has their posts
The problem is probably because you select all columns from all tables involved.
Now for every new friendship with friend_id=1 you will receive a new record, with post duplicated. I guess you need:
select distinct p.*
The following query selects all posts and each post's owner, all of the comments that belong to each post, and the owner of each comment.
I need to only retrieve 5 comments per post. I rewrote the query, but I get an error of "each derived table must have it's own alias".
SELECT posts.id AS postId, posts.body, users.id AS userId, users.displayname, comments.id AS commentId, comments.text, commenters.id, commenters.displayname
FROM posts
JOIN users ON posts.owneruserid = users.id
LEFT JOIN comments ON posts.id = comments.postid
JOIN users AS commenters ON comments.userId = commenters.id
ORDER BY posts.createdAt
New Query:
SELECT posts.id AS postId, posts.body, users.id AS userId, users.displayname
FROM posts
JOIN users ON posts.owneruserid = users.id
LEFT JOIN (
SELECT comments.id AS commentId, comments.text AS commentText, commenters.id AS commenterId, commenters.displayname AS commenterDisplayName
FROM comments
JOIN users AS commenters ON comments.userid = commenters.id
LIMIT 0,5
) AS comments ON comments.postid = posts.id
ORDER BY posts.createdAt
UPDATE The query now works, but it does not produce the desired output. I want to output 10 posts, with 5 comments for each post. This limit clause will only apply for the comments of the first post encountered.
From the edits and comment feedback, here's the query I think you are looking for... The inner most will prequery gets the posts and who initiated the post, comments and who posted the comments. This inner query is also pre-sorted with the MOST RECENT COMMENTS to the top per postID. Using the result of that, I'm joining to the sql variables (#variables) to get the #varRow increased every time a new comment and reset back to 1 each time a post ID changes (hence the inner PreQuery orders by post ID FIRST). Finally, using the HAVING clause to have the comment's #varRow count < 6 will get at MOST 5 of each post.
If you want to limit what posts you are trying to retrieve, I would apply a WHERE clause (such as date/time if available) at the INNER most that generates the "PreQuery".
select straight_join
PreQuery.*,
#varRow := if( #LastPost = PreQuery.PostID, #varRow +1, 1 ) CommentRow,
#LastPost := PreQuery.PostID PostID2
from
( select
posts.id PostID,
posts.body,
posts.CreatedAt,
u1.id UserID,
u1.DisplayName NameOfPoster,
c.id,
c.userid CommentUserID,
c.text CommentText,
u2.DisplayName CommentUserName
from
posts
join users u1
on posts.ownerUserID = u1.id
LEFT JOIN comments c
on posts.id = c.PostID
join users u2
on c.userid = u2.id
where
posts.id = TheOneParentIDYouWant
OR posts.parentid = TheOneParentIDYouWant
order by
posts.ID,
c.id desc ) PreQuery,
(select #varRow := 0, #LastPost = 0 ) SQLVars
having
CommentRow < 6
order by
PreQuery.postid,
CommentRow
--- EDIT --- per comment
I THINK what you mean by which "Parent Post" the comments are associated with is because they have the post ID directly. Since the inner-most query does a join of all elements / tables across the board, all are coming along for the ride...
Post -> User (to get posting user name )
Post -> Comment (on Common Post ID -- left joined)
Comment -> User ( to get commenting user name)
Once THAT is all done and sorted by common Post ID and most recent comment sorted to the top, I then apply the #vars against ALL returned rows. The HAVING clause will strip out any comment where it's sequence is BEYOND the 5 you were looking for.
You need to give your derived table an alias:
SELECT posts.id AS postId, posts.body, users.id AS userId, users.displayname
FROM posts
JOIN users ON posts.owneruserid = users.id
LEFT JOIN (
SELECT comments.id AS commentId, comments.text AS commentText, commenters.id AS commenterId, commenters.displayname AS commenterDisplayName
FROM comments
JOIN users AS commenters ON comments.userid = commenters.id
LIMIT 0,5
) AS derived_table_alias
ORDER BY posts.createdAt
Since you're using a subquery (which is what it means by "derived table"), it must indeed have an alias. Thus, all you need to do is:
SELECT posts.id AS postId, posts.body, users.id AS userId, users.displayname
FROM posts
JOIN users ON posts.owneruserid = users.id
LEFT JOIN (
SELECT comments.id AS commentId, comments.text AS commentText, commenters.id AS commenterId, commenters.displayname AS commenterDisplayName
FROM comments
JOIN users AS commenters ON comments.userid = commenters.id
LIMIT 0,5
) as some_alias --This is what's triggering the error
ORDER BY posts.createdAt
Even if you're not selecting from the subquery, and just using it as a filter, you have to alias it.
By Error
Add an alias following your subquery.
Example: SELECT * FROM foo JOIN (select * from bar) AS <alias_here>
Make sure you have a field in the posts table and that it is called createdAt. I'm not sure MySQL is case-sensitive, but the error you posted says createdat (with the 'A' lowercased)
You have two LEFT JOINs, but only one ON statement. A join isn't anything without the hook to join it on. Example:
SELECT *
FROM foo JOIN bar ON (foo.id=bar.id)
LEFT JOIN (select * from foobar) AS baz **ON foo.id=baz.id**
In order to join on a field, the field needs to be present in the table involved in the join. So in the above example, if you match foo.id with baz.id, id needs to be returned in the subquery (baz).