using a limit on one table when joining two tables in mysql - mysql

How do I correctly write an sql statement using a limit and join in this case?
SELECT u.userName, u.userFriends, u.userJoined, f.freindName (only 3)
FROM user u inner join friends f ON u.userId = f.addedUserId
WHERE u.userId=1
AND f.userId=1
I tried
SELECT u.userName, u.userFriends, u.userJoined, group_concat(f.freindName)
FROM user u inner join friends f ON u.userId = f.userId
WHERE u.userId = 1 and f.addedUserId = 1
GROUP by u.userName limit 5 /* but this does not work */
Can you help with the limiting the f.freindName to 3
'gid' | 'userName' | 'userId' | 'userFriends' |'userJoined'
-------|-------------|----------|-------------------|--------------
'1' | 'Jason' | '1' | '5' |'14-Aug-2014'
'gid' | 'friendName'| 'thisUserId' |'addedUserId'
-------|-------------|------------------|-----------------
'1' | 'James' | '2' |'1'
'2' | 'Lars' | '3' |'1'
'3' | 'Kirk' | '4' |'1'
'4' | 'Rob' | '5' |'1'
'5' | 'Dave' | '5' |'1'

I'm assuming that the tables users and friends are joined by the columns userId and addedUserId. And I assume further that you want a list of three friends of the user with the userId = 1.
You can use a derived table by using a subselect to get the desired result:
SELECT u.userName, u.userFriends, u.userJoined, group_concat(f.friendName)
FROM user u
INNER JOIN (
SELECT
userId,
friendName
FROM
friends
LIMIT 3
) f
ON u.userId = f.addedUserId
WHERE u.userId = 1
GROUP by u.userName
should work.

This is the query you are looking for. I think there is some problems with the field names (for instance f.freindName instead of f.friendname) but fixing that, This should work
SELECT u.userName, u.userFriends, u.userJoined, f.freindName
FROM user u inner join friends f ON u.userId = f.userId
(SELECT DISTINCT f.freindName
FROM user u2 inner join friends f2 ON u2.userId = f2.userId
WHERE u2.userId = u.userId
LIMIT 3) as f
WHERE u.userId=1

Related

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

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;

MySQL Remove repeating clauses in subqueries

I'm trying to simplify my query so that it only contains the session ID (SID) once.
The abstract structure of the Users table is:
+----+------+----------+
| ID | Name | Username |
+----+------+----------+
The Friends table has an abstract structure like:
+----+-----------------+----------+--------+---------+
| ID | UserID | FriendID | Hidden | Deleted |
| | (Foreign key | | | |
| | of ID in Users) | | | |
+----+-----------------+----------+--------+---------+
The abstract structure of the Sessions table:
+----+-----------------+-----+
| ID | UserID | SID |
| | (Foreign key | |
| | of ID in Users) | |
+----+-----------------+-----+
I have the following query, which has been adapted from the answer of a previous question of mine. As you can see, the session ID (SID) is repeated 4 times, is it possible to condense the query as a whole so that the SID is only required once?
SELECT *
,CASE
WHEN D.ID IS NULL
THEN "Wants to be your friend"
ELSE "Friends"
END AS STATUS
FROM (
SELECT DISTINCT A.ID
,A.NAME
,E.Hidden
FROM Users A
INNER JOIN Friends E ON A.ID = E.UserID
WHERE A.ID IN (
SELECT A.UserID
FROM Friends A
INNER JOIN Sessions S ON A.FriendID = S.UserID
WHERE S.SID = "1234"
AND Deleted = 'No'
)
) C
LEFT JOIN (
SELECT DISTINCT B.ID
,B.NAME
,F.Hidden
FROM Users B
INNER JOIN Friends F ON B.ID = F.FriendID
WHERE B.ID IN (
SELECT A.FriendID
FROM Friends A
INNER JOIN Sessions S ON A.UserID = S.UserID
WHERE S.SID = "1234"
AND Deleted = 'No'
)
) D ON C.ID = D.ID
UNION
DISTINCT
SELECT *
,CASE
WHEN C.ID IS NULL
THEN "Request Sent"
ELSE "Friends"
END AS STATUS
FROM (
SELECT DISTINCT A.ID
,A.NAME
,E.Hidden
FROM Users A
INNER JOIN Friends E ON A.ID = E.UserID
WHERE A.ID IN (
SELECT A.UserID
FROM Friends A
INNER JOIN Sessions S ON A.FriendID = S.UserID
WHERE S.SID = "1234"
AND Deleted = 'No'
)
) C
RIGHT JOIN (
SELECT DISTINCT B.ID
,B.NAME
,F.Hidden
FROM Users B
INNER JOIN Friends F ON B.ID = F.FriendID
WHERE B.ID IN (
SELECT A.FriendID
FROM Friends A
INNER JOIN Sessions S ON A.UserID = S.UserID
WHERE S.SID = "1234"
AND Deleted = 'No'
)
) D ON C.ID = D.ID
A basic way of explaining the system is that if two users are friends, then there is two records within the database. One from the first user to the second and another record from the second user to the first.
A friend request has been sent if there is a record from the current user to another, and a friend request has been received if there is a record from one user to the current one.
Here is a vann diagram of how it works:
SQL Fiddle - http://sqlfiddle.com/#!2/c5587/1
Sql fiddle : http://sqlfiddle.com/#!2/06e08/68/0
This return Friends and Request Sent :
SELECT
f.FriendID,
u.Name,
f.Hidden,
CASE
WHEN reqs.FriendID IS NULL
THEN "Request Sent"
WHEN reqs.FriendID = f.UserID
THEN "Friends"
END AS Status
FROM
Friends AS f
INNER JOIN
Sessions AS s
ON f.UserId = s.UserID
INNER JOIN
Users AS u
ON u.ID = f.FriendID
LEFT JOIN
Friends AS reqs
ON reqs.FriendID = f.UserID
AND reqs.UserID = f.FriendID
WHERE
s.SID = "sid1"
If you want Also Request Received, append this :
UNION
SELECT
f.UserID,
u.Name,
f.Hidden,
"Request Received" AS Status
FROM
Friends AS f
INNER JOIN
Sessions AS s
ON f.FriendID = s.UserID
INNER JOIN
Users AS u
ON u.ID = f.UserID
WHERE
f.UserID NOT IN
(
SELECT
ff.FriendID
FROM
Friends AS ff
INNER JOIN
Sessions AS ss
ON ff.UserID = ss.UserID
WHERE ss.SID = "sid1"
)
AND s.SID = "sid1"
Can't figure out how to optimise the last part. Since it's a SELF JOIN it's a damn mind twister.
I understand this is not what you expected but, i can't get ride of all the SID, but this request should be faster than the one you currently use

How to mention two aggregation functions in the same query?

I have an sql query to get information from three different tables as following:
select users.username, users.id, users.avatar, users.daily_tahmin, users.alert, f1.comments_no, f2.tahmins_no, f3.monthly_tahmins_no from users LEFT join
(SELECT count(comments) AS comments_no, user_id
FROM comments
Where user_id = 12
) AS f1 on users.id = f1.user_id left join
(
SELECT count(tahmin) AS tahmins_no, user_id
FROM tahminler
Where user_id = 12
) AS f2 on users.id = f2.user_id left join
(
SELECT count(tahmin) AS monthly_tahmins_no, user_id, matches_of_comments.match_id
FROM tahminler
INNER JOIN matches_of_comments on tahminler.match_id = matches_of_comments.match_id
Where user_id = 12 AND (MONTH( STR_TO_DATE( matches_of_comments.match_date, '%d.%m.%Y' ) ) = '01' AND YEAR( STR_TO_DATE( matches_of_comments.match_date, '%d.%m.%Y' ) ) = '2014')
) AS f3 on users.id = f3.user_id
where users.id = 12
and it gives the following result :
+------------+----+----------------+--------------+-------+-------------+------------+--------------------+
| username | id | avatar | daily_tahmin | alert | comments_no | tahmins_no | monthly_tahmins_no |
+------------+----+----------------+--------------+-------+-------------+------------+--------------------+
| cold heart | 12 | 1389002263.jpg | 0 | 0 | 65 | 258 | 10 |
+------------+----+----------------+--------------+-------+-------------+------------+--------------------+
The previous code was not optimized after i do some EXPLIAN and I tried to optimized it and I got the following query:
SELECT m.*,count(comments.id)
FROM comments
JOIN
(SELECT users.username, users.id, users.avatar, users.daily_tahmin, users.alert
FROM users
WHERE id=12)as m ON m.id = comments.user_id
My problem is that I can not get (tahmins_no,monthly_tahmins_no) every time i add them to the query it gives wrong result I can not find a way to add them correctly to the query to be in optimized way?? can I have any advice from anybody here?
Your simplified query is:
select m.*, count(c.id)
from comments c join
users m
on m.id = c.user_id
where m.id = 12
group by m.id;
You should be able to add in the monthly number:
select m.*, count(c.id), f3.*
from comments c join
users m
on m.id = c.user_id join
(select count(tahmin) AS monthly_tahmins_no, user_id, moc.match_id
from tahminler t join
matches_of_comments moc
on t.match_id = moc.match_id
Where user_id = 12 AND
MONTH( STR_TO_DATE( moc.match_date, '%d.%m.%Y' ) ) = 1 AND
YEAR( STR_TO_DATE( moc.match_date, '%d.%m.%Y' ) ) = 2014
) f3
on f3.user_id = m.id
where m.id = 12
group by m.id;
The month() and year() function return numbers, not strings. I don't understand why the field match_date would be stored as a string -- seems like a silly choice for a column whose name contains date.

SQL INNER JOIN and COUNT

I have 3 tables (user, item, userlike) and 2 sql queries. How can I unify these two queries?
SELECT item.userid, item.id, user.name FROM item
INNER JOIN user ON item.userid = user.id
SELECT userid,itemid, COUNT(*) AS `liked` FROM userlike
WHERE userid=9
GROUP BY itemid
I want to know whether a specific user (9) has liked the item or not.
Result should be somthing like this
itemid userid name liked* (*whether 'user 9' liked this item or not)
1 7 foo 0
2 4 asd 1
Thanks
You want to use an OUTER JOIN for this
SELECT i.id itemid, u.id userid, u.name, COALESCE(liked, 0) liked
FROM item i JOIN user u
ON i.userid = u.id LEFT JOIN
(
SELECT itemid, COUNT(*) liked
FROM userlike
WHERE userid = 9
GROUP BY itemid
) l
ON i.id = l.itemid;
or
SELECT i.id itemid, u.id userid, u.name, l.userid IS NOT NULL liked
FROM item i JOIN user u
ON i.userid = u.id LEFT JOIN userlike l
ON i.id = l.itemid
AND l.userid = 9;
Sample output:
| ITEMID | USERID | NAME | LIKED |
|--------|--------|-------|-------|
| 2 | 4 | user4 | 1 |
| 1 | 7 | user7 | 0 |
Here is SQLFiddle demo
SELECT item.id, item.userid, user.name, userlike.liked
FROM item
JOIN user ON user.id = item.userid
JOIN userlike ON item.id = userlike.itemid
WHERE userlike.liked = 1
GROUP BY item.id
OR
SELECT item.id, item.userid, user.name, userlike.liked
FROM item
JOIN user ON user.id = item.userid
JOIN userlike ON item.id = userlike.itemid
WHERE COUNT(userlike.liked) >= 1
GROUP BY item.id
You don't need to use COUNT. You just need to know if there is an entry for a specific item and a specific user in the userlike table :
SELECT i.id as itemid, u.id as userid, u.name,
case when ul.userid is null then 0 else 1 end as liked
FROM item i
INNER JOIN user u ON i.userid = u.id
LEFT OUTER JOIN userlike ul ON i.id = ul.itemid AND ul.userid=9
ORDER BY i.id