Mysql record count from related tables - mysql

I'm trying to get a records count from related tables. I am able to achieve the result I need by breaking the queries and merging the arrays but I know it's inefficient.
I am looking for a cleaner and efficient way of doing this.
All tables are one-to-many. Example:
-One User has many Objects
-One Object has many Items
Table Users
______________
| ID | UID |
______________
| 1 | U1 |
| 2 | U2 |
______________
Table Objects
______________
| ObjID | UID |
______________
| o1 | U1 |
| o2 | U1 |
| o3 | U1 |
______________
Table Items
_________________
| itemID | ObjID |
_________________
| i1 | o1 |
| i2 | o1 |
| i3 | o1 |
| i4 | o1 |
| i5 | o1 |
| i6 | o2 |
_________________
The result I am looking for for U1 is:
| Objects | Items |
| 3 | 6 |
This sql is where I got stuck:
select count(objects.id), count(items.id)
from users
left join Objects on objects.uid = users.uid
left join Items on items.objID = objects.objID
where users.uid = 'U1'

Consider the following:
create table users
(user_id serial primary key,name char(2) unique
);
insert into users values (101,'U1'),(102,'U2');
create table user_objects
(user_id int not null
,object_id int not null
,primary key(user_id,object_id)
);
insert into user_objects values
(101,1),
(101,2),
(101,3);
create table object_items
(object_id int not null
,item_id int not null
,primary key(object_id,item_id)
);
insert into object_items values
(1,1001),
(1,1002),
(1,1003),
(1,1004),
(1,1005),
(2,1001);
select u.name
, count(distinct uo.object_id) objects
, count(oi.item_id) items
from users u
left
join user_objects uo
on uo.user_id = u.user_id
left
join object_items oi
on oi.object_id = uo.object_id
group
by u.user_id;
http://sqlfiddle.com/#!9/d2285/1

Try this: though i am not sure about your item count
select count(objects.id), count(distinct items.id)
from Items inner join Objects on objects.ObjID= users.ObjID
inner join Items on users.uid= objects.uid
where users.uid = 'U1'

One quick way to get the desired result is to use correlated subqueries:
select (select count(*)
from Objects
where UID = 'U1') as Objects,
(select count(*)
from Items as i
inner join Objects as o on i.ObjID = o.ObjID
where o.UID = 'U1') as Items
Another way is to create a derived table of items per object and join to this table to get the total items per user:
SELECT COUNT(*) AS Objects, SUM(cnt_per_oID) AS Items
FROM Users AS u
JOIN Objects AS o ON u.UID = o.UID
LEFT JOIN
(
SELECT ObjID, COUNT(*) cnt_per_oID
FROM Items
GROUP BY ObjID
) AS i ON o.ObjID = i.ObjID
WHERE u.UID = 'U1'
GROUP BY u.UID
Demo here

Try this:
select count(distinct ObjId) Objects,
count(distinct ItemId) Items
from Items i
where exists(select 1 from Objects
where ObjId = i.ObjId and UID = 'U1');

Related

how to get count items that not exists for user with SQL

I have table user_item
+----+---------+---------+
| id | user_id | item_id |
+----+---------+---------+
| 1 | 1 | 1 |
| 2 | 1 | 3 |
| 3 | 2 | 1 |
| 4 | 2 | 2 |
| 5 | 3 | 2 |
+----+---------+---------+
Is it possible to get for each user (except user_id 1) number of items that user_id 1 has and other users don't. The desired output should be:
+---------+-------+
| user_id | count |
+---------+-------+
| 2 | 1 |
| 3 | 2 |
+---------+-------+
Thanks.
Building on Gordon Linoff's answer, here's my take:
select
u.id,
count(*) - count(ui.item_id) cnt
from
users u
join user_items ui1 on ui.id=1
left join user_items ui on ui.user_id=u.id and ui.item_id=ui1.item_id
where
u.id <>1
group by
u.id
We start by taking each user except the one with id=1. Then we multiply each row by each item for user with id=1. Then to each of the resulting rows we try to join the row for the same item of the other user. Then we group them together and count. The total count(*) will always be the number of items that user with id=1 has. The count(ui.item_id) will be the count of items that both users have overlapping. And the difference is the count of items that user with id=1 has that the other user doesn't have.
Hmmmm . . . This is tricky. Let's start by getting the count that match user 1. Assuming user/item pairs are not duplicated:
select ui.user_id, count(ui1.item_id) as match_user_1
from user_items ui left join
user_items ui1
on ui1.item_id = ui.item_id and
ui1.user_id = 1
group by ui.user_id;
Now, let's subtract from the total number of items that the user has:
select ui.user_id, count(*) - count(ui1.item_id) as not_match_user_1
from user_items ui left join
user_items ui1
on ui1.item_id = ui.item_id and
ui1.user_id = 1
group by ui.user_id;
EDIT:
For the reverse, it is pretty much the same idea, but you need to subtract the matches from the total for user 1:
select ui.user_id, count(ui1.item_id) as match_user_1,
uuix.cnt - count(ui1.item_id) as not_match_user_1
from user_items ui cross join
(select count(*) as cnt
from user_items
where user_id = 1
) ui1x left join
user_items ui1
on ui1.item_id = ui.item_id and
ui1.user_id = 1
group by ui.user_id;
Creating the table and populating the table with sample data:
CREATE TABLE user_item
(
id int PRIMARY KEY,
user_id int,
item_id int
);
INSERT INTO user_item VALUES (1,1,1),(2,1,3),(3,2,1),(4,2,2),(5,3,2);
The below query displays the number of items of user_id 1 that other users don't have.
SELECT ui.user_id, (select count(item_id) - count(ui1.item_id) from user_item where user_id = 1) as count
FROM user_item UI
LEFT JOIN user_item ui1 ON ui1.item_id = ui.item_id AND ui1.user_id = 1
WHERE ui.user_id <> 1
GROUP BY ui.user_id
ORDER BY ui.user_id;
Output:
+---------+-------+
| user_id | count |
+---------+-------+
| 2 | 1 |
+---------+-------+
| 3 | 2 |
+---------+-------+
you can try this too..
select u2.user_id, (T.counter - COALESCE(SUM(u1.item_id), 0))counter
From user_item u2
LEFT OUTER JOIN (
SELECT user_id, item_id
FROM user_item
WHERE user_id=1
)u1 ON u2.item_id=u1.item_id
LEFT OUTER JOIN (
SELECT user_id, COUNT(1)counter FROM user_item where user_id=1 group by user_id
)T ON u2.user_id != T.user_id
where u2.user_id!=1
Group by u2.user_id,T.counter

MySql query code to fetch unique data from single ID

How do i List the CUSTNUMs and NAMES of any customer who has only ordered chemical [NUMBER].
ORDERS TABLE
+---------+--------+------------+------+
| CUSTNUM | CHEMNO | DATE | QTY |
+---------+--------+------------+------+
| 123456 | 1234 | 2000-00-00 | 35 |
+---------+--------+------------+------+
CUSTOMER TABLE
+---------+-----------+-----------+
| CUSTNUM | NAME | LOCATION |
+---------+-----------+-----------+
| 123456 | AmChem | New York |
+---------+-----------+-----------+
You could join the CUSTOMER and ORDERS tables containing orders for a particular <chemno> with a subquery for the custnum that buy only a product:
SELECT
CUSTNUM, NAME
FROM
CUSTOMER c
INNER JOIN
ORDERS o ON o.CUSTNUM = c.CUSTNUM and o.CHEMNO = <chemno>
INNER JOIN
( SELECT
CUSTNUM
FROM
ORDERS
GROUP BY
CUSTNUM
HAVING
COUNT(DISTINCT CHEMNO) = 1 ) t ON t.CUSTNUM = o.CUSTNUM
I will approach this with one join between both tables, then grouping by the column CUSTNUM of the ORDERS table and finally adding the required conditions on the HAVING clause, like this:
SELECT
o.CUSTNUM,
c.NAME
FROM
ORDERS AS o
INNER JOIN
CUSTOMER AS c ON c.CUSTNUM = o.CUSTNUM
GROUP BY
o.CUSTNUM
HAVING
( COUNT(DISTINCT o.CHEMNO) = 1 AND MIN(o.CHEMNO) = <some_chemno> )
OK, slow day...
SELECT DISTINCT x.custnum
FROM orders x
LEFT
JOIN orders y
ON y.custnum = x.custnum
AND y.chemno <> x.chemno
WHERE x.chemno = 9377
AND y.order_id IS NULL;
The rest of this task has been left as an exercise for the reader

MySQL: refind groups of user from group description

I have a problem to find the group membership for users. (yes it's not very clear like that)
For example:
I have 2 table:
- one contains a user list with their permission:
userId | permission
-------|-----------
1 | build
1 | play
1 | jump
2 | build
2 | jump
2 | run
3 | drink
3 | build
4 | run
-the second table contain the group and him permisson:
groupId | permission
--------|-----------
G1 | build
G1 | jump
G2 | play
G2 | jump
G3 | drink
G3 | run
G4 | drink
G5 | build
My goal is to find all the groups that the user can have:
userId | groupId
-------|-----------
1 | G1
1 | G2
1 | G5
2 | G1
2 | G5
3 | G4
3 | G5
I have created a request to find which users belong to the group but I can not do this for all my groups (I have more than 1000 group in my datasets):
SELECT DISTINCT userId
FROM (
SELECT userId, count(*) AS nbData
from table_a
WHERE permission in (
SELECT permission
from table_b
where groupId = 'g1'
)
group by userId
) as t
where nbData = (SELECT count(*) from table_b where groupId = 'g1');
An user belongs to a group if he has all the permission of the group. And the goal is to find every group of each user
If you don't have duplicates in either table, you can join the tables together on permissions and aggregate:
select up.userId, gp.groupId
from user_permissions up join
group_permissions gp
on up.permission = gp.permission
group by up.userId, gp.groupId
having count(*) = (select count(*)
from group_permissions gp2
where gp2.groupId = gp.groupId
);
The user is in the group if all the permissions for the group match.
Something like this might work:
SELECT DISTINCT
a.userId,
b.groupId
FROM
table_a a
JOIN table_b b
ON a.permission = b.permission
E.g.:
DROP TABLE IF EXISTS user_permissions;
CREATE TABLE user_permissions
(user_id INT NOT NULL
,permission VARCHAR(12) NOT NULL
,PRIMARY KEY(user_id,permission)
);
INSERT INTO user_permissions VALUES
(1,'build'),
(1,'play'),
(1,'jump'),
(2,'build'),
(2,'jump'),
(2,'run'),
(3,'drink'),
(3,'build'),
(4,'run');
DROP TABLE IF EXISTS group_permissions;
CREATE TABLE group_permissions
(group_id INT NOT NULL
,permission VARCHAR(12) NOT NULL
,PRIMARY KEY(group_id,permission)
);
INSERT INTO group_permissions VALUES
(101,'build'),
(101,'jump'),
(102,'play'),
(102,'jump'),
(103,'drink'),
(103,'run'),
(104,'drink'),
(105,'build');
SELECT DISTINCT u.user_id
, g.group_id
FROM user_permissions u
JOIN group_permissions g
ON g.permission = u.permission -- groups that users potentially belong to
LEFT
JOIN
( SELECT DISTINCT x.user_id
, y.group_id
FROM user_permissions x
LEFT
JOIN group_permissions y
ON y.permission = x.permission
LEFT
JOIN group_permissions z
ON z.group_id = y.group_id
LEFT
JOIN user_permissions a
ON a.user_id = x.user_id
AND a.permission = z.permission
WHERE a.user_id IS NULL
) n -- groups that users don't belong to (this could be much simpler ;-) )
ON n.user_id = u.user_id
AND n.group_id = g.group_id
WHERE n.user_id IS NULL;
+---------+----------+
| user_id | group_id |
+---------+----------+
| 1 | 101 |
| 1 | 105 |
| 1 | 102 |
| 2 | 101 |
| 2 | 105 |
| 3 | 105 |
| 3 | 104 |
+---------+----------+

mysql multi join from same table give alias

currently i have the following query
SELECT * FROM tabs
JOIN users d ON tabs.`debit` = d.id
JOIN users c ON tabs.`credit` = c.id
as the table contains two user objects the names that get returned are the same like so:
id | amount | type | id | username | avatar | id | username | avatar
i need it to return as the following
id | amount | type | debit.id | debit.username | debit.avatar | credit.id | credit.username | credit.avatar
or something simmilar as long as the column names from the users are prefixed.
I think this is what you are looking for. Give it a try. (Assuming that id | amount | type belongs to the tabs table)
SELECT t.id,
t.amount,
t.type,
d.id as 'debit.id',
d.username as 'debit.username',
d.avatar as 'debit.avatar',
c.id as 'credit.id',
c.username as 'credit.username',
c.avatar as 'credit.avatar',
FROM tabs t
JOIN users d ON t.`debit` = d.id
JOIN users c ON t.`credit` = c.id

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