Include null rows when querying with JOIN - mysql

Say I have two tables: FRUITS and USERS.
fruits
-------------
id | name |
-------------
1 | Apple |
2 | Orange |
3 | Pear |
-------------
users
-------------------
id | name | fruit |
-------------------
1 | John | 3 |
2 | Bob | 2 |
3 | Adam | 1 |
-------------------
I use the below query (answered in this question) to query users with a proper fruit name instead of just fruit ID:
SELECT u.id, u.name, f.name FROM users u JOIN fruits f ON u.fruit = f.id
However the above query only returns users with a valid fruit ID. How to modify this query to also return users with invalid/empty fruit ID? That way fruit name should be NULL.

Use a 'LEFT JOIN':
SELECT
u.id
,u.name
,f.name FROM
users u
LEFT JOIN fruits f ON u.fruit = f.id

Use left join
SELECT
u.id,
u.name,
f.name
FROM users u
LEFT JOIN fruits f ON u.fruit = f.id

You would use a left join:
SELECT u.id, u.name, f.name
FROM users u LEFT JOIN
fruits f
ON u.fruit = f.id;
This will keep all rows in the users table, even those with non-matching values in fruits.

SELECT u.id, u.name, f.name FROM users u
LEFT JOIN fruits f ON u.fruit = f.id
Read about LEFT JOIN here.

Related

MYSQL - Group Contact rows with records NOT IN

My case looks simple but i'm messing around with this..
I have 4 tables: User, Macros, Categories, and another one that relate users with categories. One Macro have many Categories.
What i need, is a query that based on the Macro, get the users and the Categories where user is NOT IN.
Example: I have a macro named VEICULES, with categories CAR,TRUCK and Motorcycle. User José is on category CAR and User Julio on category CAR and TRUCK, so my query should return:
José | TRUCK,Motorcycle
Julio | Motorcycle
Tables:
prd_users
id | name | Email
---------------------------
1 | José | jose#email.com
2 | Júlio | julio#email.com
3 | André | andre#email.com
cat_macros
macro_id | macro_name
-----------------------
1 | Veicules |
cat_categories
category_id | category_name | macro_id
---------------------------------------
1 | Cars | 1
2 | Trucks | 1
3 | Motorcycles | 1
prd_tr_rabbit_catg
id | category_id | tasker_user_id
---------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
I'm stucked on just getting the categories where the user already is ..
SELECT prd_users.id, prd_users.name,
prd_users.email,cat_macros.macro_name as macro,
GROUP_CONCAT(cat_categories.category_name SEPARATOR ', ') as in_categories
FROM prd_users
INNER JOIN prd_tr_rabbit_catg ON prd_tr_rabbit_catg.tasker_user_id = prd_users.id
INNER JOIN cat_categories ON cat_categories.category_id = prd_tr_rabbit_catg.category_id
INNER JOIN cat_macros ON cat_macros.macro_id = cat_categories.macro_id
WHERE cat_macros.macro_id = '45'
GROUP BY prd_users.id;
To solve this problem it's necessary to create a list of all users joined with all categories for the given macro category. This can be done with a CROSS JOIN:
SELECT *
FROM prd_users u
CROSS JOIN (SELECT m.macro_id, m.macro_name, c.category_name, c.category_id
FROM cat_macros m
JOIN cat_categories c ON c.macro_id = m.macro_id) c
This can then be LEFT JOINed to the prd_tr_rabbit_catg table and by selecting those rows where there is no matching entry in the prd_tr_rabbit_catg table, we can find the users who don't have an entry for the given category:
SELECT c.macro_name, u.id AS user_id, u.name, u.Email, GROUP_CONCAT(c.category_name) AS missing_cats
FROM prd_users u
CROSS JOIN (SELECT m.macro_id, m.macro_name, c.category_name, c.category_id
FROM cat_macros m
JOIN cat_categories c ON c.macro_id = m.macro_id) c
LEFT JOIN prd_tr_rabbit_catg x ON x.tasker_user_id = u.id AND x.category_id = c.category_id
WHERE x.id IS NULL
AND c.macro_id = 1
GROUP BY c.macro_name, u.id
For your sample data, this gives:
macro_name user_id name Email missing_cats
Veicules 1 José jose#email.com Motorcycles,Trucks
Veicules 2 Júlio julio#email.com Motorcycles
Veicules 3 André andre#email.com Cars,Motorcycles,Trucks
Update
To exclude users who don't have any of the categories, add a HAVING clause:
HAVING COUNT(*) < (SELECT COUNT(*) FROM cat_categories WHERE macro_id = 1)
Demo on SQLFiddle

join pivot table in mysql

How to join different tables with pivote table
I have 4 tables like
users
id | name |
-------------
1 | abc |
2 | ccc |
user_profile
id | user_id | email |
-------------------------------
1 | 1 | abc#gmail.com
2 | 2 | ccc#gmail.com
skills
id | skill_name |
--------------------------
1 | java |
2 | php |
user_skills
user_id | skill_id |
---------------------------
1 | 1 |
1 | 2 |
2 | 1 |
The result should be
name | email | skills |
----------------------------------
abc |abc#gmail.com | java, php |
ccc |ccc#gmail.com | java |
I am able to join multiple tables but I have problem joining pivote
I have tried below with query
SELECT users.name,user_profiles.email, group_concat(programs.name)
from users
JOIN user_profiles on user_profiles.user_id = users.id
LEFT JOIN user_skills on user_skills.user_id = users.id
LEFT JOIN skills on user_skills.skill_id = skills.id
GROUP BY users.id
Can anyone help me on this please??Thanks
You need GROUP_CONCAT to generate the CSV list of skills:
SELECT
u.name,
up.email,
GROUP_CONCAT(s.skill_name) AS skills
FROM users u
INNER JOIN user_profile up
ON u.id = up.user_id
LEFT JOIN user_skills us
ON u.id = us.user_id
INNER JOIN skills s
ON us.skill_id = s.id
GROUP BY
u.id, u.name, up.email;
Demo
Note that I group by both the user's id and name, because perhaps two users happen to have the same name. Follow the link below for a running SQLFiddle.
Your query should work. Perhaps the problem is the reference to programs rather than skills:
select u.name, up.email, group_concat(s.name)
from users u join
user_profiles up
on up.user_id = u.id left join
user_skills us
on us.user_id = u.id left join
skills s
on us.skill_id = s.id
group by u.name, up.email;

MySQL query from data normalized tables

I have three tables:
users
user_id username
---------------------
1 | mrzander
2 | foo
3 | bar
---------------------
interests
interest_id interest
------------------------
1 | cars
2 | power tools
3 | shaving
4 | phones
5 | computers
------------------------
user_interests
id uid iid
-----------------
1 | 1 | 2
2 | 1 | 4
3 | 2 | 3
4 | 1 | 5
-----------------
Basically, I have a table of users, a table of interests, and a table that shows what users have what interests. If I know what user id I want the interests from, what query would give me all of a particular users interests?
In this example, what query would return a table called "Interests" that tells me user_id = 1 likes power tools, phones, and computers?
If you want the result on same row you should use join and group concat
select c.username, group_concat( b.interst)
from user_interest as a
left join interest as b on a.iid = b.interest_id
left join users as c. on c.user_id = a.uid
where c.user_id = 1
group by c.username
or if you need result on different rows se join only
select c.username, b.interst
from user_interest as a
left join interest as b on a.iid = b.interest_id
left join users as c. on c.user_id = a.uid
where c.user_id = 1
Simply join the two tables.
select i.*
from interests i
join user_interests u
on u.iid = i.interest_id
where i.uid = 1;

sql query combine two queries into one with empty rows

This is my sql table structure:
Table1: details
|--id--|--id_user--|--price--|
| 1 | 1 | 10 |
| 2 | 2 | 15 |
| 3 | 1 | 25 |
| 4 | 3 | 30 |
| 5 | 3 | 7 |
------------------------------
Table2: users
|--id--|--id_country--|
| 1 | 1 |
| 2 | 2 |
| 3 | 0 |
-----------------------
Table3: country
|--id--|--country--|
| 1 | France |
| 2 | Italy |
--------------------
What I need is to get the SUM of price by country:
SELECT c.country, SUM(d.price) AS price
FROM details d
INNER JOIN users u ON u.id = d.id_user
INNER JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
I get this:
|--country--|--price--|
| France | 35 |
| Italy | 15 |
-----------------------
BUT I'd need to get this:
|--country--|--price--|
| France | 35 |
| Italy | 15 |
| Undefined | 37 |
-----------------------
where undefined would be if id_country=0. (I can't add to country table the id=0 or id=undefined, it will messed up other things). Right now I'm achieving this by two separate queries, the second one is:
SELECT SUM(d.price) as price
FROM details d
INNER JOIN users u ON u.id = d.id_user AND u.id_country=0
GROUP BY u.id_country
I'm thinking if... is it possible to do this in one query?
You need to use left join in this case:
SELECT c.country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
If you use INNER JOIN, you will only get results that exists in both tables.
To replace NULL with Undefined use:
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
One way to sort to get Undefined last is to add a Sortfield
SELECT A.Country,A.Price FROM (
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price, IFNULL(c.Country,'ZZZZZZZZ') AS Sort
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
) A
ORDER BY A.Sort
Edit: ORDER BY suggested in comments
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country IS NULL, c.country
Try below query.
SELECT
CASE
WHEN c.country is NULL THEN 'Undefined'
ELSE c.country
END as country
, SUM(d.price) AS price
FROM users u
left JOIN details d ON u.id = d.id_user
left JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
For Demo :
SqlfiddlE Demo :
Please let us know if you have any que.

SQL join with aggregated data

I have two SQL tables.
USERS
ID | USERNAME
001 | Tom
002 | Jane
003 | Peter
004 | Mariah
PRODUCTS
ID | PR_NAME | USERID
01 | Apple | 002
02 | Pear | 001
03 | Tomato | 002
04 | Apple | 003
05 | Tomato | 001
06 | Apple | 001
If I select "Apple" in a form/list/app I want to see who bought "Apple" AND how many products those persons bought:
USERID | USERNAME | NUM_PRODUCTS_BOUGHT
001 | Tom | 3
002 | Jane | 2
003 | Peter | 1
What I tried is
SELECT
USERS.ID, USERS.USERNAME,
COUNT(PRODUCTS.PR_NAME) AS NUM_PRODUCTS_BOUGHT
FROM
USERS
LEFT JOIN
PRODUCTS ON PRODUCTS.USERID = USERS.ID
WHERE
PRODUCTS.PR_NAME = "Apple"
GROUP BY
USERS.ID
but it gives me wrong numbers from NUM_ALL (1) like
ID | USERNAME | NUM_PRODUCTS_BOUGHT
001 | Tom | 1
002 | Jane | 1
003 | Peter | 1
I can get this result in 3 ways but cannot get what I want.
I think you should try a having clause. I haven't tested this, but it should work:
SELECT
USERS.ID, USERS.USERNAME,
COUNT(PRODUCTS.PR_NAME) AS NUM_PRODUCTS_BOUGHT
FROM
USERS
LEFT JOIN
PRODUCTS ON PRODUCTS.USERID = USERS.ID
GROUP BY
USERS.ID
HAVING
SUM(CASE WHEN PRODUCTS.PR_NAME = "Apple" THEN 1 ELSE 0 END) > 0
If the problem is that you are missing "Mariah", this is because the where clause is undoing the left outer join. Move the condition to the on clause:
SELECT u.ID, u.USERNAME, COUNT(p.PR_NAME) AS NUM_PRODUCTS_BOUGHT
FROM USERS u LEFT JOIN
PRODUCTS p
ON p.USERID = u.ID AND
p.PR_NAME = 'Apple'
GROUP BY u.ID;
NOTE: In some databases, you would need to include u.USERNAME in the group by clause. However, assuming that USERS.ID is unique, this usage is consistent with the ANSI standard.
You can think about it in layers...
First, you have a product
--q1
select * from PRODUCTS where PR_NAME = "Apple"
Then you want to know users that brought that product
--q2
select * from users where
id in (
--q1
select USERID from PRODUCTS where PR_NAME = "Apple"
)
Now for a user you want to know how many products he/she bought
--q3
select userid, count(id) cnt from PRODUCT group by userid
And finally you want to combine output from q2 and q3:
select u.id, u.USERNAME, cnt.cnt from users u inner join
( --q3
select userid, count(id) cnt from PRODUCT group by userid
) cnt on u.ID = cnt.userid
where
u.id in (
--q1
select USERID from PRODUCTS where PR_NAME = "Apple"
)
optimization:
select u.id, u.USERNAME, cnt.cnt from users u inner join
( --q3
select userid, count(id) cnt from PRODUCT group by userid
) cnt on u.ID = cnt.userid
inner join product p on u.id = p.userid
where
p.pr_name = 'Apple'
Try this:
SELECT U.ID, U.USERNAME, COUNT(P2.PR_NAME) NUM_PRODUCTS_BOUGHT
FROM USERS U
JOIN PRODUCTS P1
ON U.ID = P1.USERID
JOIN PRODUCTS P2
ON P1.USERID = P2.USERID
WHERE P1.PR_NAME = 'Apple'
GROUP BY U.ID, U.USERNAME
Here is the SQL FIDDLE for SQL Server.