How to get user's detail from related table with MySql? - mysql

I have 3 tables that looks like this:
Users
id | name | password
------------------------
2 | John | ******
3 | Ben | ******
4 | Dan | ******
UserHobbies
id | user_id | hobbie_id
-------------------------
1 | 2 | 1
2 | 2 | 3
3 | 3 | 1
4 | 4 | 2
Hobbies
id | HobbieName
------------------------
1 | Surfing
2 | Walking
3 | Soccer
I want to find the user's related hobbies so the result will look like this:
username | HobbieName | hobbie_id
------------------------
John | Surfing | 1
Ben | Surfing | 1
As you can see - users John and Ben have the same hobby - 'Surfing', so the result will display ONLY them.
Here is what i've done so far -
SELECT users.name, hobbies.hobbie_name, COUNT(user_hobbies.hobby_id) FROM
user_hobbies
INNER JOIN users on user_hobbies.user_id = users.id
INNER JOIN hobbies ON hobbies.id = user_hobbies.hobby_id
GROUP BY user_hobbies.hobby_id
And the result :
name | hobbie_name | count
---------------------------
dan | Surfing | 2
As you can see - i get the count of each hobbie - rather then a row with the user and the hobbie

To get only the hobbies that have multiple users, join with a subquery that counts the number of users per hobby.
SELECT users.name, hobbies.hobbie_name, user_hobbies.hobby_id
FROM user_hobbies
INNER JOIN users on user_hobbies.user_id = users.id
INNER JOIN hobbies ON hobbies.id = user_hobbies.hobby_id
INNER JOIN (
SELECT hobby_id
FROM user_hobbies
GROUP BY hobby_id
HAVING COUNT(*) > 1
) AS multiple ON multiple.hobby_id = user_hobbies.hobby_id

Could use WHERE too: http://sqlfiddle.com/#!9/241cfd/4/0
Data
create table users (id INT, name VARCHAR(20), password VARCHAR(20));
create table user_hobbies (id INT, user_id INT, hobby_id INT);
create table hobbies (id INT, name VARCHAR(20));
INSERT INTO users VALUES (2, 'John', '**********');
INSERT INTO users VALUES (3, 'Ben', '**********');
INSERT INTO users VALUES (4, 'Dan', '**********');
INSERT INTO user_hobbies VALUES (1, 2, 1);
INSERT INTO user_hobbies VALUES (2, 2, 3);
INSERT INTO user_hobbies VALUES (3, 3, 1);
INSERT INTO user_hobbies VALUES (4, 4, 2);
INSERT INTO hobbies VALUES (1, 'Surfing');
INSERT INTO hobbies VALUES (2, 'Walking');
INSERT INTO hobbies VALUES (3, 'Soccer');
SQL
SELECT u.name, h.name AS hobby, uh.hobby_id
FROM user_hobbies AS uh
INNER JOIN users AS u ON uh.user_id = u.id
INNER JOIN hobbies AS h ON h.id = uh.hobby_id
WHERE uh.hobby_id = 1;
Result
| name | hobby | hobby_id |
|------|---------|----------|
| John | Surfing | 1 |
| Ben | Surfing | 1 |
Hope that helps.

Related

How to join 2 tables with one of them containing multiple values in a single column

I have two tables users and interests which i'm trying to join. Inside users table i have columns as id, name, interest, etc. The interest column contain multiple values as "1,2,3". My second table interests have 2 columns id and name as:
id | name
-------------
1 | business
2 | farming
3 | fishing
What i want to do is join interests table with users table so i get the following output:
users table:
id | name | interest | interest_name
----------------------------------------------
1 | username | "1,2" | "business, farming"
2 | username | "2,3" | " farming, fishing"
I wrote the following query to achieve this:
select users.*, interests.name as interest_name
from users
left join interests on users.interest = interests.id;
Results i got:
id | name | interest | interest_name
----------------------------------------
1 | username | "1,2" | "business"
2 | username | "2,3" | " farming"
Problem:
I'm only getting the name of first values from interest column whereas i want all the values from interest column i have already tried using group_concat and find_in_set but getting the same results.
In the case you cannot create an additional database table in order to normalize the data...
Here's a solution that creates an ad hoc, temporary user_interests table within the query.
SELECT users.id user_id, username, interests, interests.interest
FROM users
LEFT JOIN (
SELECT
users.id user_id,
(SUBSTRING_INDEX(SUBSTRING_INDEX(users.interests, ',', ui.ui_id), ',', -1) + 0) ui_id
FROM users
LEFT JOIN (SELECT id AS ui_id FROM interests) ui
ON CHAR_LENGTH(users.interests) - CHAR_LENGTH(REPLACE(users.interests, ',', '')) >= (ui.ui_id - 1)
) user_interests ON users.id = user_interests.user_id
LEFT JOIN interests ON user_interests.ui_id = interests.id
ORDER BY user_id, ui_id;
Outputs:
user_id | username | interest_ids | interest
--------+----------+--------------+---------
1 | fred | 3,4,8,6,10 | fishing
1 | fred | 3,4,8,6,10 | sports
1 | fred | 3,4,8,6,10 | religion
1 | fred | 3,4,8,6,10 | science
1 | fred | 3,4,8,6,10 | philanthropy
2 | joe | 7,11,8,9 | art
2 | joe | 7,11,8,9 | science
2 | joe | 7,11,8,9 | politics
2 | joe | 7,11,8,9 | cooking
As you can see...
SELECT
users.id user_id,
(SUBSTRING_INDEX(SUBSTRING_INDEX(users.interests, ',', ui.ui_id), ',', -1) + 0) ui_id
FROM users
LEFT JOIN (SELECT id AS ui_id FROM interests) ui
ON CHAR_LENGTH(users.interests) - CHAR_LENGTH(REPLACE(users.interests, ',', '')) >= (ui.ui_id - 1)
...builds and populates the temporary table user_interests with the users.interests field data normalized:
user_id | ui_id
--------+------
1 | 3
1 | 4
1 | 6
1 | 8
1 | 10
2 | 7
2 | 8
2 | 9
2 | 11
...which is then LEFT JOIN'ed between the users and interests tables.
Try it here: https://onecompiler.com/mysql/3yfhmgq3y
-- create
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(20),
interests VARCHAR(20)
);
CREATE TABLE interests (
id INT PRIMARY KEY,
interest VARCHAR(20)
);
-- insert
INSERT INTO users VALUES (1, 'fred', '3,4,8,6,10'), (2, 'joe', '7,11,8,9');
INSERT INTO interests VALUES (1, 'business'), (2, 'farming'), (3, 'fishing'), (4, 'sports'), (5, 'technology'), (6, 'religion'), (7, 'art'), (8, 'science'), (9, 'politics'), (10, 'philanthropy'), (11, 'cooking');
-- select
SELECT users.id user_id, username, interests, interests.interest
FROM users
LEFT JOIN (
SELECT
users.id user_id,
(SUBSTRING_INDEX(SUBSTRING_INDEX(users.interests, ',', ui.ui_id), ',', -1) + 0) ui_id
FROM users
LEFT JOIN (SELECT id AS ui_id FROM interests) ui
ON CHAR_LENGTH(users.interests) - CHAR_LENGTH(REPLACE(users.interests, ',', '')) >= (ui.ui_id - 1)
) user_interests ON users.id = user_interests.user_id
LEFT JOIN interests ON user_interests.ui_id = interests.id
ORDER BY user_id, ui_id;
Inspired by Leon Straathof's and fthiella's answers to this SO question.
Pull the interest column out of the users table and create a user_interests table that contains the user ids and interest ids:
user_id | interest_id
--------+------------
1 | 1
1 | 2
2 | 2
2 | 3
Then join the users table to the user_interests table, and the user_interests table to the interests table:
SELECT users.username, interests.interest
FROM users
LEFT JOIN user_interests ON users.id = user_interests.user_id
LEFT JOIN interests ON user_interests.interest_id = interests.id
WHERE interest_id IS NOT NULL;
Outputs:
username | interest
---------+---------
Clark | business
Clark | farming
Dave | farming
Dave | fishing
Then use your server programming language to compile the query results.
Try it here: https://onecompiler.com/mysql/3yfe5pp7x
-- create
CREATE TABLE users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL
);
CREATE TABLE user_interests (
user_id INTEGER,
interest_id INTEGER,
UNIQUE KEY user_interests_constraint (user_id,interest_id)
);
CREATE TABLE interests (
id INTEGER PRIMARY KEY,
interest TEXT NOT NULL
);
-- insert
INSERT INTO users VALUES (1, 'Clark'), (2, 'Dave'), (3, 'Ava');
INSERT INTO interests VALUES (1, 'business'), (2, 'farming'), (3, 'fishing');
INSERT INTO user_interests VALUES (1, 1), (1, 2), (2, 2), (2, 3);
-- fetch
SELECT users.username, interests.interest
FROM users
LEFT JOIN user_interests ON users.id = user_interests.user_id
LEFT JOIN interests ON user_interests.interest_id = interests.id
WHERE interest_id IS NOT NULL;

SQL JOIN with WHERE condition when two rows' values are the same and one row matches to two different rows

I have these tables:
CREATE TABLE students(
id int NOT NULL PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
CREATE TABLE studentsActivities(
studentId int NOT NULL,
activity VARCHAR(30) NOT NULL,
PRIMARY KEY (studentId, activity),
foreign KEY (studentId) REFERENCES students(id)
);
And I have to return all student names that do either Tennis or Football. However, there was a test case which I could not pass and it was stated like that:
Students with the same name.
I do not know the exact implementation of the test cases, but I suspect it was the situation where student A named Carl does Tennis and student B also named Carl does Football and Carl is showed two times. How could I query that database to get the result like that? I've created the demo base to try:
CREATE TABLE students(
id int NOT NULL PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
CREATE TABLE studentsActivities(
studentId int NOT NULL,
activity VARCHAR(30) NOT NULL,
PRIMARY KEY (studentId, activity),
foreign KEY (studentId) REFERENCES students(id)
);
INSERT INTO students
VALUES
(1, "Jeremy"),
(2, "Hannah"),
(3, "Luke"),
(4, "Frank"),
(5, "Sue"),
(6, "Sue"),
(7, "Peter");
INSERT INTO studentsActivities
VALUES
(1, "Tennis"),
(1, "Football"),
(2, "Running"),
(3, "Tennis"),
(4, "Football"),
(5, "Football"),
(6, "Tennis");
SQL Fiddle
And let's suppose that the passing set would be:
Jeremy
Luke
Frank
Sue
Sue
I've attempted with these two queries, but none gives the correct answer.
--- 1
SELECT s.name
FROM students s
JOIN studentsActivities sa
ON sa.studentId = s.id
WHERE activity = "Tennis"
UNION
SELECT s.name
FROM students s
JOIN studentsActivities sa
ON sa.studentId = s.id
WHERE activity = "Football"
--- Returns Frank Jeremy Luke Sue (missing one Sue)
--- 2
SELECT s.name
FROM students s
JOIN studentsActivities sa
ON sa.studentId = s.id
WHERE activity = "Tennis"
OR activity = "Football"
ORDER BY s.name;
--- Returns Frank Jeremy Jeremy Luke Sue Sue (too much Jeremies)
Join the tables, filter only the rows with the activities that you want and return distinct rows:
select distinct s.id, s.name
from students s inner join studentsActivities a
on a.studentId = s.id
where a.activity in ('Tennis', 'Football')
See the demo.
Results:
| id | name |
| --- | ------ |
| 1 | Jeremy |
| 3 | Luke |
| 4 | Frank |
| 5 | Sue |
| 6 | Sue |
If you want only the names of the students without the ids:
select s.name
from students s inner join studentsActivities a
on a.studentId = s.id
where a.activity in ('Tennis', 'Football')
group by s.id, s.name
See the demo.
Results:
| name |
| ------ |
| Jeremy |
| Luke |
| Frank |
| Sue |
| Sue |
You can use exists:
select s.*
from students s
where exists (
select 1
from studentsActivities sa
where sa.studentId = s.id and sa.activity in ('Tennis', 'Football')
)
Demo on DB Fiddle:
id | name
-: | :-----
1 | Jeremy
3 | Luke
4 | Frank
5 | Sue
6 | Sue

SQL - LEFT JOIN, but I want COUNT(*) to only count the results from the INNER part of the join

I want to display the number of purchases each customer has made. If they've made 0 purchases, I want to display 0.
Desired Output:
-------------------------------------
| customer_name | number_of_purchases |
-------------------------------------
| Marg | 0 |
| Ben | 1 |
| Phil | 4 |
| Steve | 0 |
-------------------------------------
Customer Table:
-----------------------------
| customer_id | customer_name |
-----------------------------
| 1 | Marg |
| 2 | Ben |
| 3 | Phil |
| 4 | Steve |
-----------------------------
Purchases Table:
--------------------------------------------------
| purchase_id | customer_id | purchase_description |
--------------------------------------------------
| 1 | 2 | 500 Reams |
| 2 | 3 | 6 Toners |
| 3 | 3 | 20 Staplers |
| 4 | 3 | 2 Copiers |
| 5 | 3 | 9 Name Plaques |
--------------------------------------------------
My current query is as follows:
SELECT customer_name, COUNT(*) AS number_of_purchaes
FROM customer
LEFT JOIN purchases ON customer.customer_id = purchases.customer_id
GROUP BY customer.customer_id
However, since it's a LEFT JOIN, the query results in rows for customers with no purchases, which makes them part of the COUNT(*). In other words, customers who've made 0 purchases are displayed as having made 1 purchase, like so:
LEFT JOIN Output:
-------------------------------------
| customer_name | number_of_purchases |
-------------------------------------
| Marg | 1 |
| Ben | 1 |
| Phil | 4 |
| Steve | 1 |
-------------------------------------
I've also tried an INNER JOIN, but that results in customers with 0 purchases not showing at all:
INNER JOIN Output:
-------------------------------------
| customer_name | number_of_purchases |
-------------------------------------
| Ben | 1 |
| Phil | 4 |
-------------------------------------
How could I achieve my Desired Output where customers with 0 purchases are shown?
Instead of count(*) use count(purchase_id)
SELECT customer_name, COUNT(purchase_id) AS number_of_purchaes
FROM customer
LEFT JOIN purchases ON customer.customer_id = purchases.customer_id
GROUP BY customer_id,customer_name
You can try like this:
Sample Data:
create table customer(customer_id integer, customer_name varchar(20));
create table purchaser(purchaser_id varchar(20), customer_id integer, description varchar(20));
insert into customer values(1, 'Marg');
insert into customer values(2, 'Ben');
insert into customer values(3, 'Phil');
insert into customer values(4, 'Steve');
insert into purchaser values(1, 2, '500 Reams');
insert into purchaser values(2, 3, '6 toners');
insert into purchaser values(3, 3, '20 Staplers');
insert into purchaser values(4, 3, '20 Staplers');
insert into purchaser values(5, 3, '20 Staplers');
SELECT c.customer_id, c.customer_name, COUNT(p.purchaser_id) AS number_of_purchaes
FROM customer c
LEFT JOIN purchaser p ON c.customer_id = p.customer_id
GROUP BY c.customer_id;
SQL fiddle: http://sqlfiddle.com/#!9/32ff0a/2
COUNT(*) returns the number of items in a group. This includes NULL values and duplicates.
COUNT(ALL expression) evaluates expression for each row in a group, and returns the number of nonnull values.
CREATE table customer(customer_id integer , customer_name varchar(20));
create table purchases(purchase_id integer , customer_id integer , purchase_description varchar(30));
INSERT INTO customer ( customer_id, customer_name )
VALUES ( 1, 'Marg' )
, ( 2, 'Ben' )
, ( 3, 'Phil' )
, ( 4, 'Steve' );
INSERT INTO purchases ( purchase_id, customer_id, purchase_description )
VALUES ( 1, 2, '500 Reams' )
, ( 2, 3, '6 toners' )
, ( 3, 3, '20 Staplers' )
, ( 4, 3, '2 Copiers' )
, ( 5, 3, '9 Name Plaques' );
SELECT c.customer_name
, COUNT(p.purchase_id) AS number_of_purchases
FROM customer c
LEFT JOIN purchases p
ON c.customer_id = p.customer_id
GROUP BY c.customer_name
COUNT(*) counts rows. You want to count matches, so count from the second table as following:
select customer.customer_name , a.number_of_purchases from (
SELECT customer_id, COUNT(purchases.purchase_id) AS number_of_purchaes
FROM customer
LEFT JOIN purchases ON customer.customer_id = purchases.customer_id
GROUP BY customer.customer_id) as a
inner join customer on customer.customer_id=a.customer_id;
In other words, the LEFT JOIN returns a row when there is no match. That row has a NULL value for all the columns in the purchases table.
SELECT
customer_name, COUNT(purchase_id) AS number_of_purchases
FROM
customer AS c
LEFT JOIN purchases AS p ON (c.cid = p.cid)
GROUP BY c.name
Instead of count(*) use COUNT(purchases.customer_id)
SELECT customer_name, COUNT(purchases.customer_id) AS number_of_purchaes
FROM customer
LEFT JOIN purchases ON customer.customer_id = purchases.customer_id
GROUP BY customer.customer_id
SELECT c.customer_name,count(p.purchase_id)number_of_purchases FROM Customer c
LEFT JOIN
Purchases AS p ON c.customer_id = p.customer_id
GROUP BY c.customer_name

Find duplicate values within 3 columns SQL

I have a table like the below:
Site | Name | ID
A | Mike | 1
A | Mary | 2
A | Mary | 3
B | Mary | 1
B | Rich | 2
I'd like to find all the duplicate Name's within a Site. So I'm trying to return:
Site | Name | ID
A | Mary | 2
A | Mary | 3
I've tried this:
SELECT DISTINCT Site, Name, ID
from table
group by ID having count(*) > 1
The results come back erroneously because it's counting Sites A & B together. I would like to only find the duplicates for within each Site--not duplicates across Sites.
You can use exists or in:
select t.*
from t
where exists (select 1
from t t2
where t2.site = t.site and t2.name = t.name and t2.id <> t.id
);
You can try to use NOT exists with subquery having.
the duplicate Name's within a Site
CREATE TABLE T(
Site varchar(5),
Name varchar(5),
ID int
);
insert into t values ('A','Mike', 1);
insert into t values ('A','Mary', 2);
insert into t values ('A','Mary', 3);
insert into t values ('B','Mary', 1);
insert into t values ('B','Rich', 2);
Query 1:
SELECT * FROM T t1 WHERE
NOT exists
(
SELECT 1
FROM T tt
where t1.name = tt.name
group by Name
HAVING MIN(tt.ID) = t1.ID
)
Results:
| Site | Name | ID |
|------|------|----|
| A | Mary | 2 |
| A | Mary | 3 |
find the duplicates for within each Site
CREATE TABLE T(
Site varchar(5),
Name varchar(5),
ID int
);
insert into t values ('A','Mike', 1);
insert into t values ('A','Mary', 2);
insert into t values ('A','Mary', 3);
insert into t values ('B','Mary', 1);
insert into t values ('B','Rich', 2);
Query 1:
SELECT t1.*
FROM T t1
WHERE not exists
(
SELECT 1
FROM T tt
where t1.name = tt.name and t1.Site = tt.Site
group by Name,Site
HAVING MIN(tt.ID) = t1.ID
)
Results:
| Site | Name | ID |
|------|------|----|
| A | Mary | 3 |

GROUP_CONCAT on LEFT JOIN generates groups against NULL

I'm trying to use a LEFT JOIN in conjunction with a GROUP_CONCAT but not getting the expected results.
Two simple tables:
weather_alerts:
id | user_id | resort_id
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 1 | 5
weather_users
id | email
1 | me#me.com
The query:
SELECT GROUP_CONCAT(wa.resort_id) AS resort_ids, wu.email FROM weather_alerts wa LEFT JOIN weather_users wu ON wa.id = wu.user_id GROUP BY wu.email
Instead of generating:
email resort_ids
me#me.com 1,2,3,5
I get:
email resort_ids
NULL 2,3,5
me#me.com 1
I suspect this is an issue with the JOIN rather than the CONCAT.
It appears that your LEFT JOIN needs improvement.
create table weather_alerts (id int, user_id int, resort_id int);
insert into weather_alerts values (1, 1, 1), (2, 1, 2), (3, 1, 3), (4, 1, 5);
create table weather_users (id int, email varchar(100));
insert into weather_users values (1, 'me#me.com');
Query
SELECT GROUP_CONCAT(wa.resort_id ORDER BY wa.resort_id) AS resort_ids, wu.email
FROM weather_alerts wa
LEFT JOIN weather_users wu ON wa.user_id = wu.id
GROUP BY wu.email
Notice that you are joining on wa.id = wu.user_id. The join should be on wa.user_id = wu.id
Result
| resort_ids | email |
|------------|-----------|
| 1,2,3,5 | me#me.com |