How do I add DESC to this query? - mysql

I'm doing a project and I'm using MYSQLI I just need to make this descend. How can I do this since the usual way on how I do this is not working.
SELECT * FROM posts
LEFT JOIN members ON posts.user_id = members.user_id
UNION
SELECT * FROM posts
RIGHT JOIN members ON posts.user_id = members.user_id
WHERE posts.user_id IS NOT NULL
This is what I tried
SELECT * FROM posts
LEFT JOIN members
ON posts.user_id = members.user_id
UNION
SELECT *
FROM posts
RIGHT JOIN members
ON posts.user_id = members.user_id
WHERE posts.user_id IS NOT NULL AND
ORDER BY posts.user_id DESC

IF you require ALL members with or without posts, then revers the table relationships so that the posts are left joined to members:
SELECT *
FROM members
INNER JOIN posts ON members.user_id = posts.user_id
ORDER BY members.user_id DESC, posts.id ASC ## I am guessing some column names
IF you have members with no posts AND posts with no members THEN you want the equivalent of a "full outer join" and this does require a UNION... although I seriously doubt the need for this here I include it for completeness:
SELECT * ## MUST choose the columns!!
FROM (
SELECT posts.*, members.* ## MUST choose the columns!!
FROM members
LEFT JOIN posts ON members.user_id = posts.user_id
UNION
SELECT posts.*, members.* ## MUST choose the columns!!
FROM posts
LEFT JOIN members ON posts.user_id = members.user_id
) d
ORDER BY user_id DESC, posts_id ASC ## I am guessing some column names
----
If you only require posts which have an associated user_id then I suggest you try this:
SELECT *
FROM posts
INNER JOIN members ON posts.user_id = members.user_id
ORDER BY members.user_id DESC, posts.id ASC ## I am guessing some column names
If you do need posts without a user_id then suggest you try this:
SELECT *
FROM posts
LEFT JOIN members ON posts.user_id = members.user_id
ORDER BY ISNULL(members.user_id) ASC, members.user_id DESC
The second part of your initial query will not add more rows to the final outcome. Consider the following test:
CREATE TABLE members
(`user_id` int);
INSERT INTO members
(`user_id`)
VALUES
(1);
CREATE TABLE posts
(`id` int, `user_id` int);
INSERT INTO posts
(`id`, `user_id`)
VALUES
(1, 1),
(2, NULL),
(3, NULL);
SELECT * FROM posts
LEFT JOIN members ON posts.user_id = members.user_id;
id | user_id | user_id
-: | ------: | ------:
1 | 1 | 1
2 | null | null
3 | null | null
SELECT * FROM posts
RIGHT JOIN members ON posts.user_id = members.user_id
WHERE posts.user_id IS NOT NULL;
id | user_id | user_id
-: | ------: | ------:
1 | 1 | 1
SELECT *
FROM posts
LEFT JOIN members ON posts.user_id = members.user_id
ORDER BY ISNULL(members.user_id) ASC, members.user_id DESC;
id | user_id | user_id
-: | ------: | ------:
1 | 1 | 1
2 | null | null
3 | null | null
dbfiddle here

Instead of using left and right join separately I would suggest you to use full outer join. And also remove the AND which is placed before ORDER.
So that your query will be like
SELECT * FROM POSTS FULL OUTER JOIN MEMBERS ON POSTS.user_id = MEMBERS.user_id
WHERE POSTS.user_id IS NOT NULL
ORDER BY POSTS.user_id DESC;

Related

MySQL where joined value is multiple ANDs

Running into a seemingly simple JOIN problems here..
I have two tables, users and courses
| users.id | users.name |
| 1 | Joe |
| 2 | Mary |
| 3 | Mark |
| courses.id | courses.name |
| 1 | History |
| 2 | Math |
| 3 | Science |
| 4 | English |
and another table that joins the two:
| users_id | courses_id |
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
I'm trying to find distinct user names who are in course 1 and course 2
It's possible a user is in other courses, too, but I only care that they're in 1 and 2 at a minimum
SELECT DISTINCT(users.name)
FROM users_courses
LEFT JOIN users ON users_courses.users_id = users.id
LEFT JOIN courses ON users_courses.courses_id = courses.id
WHERE courses.name = "History" AND courses.name = "Math"
AND courses.name NOT IN ("English")
I understand why this is returning an empty set (since no single joined row has History and Math - it only has one value per row.
How can I structure the query so that it returns "Joe" because he is in both courses?
Update - I'm hoping to avoid hard-coding the expected total count of courses for a given user, since they might be in other courses my search does not care about.
Join users to a query that returns the user ids that are in both courses:
select u.name
from users u
inner join (
select users_id
from users_courses
where courses_id in (1, 2)
group by users_id
having count(distinct courses_id) = 2
) c on c.users_id = u.id
You can omit distinct from the condition:
count(distinct courses_id) = 2
if there are no duplicates in users_courses.
See the demo.
If you want to search by course names and not ids:
select u.name
from users u
inner join (
select uc.users_id
from users_courses uc inner join courses c
on c.id = uc.courses_id
where c.name in ('History', 'Math')
group by uc.users_id
having count(distinct c.id) = 2
) c on c.users_id = u.id
See the demo.
Results:
| name |
| ---- |
| Joe |
You can use in operator and use select to generate list of potential users_id attending the second course, to find matching ones in the first course. This is many times faster than using joins.
select distinct u.users_id, users.name
from users_courses u, users
where u.users_id in (select distinct users_id from users_courses where courses_id = 2)
and u.courses_id = 1
and users.users_id = u.users_id
Almost similar to what #Nae's solution.
select u.name from users u
where exists
(select 1
from users_courses uc
where uc.course_id in (1, 2)
and uc.user_id = u.id
group by uc.user_id
having count(0) = 2);
Your code is close. Just use GROUP BY and a HAVING clause:
SELECT u.name
FROM users_courses uc JOIN
users u
ON uc.users_id = u.id JOIN
courses c
ON uc.courses_id = c.id
WHERE c.name IN ('History', 'Math')
GROUP BY u.name
HAVING COUNT(DISTINCT c.name) = 2;
Notes:
This assumes that users cannot have the same name. You might want to use GROUP BY u.id, u.name to ensure that you are counting individual users.
If users cannot take the same course multiple times, then use COUNT(*) = 2 rather than COUNT(DISTINCT).
I'd write:
SELECT MAX(u.name)
FROM users_courses uc
LEFT JOIN users u ON uc.users_id = u.id
WHERE uc.courses_id IN (1, 2)
GROUP BY uc.users_id
HAVING COUNT(0) = 2
;
For more complex conditions (for example requiring the user to be in certain classes but also not in certain classes such as "Science") this should also work:
SELECT MAX(u.name)
FROM users_courses uc
LEFT JOIN users u ON uc.users_id = u.id
GROUP BY uc.users_id
HAVING (
SUM(uc.courses_id = 1) = 1
-- user enrolled exactly once in the course 2
AND SUM(uc.courses_id = 2) = 1
-- user enrolled in course 3, 0 times
AND SUM(uc.courses_id = 3) = 0
)
;

Phpmyadmin not selecting correct vaues

I am running this query in my database:
SELECT * FROM posts INNER JOIN profile ON posts.user_id = profile.user_id
WHERE posts.user_id IN
(SELECT user_id FROM users WHERE class_name IN
(SELECT class_name FROM classroom WHERE created_by = '456'))
OR posts.user_id IN
(SELECT user_id FROM users WHERE role = 'teacher' AND school_name = 'SK Taman Megah')
ORDER BY posts.created_at DESC;
This is my classroom table:
This is my users table:
This is my post table:
This is my profile table:
This is my output:
Expected output should have a total of 4 rows with post_id of (60,57,61,56) but only 2 was shown. Is there a mistake in the SQL query?
I think following one is what you are expecting:
SELECT * FROM posts INNER JOIN profile ON posts.user_id = profile.user_id where posts.user_id IN (select distinct user_id from users left join classroom on classroom.class_name = users.class_name where classroom.created_by='456' or (users.role='teacher' and users.school_name='SK Taman Megah') )

How to exclude rows when using a LEFT JOIN (MySQL)

I have users with many posts. I want to build an SQL query that would do the following in 1 query (no subquery), and hopefully no unions if possible. I know I can do this with union but I want to learn if this can be done using only joins.
I want to get a list of distinct active users who:
have no posts
have no approved posts
Here's what I have so far:
SELECT DISTINCT u.*
FROM users u
LEFT JOIN posts p
ON p.user_id = u.id
LEFT JOIN posts p2
ON p2.user_id = u.id
WHERE u.status = 'active'
AND (p.status IS NULL
OR p2.status != 'approved');
The problem is when a user has multiple posts and one is active. This will still return the user which I do not want. If a user has an active post, he should be removed from the result set. Any ideas?
Here's what the data looks like:
mysql> select * from users;
+----+---------+
| id | status |
+----+---------+
| 1 | active |
| 2 | pending |
| 3 | pending |
| 4 | active |
| 5 | active |
+----+---------+
5 rows in set (0.00 sec)
mysql> select * from posts;
+----+---------+----------+
| id | user_id | status |
+----+---------+----------+
| 1 | 1 | approved |
| 2 | 1 | pending |
| 3 | 4 | pending |
+----+---------+----------+
3 rows in set (0.00 sec)
The answer here should be only users 4 and 5. 4 doesn't have an approved post and 5 doesn't have a post. It should not include 1, which has an approved post.
Not exists:
SELECT u.*
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM posts p
WHERE p.user_id = u.id AND p.status = 'approved');
Or equivalent LEFT JOIN
SELECT u.*
FROM users u
LEFT JOIN posts p
ON p.user_id = u.id AND p.status = 'approved'
WHERE p.user_id IS NULL;
Taking your requirements and translating them literally to SQL, I get this:
SELECT users.id,
COUNT(posts.id) as posts_count,
COUNT(approved_posts.id) as approved_posts_count
FROM users
LEFT JOIN posts ON posts.user_id = users.id
LEFT JOIN posts approved_posts
ON approved_posts.status = 'approved'
AND approved_posts.user_id = users.id
WHERE users.status = "active"
GROUP BY users.id
HAVING (posts_count = 0 OR approved_posts_count = 0);
For your test data above, this returns:
4|1|0
5|0|0
i.e. users with ids 4 and 5, the first of which has 1 post but no approved posts and the second of which has no posts.
However, it seems to me that this can be simplified since any user that has no approved posts will also have no posts, so the union of conditions is unnecessary.
In that case, the SQL is simply:
SELECT users.id,
COUNT(approved_posts.id) as approved_posts_count
FROM users
LEFT JOIN posts approved_posts
ON approved_posts.status = 'approved'
AND approved_posts.user_id = users.id
WHERE users.status = "active"
GROUP BY users.id
HAVING approved_posts_count = 0;
This also returns the same two users. Am I missing something?
Please explain why you don't want JOINs or UNIONs. If it is because of performance, then consider the following:
CREATE TABLE t ( PRIMARY KEY(user_id) )
SELECT user_id, MIN(status) AS z
FROM Posts
GROUP BY user_id;
SELECT u.id AS user,
IFNULL(z, 'no_posts') AS status
FROM users u
WHERE u.status = 'active'
LEFT JOIN t ON t.user_id = u.id
HAVING status != 'approved';
It will make only one pass over each table, thereby being reasonably efficient (considering the complexity of the query).
This one may help:
SELECT DISTINCT u.*
FROM users u
LEFT JOIN posts p ON 1=1
-- matches only if user has any post
AND p.user_id = u.id
-- matches only if user has any active post
AND p.status = 'approved'
WHERE 1=1
-- matches only active users
AND u.status = 'active'
-- matches only users with no matches on the LEFT JOIN
AND p.status IS NULL
;
I think this should be easy.
SELECT u.`id`, u.`status` FROM `users` u
LEFT OUTER JOIN `post` p ON p.`user_id` = u.`id` AND p.`status` = 'approved'
WHERE u.`status` = 'active' AND p.`id` IS NULL
Gives a result of 4 & 5.
[Edit] Just wanted to add why this works:
u.status = 'active'
This results into exclusion of all users that are not active.
p.status = 'approved'
This excludes all posts that are approved.
Hence, by using these two lines, we have excluded all users that qualify as approved for your criteria.
[Edit 2]
If you also need to know how many pending and how many approved, here is an updated version:
SELECT u.`id`, u.`status`, SUM(IF(p.`status` = 'approved', 1, 0)) AS `Approved_Posts`, SUM(IF(p.`status` = 'pending', 1, 0)) AS `Pending_Posts`
FROM `test_users` u
LEFT OUTER JOIN `test_post` p ON p.`user_id` = u.`id`
WHERE u.`status` = 'active'
GROUP BY u.`id`
HAVING SUM(IF(p.`id` IS NOT NULL, 1, 0))
Try this
SELECT DISTINCT u.*
FROM users u LEFT JOIN posts p
ON p.user_id = u.id
WHERE p.status IS NULL
OR p.status != 'approved';
Can you try with the below query:
SELECT DISTINCT u.*
FROM users u
LEFT JOIN posts p
ON p.user_id = u.id
WHERE
u.status = 'active' AND (
p.user_id IS NULL
OR p.status != 'approved');
EDIT
As per the updated question, the above query will include User 1. If we want to prevent that, and don't want to use inner query, we can use group_concat function of MySQL to get all the (distinct) statuses and see if it contains 'active' status, below query should give the desired output:
SELECT u.id, group_concat(distinct p.status) as statuses
FROM users u
LEFT JOIN posts p
ON u.id = p.user_id
WHERE
u.status = 'active'
group by u.id
having (statuses is null or statuses not like '%approved%');

SQL Query Returning Wring values for Sum and Count

My SQL Query contains three tables the posts table, post_likes table, and comments table.
All tables are connected with the post_id primary key in the posts table. I am trying to return the content of the posts row as well the amount of likes/dislikes it has in the post_likes table, and the amount of comments. The query worked fine until I introduced the second left join and it now displays the like_count column x5 dislike_count column x5 and the new comment_count x4.
This is the query in question:
SELECT c.post_id, c.post_name, c.post_content, c.post_datetime, c.user_name, sum(p.like_count) AS like_count, sum(p.dislike_count) AS dislike_count, sum(s.comment_count) AS comment_count FROM posts c LEFT JOIN post_likes p ON c.post_id = p.post_id LEFT JOIN comments s ON c.post_id = s.post_id WHERE c.user_name = 'test' GROUP BY c.post_id
is returning the sum values:
//column | returned value | expected value
like_count | 10 | 2
dislike_count | 5 | 1
comment_count | 20 | 5
Some additional notes, the likes/dislikes are stored in the postlikes table with the structure.
post_like_id, like_count, dislike_count, post_id, user_name
The like or dislike count can only be 1 in either column the PHP handles this to ensure users cant like multiple times etc and the user_name column is the user who liked the post.
The comments table structure is as follows:
comment_id, comment_name, comment_content, comment_datetime, comment_count, post_id, user_name
The comment_count is always 1 when inserted to allow for the sum function, post_id is the id of the post for the comment, and the user_name is the user who commented.
Your joins are producing a cartesian product -- instead move the aggregation results into subqueries:
SELECT c.post_id, c.post_name, c.post_content, c.post_datetime, c.user_name,
p.like_count,
p.dislike_count,
s.comment_count
FROM posts c
LEFT JOIN (
select post_id,
sum(like_count) like_count,
sum(dislike_count) dislike_count
from post_likes
group by post_id
) p ON c.post_id = p.post_id
LEFT JOIN (
select post_id, sum(comment_count) comment_count
from comments
group by post_id
) s ON c.post_id = s.post_id
WHERE c.user_name = 'test'

LEFT JOIN in MySQL across multiple tables with NULL values

I have the following table structure with data
TABLE: USER
USER ID | USER NAME
1 | Joe
2 | Mary
TABLE : USER GROUP
USER ID | GROUP ID
1 | 1
1 | 2
TABLE : GROUP
GROUP ID | GROUP NAME
1 | Company 1
2 | Company 2
TABLE : ROLE
ROLE ID | ROLE NAME
1 | Administrator
2 | Users
TABLE : USER ROLE
USER ID | ROLE ID
1 | 1
2 | 1
As you can see user #2 does not belong to any group. Roles & Groups are optional forcing me to left joint but when I run a query as below
`SELECT a.user_id,
a.user_name
GROUP_CONCAT(r.role_name) AS role_names,
GROUP_CONCAT(g.group_name) AS group_names
FROM user a
LEFT JOIN role_map m ON a.user_id = m.user_id
INNER JOIN role r ON m.role_id = r.role_id
LEFT JOIN user_group s ON a.user_id = s.user_id
INNER JOIN group g ON s.group_id = g.group_id
GROUP BY a.user_id`
I get a cartesian product in the role_names column - the result looks like this
Joe | Administrators, Administrators | Company 1, Company 2
What am I doing wrong?
The easiest way to solve this is by using DISTINCT in your GROUP_CONCAT (SQL Fiddle). Also, you will need to add GROUP BY a.user_id in order to group per user:
SELECT a.user_id,
a.user_name,
GROUP_CONCAT(DISTINCT r.role_name) AS role_names,
GROUP_CONCAT(DISTINCT g.group_name) AS group_names
FROM `user` a
LEFT JOIN `user_role` m ON a.user_id = m.user_id
LEFT JOIN `role` r ON m.role_id = r.role_id
LEFT JOIN `user_group` s ON a.user_id = s.user_id
LEFT JOIN `group` g ON s.group_id = g.group_id
GROUP BY a.user_id;