Left join without using view - mysql

I have a table structure similar to this:
users
| id | name |
|----|--------|
| 1 | jane |
| 2 | john |
client
| id | name | user_id |
|----|------------|---------|
| 1 | client 1 | 2 |
| 2 | client 2 | 2 |
| 3 | client 3 | 2 |
| 4 | client 4 | 1 |
| 5 | client 5 | 1 |
products
| id | name | user_id |
|----|------------|---------|
| 1 | product 1 | 1 |
| 2 | product 2 | 1 |
| 3 | product 3 | 1 |
| 4 | product 4 | 2 |
| 5 | product 5 | 2 |
and I also created two views
client_total
SELECT user_id, COUNT(id) AS client_total FROM client GROUP BY user_id
| user_id | client_total |
|---------|--------------|
| 1 | 2 |
| 2 | 3 |
products_total
SELECT user_id, COUNT(id) AS products_total FROM products GROUP BY user_id
| user_id |products_total|
|---------|--------------|
| 1 | 3 |
| 2 | 2 |
and that was a result
SELECT
users.*,
client_total.client_total,
products_total.products_total
FROM
users
LEFT JOIN client_total ON users.user_id = client_total.user_id
LEFT JOIN products_total ON users.user_id = products_total.user_id
| id | name |products_total| client_total |
|----|--------|--------------|--------------|
| 1 | jane | 3 | 2 |
| 2 | john | 2 | 3 |
My question is:
Can I get this same result without using these two views?

GROUP BY in derived tables (the subqueries), before you LEFT JOIN:
SELECT
u.id,
u.name,
c.client_total,
p.products_total
FROM users u
LEFT JOIN
(SELECT user_id, COUNT(id) AS client_total FROM client GROUP BY user_id) c
ON u.id = c.user_id
LEFT JOIN
(SELECT user_id, COUNT(id) AS products_total FROM products GROUP BY user_id) p
ON u.id = p.user_id
(Doesn't even some people call these derived tables inline views?)

You may try the following direct aggregation join query:
SELECT
u.id,
u.name,
COUNT(DISTINCT p.id) AS products_total,
COUNT(DISTINCT c.id) AS client_total
FROM users u
LEFT JOIN client c
ON u.id = c.user_id
LEFT JOIN products p
ON u.id = p.user_id
GROUP BY
u.id,
u.name;
Demo

Often the most performant way to accomplish this uses correlated subqueries:
SELECT u.id, u.name,
(SELECT COUNT(*)
FROM client c
WHERE c.user_id = u.id
) as client_total,
(SELECT COUNT(*)
FROM products p
WHERE p.user_id = u.id
) as products_total
FROM users u;
In particular, this can take advantage of indexes on client(user_id) and products(user_id).

Related

How to COUNT and SELECT with WHERE in sql?

I have two tables user and group and also a usergroup as a link table which contains which users exist in a group.
user table
+-----+-------+
| id | name |
+-----+-------+
| U12 | John |
| U13 | Rick |
| U14 | Morty |
| U15 | Alex |
| U16 | Felix |
+-----+-------+
group table
+-----+--------+--------+
| id | name | points |
+-----+--------+--------+
| G12 | Red | 11 |
| G13 | Blue | 22 |
| G14 | Green | 55 |
| G15 | Yellow | 64 |
| G16 | Orange | 23 |
+-----+--------+--------+
usergroup table
+----+---------+----------+
| id | user_id | group_id |
+----+---------+----------+
| 1 | U12 | G12 |
| 2 | U14 | G12 |
| 3 | U15 | G12 |
| 4 | U15 | G15 |
| 5 | U12 | G13 |
+----+---------+----------+
To select the groups that a particular user is in, I can just do
SELECT group.*
FROM usergroup
INNER JOIN group
ON group.id = usergroup.group_id
WHERE usergroup.user_id = ?
But how to also simultaneously select the number of total users in the same group?
Expected Sample Output - for the groups that user U12 is in, along with total user count
+-----+------+--------+-------------+
| id | name | points | users_count |
+-----+------+--------+-------------+
| G12 | Red | 11 | 3 |
| G13 | Blue | 22 | 1 |
+-----+------+--------+-------------+
use scalar subquery
SELECT `group`.* , (select count(id) from usergroup a where a.group_id=usergroup.group_id ) as user_count
FROM usergroup
INNER JOIN `group` ON `group`.id = usergroup.group_id
WHERE usergroup.user_id = 'U12'
SELECT y.id, y.name, y.points, y.users_count
FROM usergroup x INNER JOIN
( SELECT b.id, b.name, b.points, count(a.user_id) as users_count
FROM usergroup a INNER JOIN group b
ON a.group_id = b.id
GROUP BY b.id ) AS y
ON x.group_id = y.id
WHERE x.user_id = 'U12'
SELECT a.group_id,
a.NAME,
a.points,
c.user_count
FROM group a,
usergroup b,
(SELECT x.group_id,
Count(*) AS user_count
FROM usergroup x
GROUP BY x.group_id) c
WHERE a.group_id = b.group_id
AND a.group_id = c.group_id
AND user_id = 'U12';
SELECT group.*, count(usergroup.group_id),usergroup.user_id
FROM usergroup
INNER JOIN group
ON group.id = usergroup.id
GROUP BY usergroup.group_id
Having usergroup.user_id = 'U12'
You need to use group by with having.
Check it on SQLFiddle: sqlfiddle.com/#!9/b63e13/3/0

Get latest record per timestamp in a left join query

I currently have 4 tables that I query to list status of orders.
Tables with relevant field look like this
+--------------+ +------------+ +----------+ +----------+
|Orders | | Customers | | Users | | Status |
+--------------+ +------------+ +----------+ +----------+
| id | | id | | id | | id |
| customer_id | | name | | name | | order_id |
| rep_id | +------------+ +----------+ | status |
+--------------+ | comments |
| date |Timestamp
| tech_id |
+----------+
Using the following SQL I can display the list of orders and status.
SELECT
Orders.id AS orderid,
Customers.name AS CLIENT,
Users.name AS rep,
Status.status
FROM
Orders
LEFT JOIN
Customers ON Orders.customer_id = Customers.id
LEFT JOIN
Users ON Orders.rep_id = Users.id
LEFT JOIN
Status ON Orders.id = Status.order_id
I get something like this.
+---------+----------+-------+--------+
| orderid | CLIENT | rep | status |
+---------+----------+-------+--------+
| 1 | Client 1 | Rep 1 | 1 |
| 2 | Client 2 | Rep 2 | 1 |
| 3 | Client 3 | Rep 1 | 1 |
| 4 | Client 4 | Rep 2 | 1 |
| 6 | Client 6 | Rep 4 | 1 |
| 1 | Client 1 | Rep 3 | 4 |
| 6 | Client 6 | Rep 4 | 4 |
+---------+----------+-------+--------+
I need to get one record per orderid based on the most recent date on the Status table. I feel that I'm so close however can't figure it out.
The simplest way is probably a correlated subquery:
SELECT o.id AS orderid, c.name AS CLIENT, u.name AS rep,
(SELECT s.status
FROM status s
WHERE o.id = s.order_id
ORDER BY s.date DESC
LIMIT 1
) latest_status
FROM Orders o LEFT JOIN
Customers c
ON o.customer_id = c.id LEFT JOIN
Users u
ON o.rep_id = u.id ;

SQL cross match data from different columns and rows

I've been having hard times figuring out how to select the following...
I have two tables
Table_users Table_followers
| id | name | | follow_id | user_id |
| 1 | John | | 1 | 2 |
| 2 | Max | | 3 | 1 |
| 3 | Mary | | 2 | 1 |
| 4 | Robert | | 6 | 1 |
| 5 | Robin | | 1 | 5 |
| 6 | Sarah | | 1 | 6 |
I'd like to return in a single query users who are following John and John is following them back, so that would be called MATCH.
Then users who are following John, FOLLOWERS
And finally users followed by John, FOLLOWING
I've used the following query, but it returns duplicates and it's far from what I'm looking for
SELECT u.id, u.name, f.follower_id, f.user_id
FROM table_users u
LEFT JOIN table_followers f ON f.follower_id = u.id OR f.user_id = u.id
WHERE (f.user_id != 1 OR f.follower_id != 1) AND u.id != 1
ORDER BY u.id ASC";
Desired result would be like...
| uid | name | match | follower | following |
| 2 | Max | 1 | null | null |
| 6 | Sarah | 1 | null | null |
| 3 | Mary | null | 1 | null |
| 5 | Robin | null | null | 1 |
Would it be possible at all with SQL?
One way to solve this is to join the follower table twice (once for followers, once for following) and do a query like this:
select
u.id,
u.name,
case when follow_id and user_id then 1 end as `match`,
case when follow_id and user_id is null then 1 end as follower,
case when user_id and follow_id is null then 1 end as following
from Table_users u
left join (select user_id from Table_followers where follow_id = 1) followers
on u.id = followers.user_id
left join (select follow_id from Table_followers where user_id = 1) following
on u.id = following.follow_id
where u.id <> 1 and (follow_id or user_id)
order by `match` desc, follower desc, following desc, u.id;
I'm sure there are more efficient and cleaner ways to do this, but it's late and the old brain is only working at half speed ;)
Sample SQL Fiddle
With MySQL the select part can be further reduced to this:
select
u.id,
u.name,
ifnull((follow_id and user_id),0) as `match`,
(follow_id and user_id is null) as follower,
(user_id and follow_id is null) as following
from Table_users u
But this would give you 0 instead of null for the missing values. (Sample).

Complex MySQL query for specific problemm

I have searched and gone through the available topics similar to mine. But, failed to find that satisfies my requirements. Hence, posting it here.
I have four tables as follows:
"Organization" table:
--------------------------------
| org_id | org_name |
| 1 | A |
| 2 | B |
| 3 | C |
"Members" table:
----------------------------------------------
| mem_id | mem_name | org_id |
| 1 | mem1 | 1 |
| 2 | mem2 | 1 |
| 3 | mem3 | 2 |
| 4 | mem4 | 3 |
"Resource" table:
--------------------------------
| res_id | res_name | res_prop |
| 1 | resource1 | prop-1 |
| 2 | resource2 | prop-2 |
| 3 | resource3 | prop-3 |
| 4 | resource4 | prop-4 |
| 5 | resource1 | prop-5 |
| 6 | resource2 | prop-6 |
A constraint of UNIQUE INDEX (res_name, res_prop) is applied in the above table.
"member-resource" table:
--------------------------------------------
| sl_no | mem_id | res_id |
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 1 |
| 4 | 4 | 3 |
| 5 | 3 | 4 |
| 6 | 2 | 3 |
| 7 | 4 | 3 |
| 8 | 1 | 5 |
| 9 | 1 | 6 |
I want to find out the distinct res_name from Resource table that have more than one res_prop for a specific organization. For example, expected output for organization A would be as follows:
| res_name | res_prop_count |
| resource1 | 2 |
| resource2 | 2 |
Any help in this regard will highly be appreciated.
Regards.
When doing things like this you should build your query up logically, so to start get all your resources and props from organisation A
SELECT r.res_name,
r.res_prop
FROM Resource r
INNER JOIN `member-resource` mr
ON mr.res_id = r.res_id
INNER JOIN Members m
ON m.mem_id = mr.mem_id
INNER JOIN Organization o
ON o.org_id = m.org_id
WHERE o.org_name = 'A'
Then you can start thinking about how you want to filter it, so you want to find resource names that have more than one different res_prop, so you need to group by res_name, and apply a HAVING clause to limit it to res_names with more than one distinct res_prop:
SELECT r.res_name,
COUNT(DISTINCT r.res_prop) AS res_prop_count
FROM Resource r
INNER JOIN `member-resource` mr
ON mr.res_id = r.res_id
INNER JOIN Members m
ON m.mem_id = mr.mem_id
INNER JOIN Organization o
ON o.org_id = m.org_id
WHERE o.org_name = 'A'
GROUP BY r.res_name
HAVING COUNT(DISTINCT r.res_prop) > 1;
Example on SQL Fiddle
I'm not entirely sure I understand what you are looking for, but I think this should work:
SELECT Resource.res_name,
COUNT(DISTINCT Resource.res_prop) AS res_prop_count
FROM Resource
INNER JOIN member_resource
USING (res_id)
INNER JOIN Members
USING (mem_id)
INNER JOIN Organization
USING (org_id)
WHERE Organization.org_name = 'A'
GROUP BY res_name
HAVING res_prop_count > 1;
try this:
select
res_name, count(res_prop) as res_prop_count
from
Resource
where
res_id in (select
res_id
from
member_resource
where
mem_id in (select
mem_id
from
Members
where
org_id = (select
org_id
from
Organization
where
org_name = 'A'))) group by res_name
Try this:
If you have Organization ID then use the query below:
SELECT r.res_name, COUNT(DISTINCT r.res_prop) res_prop_count
FROM member-resource mr
INNER JOIN Resource r ON mr.res_id = r.res_id
INNER JOIN Members m ON mr.mem_id = r.mem_id
WHERE m.org_id = 1
GROUP BY r.res_id HAVING res_prop_count > 1;
If you have Organization name then use the query below:
SELECT r.res_name, COUNT(DISTINCT r.res_prop) res_prop_count
FROM member-resource mr
INNER JOIN Resource r ON mr.res_id = r.res_id
INNER JOIN Members m ON mr.mem_id = r.mem_id
INNER JOIN Organization o ON m.org_id = o.org_id
WHERE m.org_name = 'A'
GROUP BY r.res_id HAVING res_prop_count > 1

How must the mysql query be like to achieve the shown result?

Say, I have two tables like these:
Table group Table user
+----+-----------+ +----+----------+------+----------+
| id | groupname | | id | username | rank | group_id |
+----+-----------+ +----+----------+------+----------+
| 1 | Friends | | 1 | Frank | 1 | 1 |
| 2 | Family | | 2 | Mike | 3 | 1 |
+----+-----------+ | 3 | Steve | 2 | 1 |
| 4 | Tom | 1 | 2 |
+----+----------+------+----------+
And I want to select all groups and get the user with the highest rank (the highest number) for each group. So basically I want to get this result:
+-----------------+----------+---------+---------------+
| group.groupname | group.id | user.id | user.username |
+-----------------+----------+---------+---------------+
| Friends | 1 | 2 | Mike |
| Family | 2 | 4 | Tom |
+-----------------+----------+---------+---------------+
How has the select to be like?
It maybe very simple, but I'm not getting it right now....
Edit 2:
My previous answer was wrong, the max() call destroyed the result. Here's a correct solution:
SELECT g.groupname, g.id AS group_id, u.id AS user_id, u.username
FROM `user` u
LEFT JOIN `group` g ON (u.group_id=g.id)
WHERE u.rank=(
SELECT MAX(rank)
FROM `user` u2
WHERE u.group_id=u2.group_id
)
The check in the WHERE clause should be more understandable, too.
mysql> SELECT g.groupname, g.id AS group_id, u.id AS user_id, u.username
-> FROM `user` u
-> LEFT JOIN `group` g ON (u.group_id=g.id)
-> WHERE u.rank=(
-> SELECT MAX(rank)
-> FROM `user` u2
-> WHERE u.group_id=u2.group_id
-> );
+-----------+----------+---------+----------+
| groupname | group_id | user_id | username |
+-----------+----------+---------+----------+
| Friends | 1 | 2 | Mike |
| Family | 2 | 4 | Tom |
+-----------+----------+---------+----------+
2 rows in set (0.00 sec)
select g.groupname, u.group_id, u.id as user_id, u.username
from group g
inner join (
select group_id, max(rank) as MaxRank
from user
group by group_id
) um on g.id = um.group_id
inner join user u on um.group_id = u.group_id and um.MaxRank = u.rank