SQL join with aggregated data - mysql

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.

Related

MYSQL GROUP BY LEFT JOIN and COUNT

I am having a problem grouping and counting items in a MYSQL database with JOIN clause
My two tables are as follows
users table
id | surname | othernames
1 | Doe | John
2 | Doe | Jane
3 | Doe | Mary
subscriptions table
id | user_id | parent_subscription_id
1 | 1 | Null
2 | 2 | 1
3 | 3 | 1
4 | 4 | 2
5 | 5 | 3
6 | 6 | 3
I need to be able to produce a list as follows
Name | Referrals
John Doe | 2
Jane Doe | 1
Mary Doe | 2
In other words,it Is the user in users table with the users.id which matches subscriptions.user_id that has the subscription with subscriptions.id which is a parent subscription to another subscription. That means, if your subscription is referenced by another subscription as its own parent_subscription_id, then that new subscription becomes your referral.
I have tried the following query and it is not giving me the expected results
SELECT users.surname, users.othernames,count('s.parent_subscription_id') as referrals
FROM users
LEFT JOIN subscriptions s ON s.user_id=users.id
group BY parent_subscription_id
I have checked some other questions on SO but I have not been able to find any that solves this type of issue
Thank you
I think that the logic you want is:
select u.surname, u.othernames, count(s.parent_subscription_id) referrals
from subscriptions s
left join subscriptions p on p.id = s.parent_subscription_id
inner join users u on u.id = coalesce(p.user_id, s.user_id)
group by u.id, u.surname, u.othernames
Demo on DB Fiddle:
surname | othernames | referrals
:------ | :--------- | --------:
Doe | John | 2
Doe | Jane | 1
Doe | Mary | 2
You ca use join between users table and select calculated count as:
SELECT
-- Get all users
users.surname,
users.othernames,
IFNULL(reff.cnt, 0) as referrals -- Preset 0 for users have not referrals in joined table
FROM users
-- Join calculation result
LEFT JOIN (
-- Calculate count by user
SELECT parent_subscription_id, COUNT(*) AS cnt
FROM subscriptions
GROUP BY subscriptions.parent_subscription_id
) reff on reff.parent_subscription_id = users.id;
Change group by fields with users.surname, users.othernames
SELECT users.surname, users.othernames,count(s.parent_subscription_id) as referrals
FROM users
LEFT JOIN subscriptions s ON s.user_id=users.id
group BY users.surname, users.othernames
you need to group by id of users table because you need get count for each user and here is your main table is users. try like this.
SELECT users.surname, users.othernames, count('s.parent_subscription_id') as referrals
FROM users
LEFT JOIN subscriptions s ON s.user_id = users.id
group BY users.id
This query eventually gave me the result I have been looking for
SELECT u.surname,u.othernames,s1.id,s1.parent_subscription_id,
s1.refcode, IFNULL(count(s2.parent_subscription_id),0) as referrals
FROM `subscriptions` s1 left join subscriptions s2
on s1.id=s2.parent_subscription_id
LEFT JOIN users u ON u.id=s1.user_id
GROUP by s1.id
Thank you all for your guidance and support on this. I deeply appreciate it

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 SUM of a column

I wish to return a users details including the total number of votes a user has for all their events.
I have the following query which returns a list of users events and the number of events they have.
SELECT u.user_uid, u.firstname, u.lastname,
( SELECT COUNT(ev.event_vote_id)
FROM event_vote ev
WHERE ue.event_uid = ev.event_uid
) AS votes,
( SELECT COUNT(ue.user_event_id)
FROM user_event ue
WHERE ue.user_uid = u.user_uid
) AS no_of_events
FROM user_event ue
JOIN user u
ON u.user_uid = '1'
WHERE ue.user_uid = '1'
But it returns a record for every event they have and I need a SUM of the votes column.
USER
| USER_UID | FIRSTNAME | LASTNAME |
1 bob smith
2 rob smithies
3 john clark
EVENT
| GUID | NAME |
101 event1
102 event2
103 event3
USER_EVENT
| USER_EVENT_ID | USER_UID | EVENT_UID |
1001 1 101
1002 2 102
1003 1 103
EVENT_VOTE
| EVENT_VOTE_ID | USER_UID | EVENT_UID |
2001 2 101
2002 3 101
2003 2 103
Expected Result
user_uid: 1
firstname: bob
lastname: smith
votes: 3 // 2 for 101, 1 for 103.
no_of_events: 2
You can either join all tables and aggregate then:
select
u.user_uid,
u.firstname,
u.lastname,
count(*) as votes,
count(distinct event_uid) as events
from user u
join user_event ue on ue.user_uid = u.user_uid
join event_vote ev on ev.event_uid = ue.event_uid
group by u.user_uid;
Or aggregate first and join then:
select
u.user_uid,
u.firstname,
u.lastname,
sums.votes,
sums.events
from user u
left join
(
select
ue.user_uid,
count(*) as votes,
count(distinct ev.event_uid) as events
from user_event ue
left join event_vote ev on ev.event_uid = ue.event_uid
group by ue.user_uid
) sums on sums.user_uid = u.user_uid
If you want to select users without events or without votes, too, you need outer joins and count(ev.event_vote_id) instead of count(*).

Include null rows when querying with JOIN

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.

LEFT JOIN 3 columns to get username

I have three columns I need to join which comes from 3 different tables,
Contributions table:
+-----------+---------------------+
| record_id | contributor_user_id |
+-----------+---------------------+
| 1 | 2 |
+-----------+---------------------+
| 1 | 5 |
+-----------+---------------------+
Members table:
+--------------+---------+
| username | user_id |
+--------------+---------+
| Test | 1 |
+--------------+---------+
| Test2 | 5 |
+--------------+---------+
| Test3 | 6 |
+--------------+---------+
Records table:
+---------+-----------+
| user_id | record_id |
+---------+-----------+
| 28 | 1 |
+---------+-----------+
For what I need to return is the username and user_id for displaying the record owner. Also, display the username and the user_id, but this can be multiple (more than 1+ user). I've tried this:
SELECT usr.username,
usr.user_id,
rec.record_id,
contrib.record_id,
contrib.contributor_user_id
FROM
(
records rec
INNER JOIN members usr ON rec.user_id = usr.user_id
# this returns records as NULL
LEFT OUTER JOIN contributions contrib ON rec.record_id = contrib.record_id AND contrib.contributor_user_id = usr.user_id
# this works, but I need the username to be displayed too
LEFT OUTER JOIN contributions contrib ON rec.record_id = contrib.record_id
)
WHERE rec.record_id = 1
Try nesting the join for contributing users inside of the left join to contributions.
SELECT u.username, u.user_id, r.record_id, u2.username as ContributorName, u2.user_id as ContributorId
FROM records r
INNER JOIN members u
ON r.user_id = u.user_id
LEFT JOIN contributions c
INNER JOIN members u2
ON c.contributor_user_id = u2.user_id
ON r.record_id = c.record_id
WHERE r.record_id = 1
SELECT
usr.username AS record_owner
, usr.user_id AS record_owner_id
, rec.record_id
, con.contributor_user_id AS contributor_id
, contributors.username AS contributor_name
FROM
records rec
INNER JOIN
members usr
ON rec.user_id = usr.user_id
LEFT OUTER JOIN
contributions con
ON rec.record_id = con.record_id
INNER JOIN
members contributors
ON con.contributor_user_id = contributors.user_id
WHERE
rec.record_id = 1