So I have the following table, users:
+-----------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+-------+
| uid | int(10) unsigned | NO | PRI | 0 | |
| name | varchar(60) | NO | UNI | | |
---------------------------------------------------------------
And the table, roles:
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| rid | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(64) | NO | UNI | | |
+-------+------------------+------+-----+---------+----------------+
I have a third table, users_roles, which in a one-to-many relationship maps users to one or more roles:
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| uid | int(10) unsigned | NO | PRI | 0 | |
| rid | int(10) unsigned | NO | PRI | 0 | |
+-------+------------------+------+-----+---------+-------+
Finally, I have a fourth table, memberships, that maps one-to-one with users:
+-------------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+---------------+------+-----+---------+-------+
| membership_no | varchar(32) | NO | PRI | NULL | |
| uid | int(11) | YES | | NULL | |
--------------------------------------------------------------------
Currently I have a nice and simple query that provides a list of users with their membership number:
SELECT u.uid, u.name, m.membership_no FROM users AS u
LEFT JOIN memberships AS m ON u.uid = m.uid;
But now I would like to add an additional column to my query that looks up all the users_roles entries once per user, and then concatenates each roles.name in one column, so I can see all the roles for that user. An example output might be:
uid name membership no roles
--- ---- ----- ------
1 foo 123432 admin, normal user, student
2 bar baz 235235 admin
3 bak 2352352 normal user, student
So my task is how I would integrate this additional query on the users_roles table, link user_roles.rid to roles.rid and concatenate the data in a column. Any pointers?
You're looking for GROUP_CONCAT. First JOIN the other tables, so you can get the role names, then GROUP them together by user id.
SELECT u.uid, u.name, m.membership_no, GROUP_CONCAT(r.name ORDER BY r.rid) AS roles
FROM users AS u
LEFT JOIN memberships AS m ON u.uid = m.uid
JOIN users_roles AS ur ON u.uid = ur.uid
JOIN roles AS r ON ur.rid = r.rid
GROUP BY u.uid
DEMO: http://sqlfiddle.com/#!2/ffa31/1
Use MySQL GROUP_CONCAT function.
SELECT
u.uid,
u.name,
m.membership_no,
(SELECT GROUP_CONCAT(name) FROM users_roles ur INNER JOIN roles r ON r.rid = ur.rid WHERE ur.uid = u.uid) as roles
FROM users AS u
LEFT JOIN memberships AS m ON u.uid = m.uid;
Related
I have two MySql table user and marks
User table:
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| firstName | varchar(255) | YES | | NULL | |
| lastName | varchar(255) | YES | | NULL | |
| email | varchar(55) | YES | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
Marks Table
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| user_id | int | NO | | NULL | |
| subject_id | varchar(255) | YES | | NULL | |
| score | int | YES | | NULL | |
| subject_name | varchar(225) | YES | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
I want to fetch details(userid, firstName and lastName) of top 10 users with the highest marks in descending order.
Marks of the user is defined as sum of all scores a user has in different subjects.
I am really confused which join is to be used here, I am new to MySql and this query is kind of challenging for me, Hope you understood the problem.
Please let me know if you have any suggestion, Thank You
You can join the two tables together by id=user_id, then you group the result by id, sort by the total marks per id, then take the top 10 results.
If you wanted a result even if the user had no marks at all, change JOIN to LEFT JOIN, this will still give you a result from the first table even if there are no results from the second.
SELECT u.id, u.firstName, u.lastName, SUM(m.score) AS TotalScore
FROM [User] AS u
JOIN Marks AS m ON m.user_id = u.id
GROUP BY u.id, u.firstName, u.lastName
ORDER BY SUM(m.score) DESC
LIMIT 10;
You should inner join the table Marks on the id from table User with user_id from the table Marks:
SELECT user_id, firstName, lastName
FROM User
INNER JOIN Marks
ON User.id = Marks.user_id
Here is a helpful resource for SQL joins
I'd like to update followers in profile table by counting the followed_id on follow table.
mysql> explain follow;
+-------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | |
| followed_id | int(11) | NO | | NULL | |
| follower_id | int(11) | NO | | NULL | |
+-------------+---------+------+-----+---------+-------+
And
mysql> explain profile;
+----------------+---------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(10) | NO | MUL | 0 | |
| followers | int(7) | NO | | 0 | |
| following | int(7) | NO | | 0 | |
+----------------+---------------+------+-----+-------------------+----------------+
Here is the query that I came up with:
UPDATE profile A
INNER JOIN (SELECT id,COUNT(*) idcount FROM follow GROUP BY id) as B
ON B.id = A.user_id
SET A.followers = B.idcount
But the query does not work as it should. It adds only 1 when profile has followers.
How can I fix this?
You are currently counting the number of rows for each id value in follow, which is always going to be 1. What you need to do is count the number of follower_id values for each followed_id. Also, as #juergend pointed out, you should use a LEFT JOIN so that you can get 0 values for users with no followers. Change your query to this:
UPDATE profile A
LEFT JOIN (SELECT followed_id, COUNT(DISTINCT follower_id) AS idcount
FROM follow
GROUP BY followed_id) as B ON B.followed_id = A.user_id
SET A.followers = COALESCE(B.idcount, 0)
You can use a similar query to update following:
UPDATE profile A
LEFT JOIN (SELECT follower_id, COUNT(DISTINCT followed_id) AS idcount
FROM follow
GROUP BY follower_id) as B ON B.follower_id = A.user_id
SET A.following = COALESCE(B.idcount, 0)
I cant seem to figure out how to write these 2 queries on the tables that I have created. The two queries that I am trying to write are
Find users that have reviewed both shops and restaurants.
Find users that reviewed businesses, but not shops or restaurants.
The tables that I am using are
reviews;
+-------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------+------+-----+---------+-------+
| business_id | int(11) | NO | PRI | NULL | |
| user_id | int(11) | NO | PRI | NULL | |
| review_id | int(11) | NO | PRI | NULL | |
| review_date | date | YES | | NULL | |
| star_rating | int(1) | YES | | 1 |
businesses
+--------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| business_id | int(11) | NO | PRI | NULL | |
| name | varchar(50) | YES | | NULL | |
| city | varchar(40) | YES | | NULL | |
| state | varchar(20) | YES | | NULL | |
| full_address | varchar(120) | YES | | NULL | |
users;
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| user_id | int(11) | NO | PRI | NULL | |
| name | varchar(50) | YES | | NULL | |
| user_since | date | YES | | NULL
explain is_a_restaurant;
+--------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+-------+
| business_id | int(11) | NO | PRI | NULL | |
| cuisine_type | varchar(20) | YES | | NULL | |
| total_seats | int(11) | YES | | 1 | |
+--------------+-------------+------+-----+---------+-------+
explain is_a_shop;
+-------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+-------+
| business_id | int(11) | NO | PRI | NULL | |
| shop_type | varchar(50) | YES | | NULL | |
I honestly dont know where to even start. I know I am going to join the businesses with the business ID but I dont know how I am going to find people who reviewed both shops and restaurants. can anyone help?
EDIT: WHat I have tried
For the first query: Find users that have reviewed both shops and restaurants.
SELECT b.business_id
FROM is_a_shop b
JOIN reviews r
ON r.business_id = b.business_id
JOIN is_a_restaurant k
ON r.business_id = k.business_id;
Here's what the first one should look like: you can change the select statement based on what info you want to see.
SELECT u.name
FROM users u
INNER JOIN reviews rev ON rev.user_id = u.user_ID
WHERE EXISTS
(SELECT *
FROM is_a_shop s
WHERE s.business_id = rev.business_id)
OR EXISTS
(SELECT *
FROM is_a_restaurant r
WHERE r.business_id = rev.business_id)
This basically pulls all the users who wrote reviews where the business_id is found in the is_a_shop table or is_a_restaurant. you should be able to figure out the second query easily from this.
Like most things SQL these queries can be solved in a number of different ways. These solutions should be pretty easy to understand:
Find users that have reviewed both shops and restaurants.
-- solution 1 - using joins:
select u.name
from users u
join (
select r.user_id
from reviews r
join is_a_restaurant i on i.business_id = r.business_id
) resturant_reviews on u.user_id = resturant_reviews.user_id
join (
select r.user_id
from reviews r
join is_a_shop i on i.business_id = r.business_id
) shop_reviews on u.user_id = shop_reviews.user_id
-- solution 2: using exists - probably faster than solution 1
select u.name
from users u
join reviews r on u.user_id = r.user_id
where exists (
select 1 from is_a_restaurant i
where i.business_id = r.business_id
) and exists (
select 1 from is_a_shop i
where i.business_id = r.business_id
)
Find users that reviewed businesses, but not shops or restaurants.
-- solution 1: using not in - probably faster than solution 2
select u.name from users u
join reviews r on u.user_id = r.user_id
where r.business_id not in
(
select business_id from is_a_restaurant
union all
select business_id from is_a_shop
)
-- solution 2: using exists
select u.name from users u
join reviews r on u.user_id = r.user_id
where not exists (
select business_id from is_a_restaurant
where r.business_id = business_id
)
and not exists (
select business_id from is_a_shop
where r.business_id = business_id
)
Is it possible to convert the following subquery to a JOIN, preferably without a derived table?
SELECT * FROM users u
LEFT JOIN user_groups ug ON u.usergroupid=ug.groupid
WHERE
u.userstatus=1 AND
ug.groupstatus=1 AND
ug.grouprank>=(SELECT grouprank FROM user_groups WHERE groupkey='users')
The user_groups table looks like:
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| groupid | tinyint(3) unsigned | NO | PRI | NULL | auto_increment |
| groupkey | varchar(8) | NO | | NULL | |
| grouprank | smallint(6) | NO | | NULL | |
| groupstatus | tinyint(1) unsigned | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
The users table looks like:
+--------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------------+------+-----+---------+----------------+
| userid | int(10) unsigned | NO | PRI | NULL | auto_increment |
| username | varchar(32) | NO | | NULL | |
| userpassword | varchar(32) | NO | | NULL | |
| usergroupid | tinyint(3) unsigned | NO | | NULL | |
| userstatus | tinyint(1) unsigned | NO | | NULL | |
+--------------+---------------------+------+-----+---------+----------------+
Assuming that exactly one user_groups record has groupkey = 'users' (since otherwise your query is invalid), your query is equivalent to this:
SELECT u.*,
ug1.*
FROM users u
LEFT
JOIN user_groups ug1
ON u.usergroupid = ug1.groupid
LEFT
JOIN user_groups ug2
ON ug2.groupkey = 'users' -- not a real join condition
WHERE u.userstatus = 1
AND ug1.groupstatus = 1
AND ug1.grouprank >= ug2.grouprank
;
But note that the LEFT JOINs actually end up working as INNER JOINs, since your WHERE clause depends on the joins having succeeded. So you probably really want something like this:
SELECT u.*,
ug1.*
FROM users u
LEFT
JOIN user_groups ug1
ON u.usergroupid = ug1.groupid
AND ug1.groupstatus = 1
LEFT
JOIN user_groups ug2
ON ug2.groupkey = 'users'
AND ug1.grouprank >= ug2.grouprank
WHERE u.userstatus = 1
;
I think this is what you're looking for -- give it a test.
SELECT * FROM users u
LEFT JOIN user_groups ug ON u.usergroupid=ug.groupid
JOIN user_groups ug2 ON ug.grouprank >= ug2.grouprank AND ug2.groupkey = 'users'
WHERE
u.userstatus=1 AND
ug.groupstatus=1
mysql> describe posts;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | | NULL | |
| title | varchar(255) | NO | | NULL | |
| body | text | YES | | NULL | |
| created | datetime | YES | | NULL | |
| modified | datetime | YES | | NULL | |
| category_id | int(11) | NO | | NULL | |
| tags | varchar(50) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)
mysql> describe users;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | UNI | NULL | |
| password | char(40) | NO | | NULL | |
| group_id | int(11) | NO | | NULL | |
| created | datetime | YES | | NULL | |
| modified | datetime | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
I'm trying to select column id,title from posts table and username from users table where posts.user_id = users.id.
If i select user_id from posts i will get only the integer value.
But i want to get the equivalent varchar value from users table.
It would be a joining sql query.
How can i do this?
Thanks in advance.
this should do the trick
SELECT p.id, p.title, u.UserName
FROM posts p
INNER JOIN users u ON p.user_id = u.id
or
SELECT p.id, p.title, u.UserName
FROM posts p,users u
WHERE p.user_id = u.id
or
SELECT p.id, p.title, (select u.UserName FROM users u WHERE p.user_id = u.id)
FROM posts p
In order of preference
You indeed need to use a join (most likely an inner join, here) between your two tables :
select posts.id, posts.title, users.username
from posts
inner join users on users.id = posts.user_id
With this, for each post, you'll get the corresponding user's informations -- and you can add whatever field you want from users to the select part of the query.
Try this query:
SELECT Posts.ID, Posts.Title, Users.UserName
FROM Posts INNER JOIN Users ON Posts.User_ID = Users.ID
Try this:
select posts.id, title, username
from posts join users
on posts.user_id = users.id
If the column name is ambiguous (e.g., id), you can prefix it with the table name, as shown above.