Writing a SQL query that gets information from 3 different tables - mysql

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
)

Related

Cannot update table by counting fields in related table

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)

SELECT statement with an case statement

I have the following table for Customer:
+------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| first | varchar(45) | YES | | NULL | |
| last | varchar(45) | YES | | NULL | |
| password | varchar(45) | NO | | NULL | |
| contact_id | int(11) | NO | PRI | NULL | |
| address_id | int(11) | NO | PRI | NULL | |
+------------+-------------+------+-----+---------+----------------+
And the following structure for Appointment:
+-------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| time | datetime | NO | | NULL | |
| cancelled | tinyint(1) | YES | | 0 | |
| confirmed | tinyint(1) | YES | | 0 | |
| customer_id | int(11) | NO | PRI | NULL | |
+-------------+------------+------+-----+---------+----------------+
I want to use a single query to get the customer information, if they have appointment information, then it'll query it, otherwise it won't.
I am trying to use the following:
CASE
WHEN (SELECT count(a.id) FROM appointment
INNER JOIN customer c ON a.customer_id = c.id)
THEN (SELECT c.first, c.last, c.id, a.id FROM appointent
INNER JOIN customer c ON a.customer_id = c.id)
ELSE
(SELECT c.first, c.last, c.id FROM customer)
END;
Do you have any suggestions?
how about
SELECT * FROM Customer c LEFT JOIN Appointment a ON a.CustomerId = c.Id
You could make two queries and UNION them.
SELECT c.first, c.last, c.id, a.id FROM appointent a
INNER JOIN customer c ON a.customer_id = c.id
UNION
SELECT c.first, c.last, c.id, null FROM customer c
Or an outer join, where the a.id would be populated with null if there was no match during the join.
SELECT c.first, c.last, c.id, a.id FROM customer c
OUTER JOIN appointent a ON a.customer_id = c.id
As per my comment on Zdravko's answer, you could have also used:
select * from customer where id in (select customer_id from appointment where cancelled = 0)
Which would allow you to filter in a nice way.
You can also filter Zdravko's answer like this:
SELECT * FROM Customer c LEFT JOIN Appointment a ON a.CustomerId = c.Id
WHERE a.cancelled 0 and a.confirmed = 1

MySQL join query on post with categories, only return one result per post

This is very basic, I know, but I tend to happen on this issue often.
Tables
mysql> describe Posts;
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| Time | int(11) | YES | MUL | NULL | |
+-----------+---------+------+-----+---------+----------------+
mysql> describe PostCategories;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| pid | int(11) | YES | | NULL | |
| Category | varchar(20) | YES | | NULL | |
+----------+-------------+------+-----+---------+----------------+
Query
SELECT P.id AS pid, P.Time, PC.Category FROM Posts P
LEFT OUTER JOIN PostCategories PC ON PC.pid = P.id
WHERE PC.Category IN('value1','value2') OR PC.Category IS NULL
ORDER BY P.Time DESC
Returns
+-----+------------+----------+
| pid | Time | Category |
+-----+------------+----------+
| 8 | 1396906256 | NULL |
| 7 | 1396524835 | value1 |
| 7 | 1396524835 | value2 |
+-----+------------+----------+
Desired outcome
I would like it to only give me one row for every pid. In other words, no matter how many categories the Post have, I want it to only result in one row in the result dataset.
+-----+------------+----------+
| pid | Time | Category |
+-----+------------+----------+
| 8 | 1396906256 | NULL |
| 7 | 1396524835 | value1 |
+-----+------------+----------+
Category result does not matter, I will not fetch it once it works as I want it to.
SELECT P.id AS pid, MIN(P.Time) as first_post_time, MAX(PC.Category) as Category FROM Posts P
LEFT OUTER JOIN PostCategories PC ON PC.pid = P.id
WHERE PC.Category IN('value1','value2') OR PC.Category IS NULL
GROUP BY P.id
ORDER BY P.Time DESC
Just add group by and calculate time and category (use min or max)
Group posts by using Group By and you can also get all categories if you want by using group_concat procedure in mysql.
SELECT P.id AS pid, P.Time, GROUP_CONCAT(PC.Category, ' , ') FROM Posts P
LEFT OUTER JOIN PostCategories PC ON PC.pid = P.id
WHERE PC.Category IN('value1','value2') OR PC.Category IS NULL
GROUP BY P.pid
ORDER BY P.Time DESC

Convert subquery to join

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

SQL query help: How can i get the column value from other table?

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.