MYSQL SUM() in one table and JOIN with 2 other tables - mysql

I have 3 tables: items, purchases, and collaborators. A user can own an item, purchase an item, or be a collaborator on an item. Additionally, items that are purchased can be rated up, +1, or down, -1. An owner or collaborator can't purchase their own item.
I'd like to get all items for a given user and also display the ratings on each item.
Here's my tables:
items | purchases | collaborators
i_id item_id user_id | p_id item_id user_id rating |c_id item_id user_id
1 1 11 | 1 1 13 -1 | 1 1 12
2 2 12 | 2 2 11 1 | 2 2 13
3 3 13 | 3 3 12 NULL |
| 4 1 14 -1 |
Here's my MYSQL query so far:
select *, count(p_id) as tots, sum(rating=1) as yes, sum(rating= '-1') as no
from items
left join purchases
on items.item_id=purchases.item_id
left join collaborators
on items.item_id=collaborators.item_id
where items.user_id=13 or purchases.user_id=13 or collaborators.user_id=13
group by items.item_id
Here's my expected results for user_id=11 (changing each user_id in the WHERE clause):
item_id tots yes no
1 2 0 2
2 1 1 0
// notice how user_id=11 doesn't have anything to do with item_id=3
Here's my expected results for user_id=12:
item_id tots yes no
1 2 0 2
2 1 1 0
3 1 1 0
Here's my expected results for user_id=13:
item_id tots yes no
1 2 0 2
2 1 1 0
3 1 1 0
//notice user_id=13 should have same results as user_id=12. Although, their
relation to each of the 3 items is different, they still either purchased,
own, or collaboratored on each of them.
Unfortunately, I get the first two results but not the correct one for user_id=13.
For user_id=13, item_id=1 the tots=1 and not tots=2 for some reason I can't understand.
Any thoughts, such as, "its better to separate this into 2 queries", would be greatly appreciated,

I'm still not entirly sure I understand you correct but you could try following statement and let us work from there.
Edit
Following statement returns the expected results.
You can verify this (using SQL Server) here.
The gist of this is to
select all possible user_id and item_id combinations from your three tables
select the counts/ratings for each item
combine the results
SQL Statement
SELECT u.user_id, pt.item_id, pt.cnt, pt.yes, pt.no
FROM (
SELECT user_id, item_id, title FROM items
UNION SELECT user_id, item_id, NULL FROM purchases
UNION SELECT user_id, item_id, NULL FROM collaborators
) u INNER JOIN (
SELECT COUNT(*) AS cnt
, SUM(CASE WHEN ISNULL(rating, 1) = 1 THEN 1 ELSE 0 END) AS yes
, SUM(CASE WHEN rating =-1 THEN 1 ELSE 0 END) AS no
, item_id
FROM purchases
GROUP BY
item_id
) pt ON pt.item_id = u.item_id
MYSQL statement
SELECT u.user_id, pt.item_id, pt.cnt, pt.yes, pt.no, u.title
FROM (
SELECT user_id, item_id, title FROM items where user_id=13
UNION SELECT user_id, item_id, NULL FROM purchases where user_id=13
UNION SELECT user_id, item_id, NULL FROM collaborators where user_id=13
) u INNER JOIN (
SELECT COUNT(*) AS cnt
, SUM(rating=1) AS yes
, SUM(rating =-1) AS no
, item_id
FROM purchases
GROUP BY
item_id
) pt ON pt.item_id = u.item_id

Related

SQL query to only yield multiple occurances

I am using mariadb and I have a table called links:
id | product_id | last_change
------------------------------
1 1 xxx
2 2 xxx
3 5 xxx
4 5 xxx
I want to find every object (3, 4 in this example) that occures more than once. Following this answer I tried:
SELECT product_id, COUNT(*) from links HAVING COUNT(*) > 1
But this results in the (adapted to this example) first row being shown and the total number of product_id occurrences:
product_id | COUNT(*)
---------------------
1 4
I wanted to achieve a list of all items occuring more than once:
id | product_id | last_change
------------------------------
3 5 xxx
4 5 xxx
An aggregation function without GROUP BY always results in only one row result as it aggregates all rows
So use a GROUP BY
SELECT product_id, COUNT(*) from links GROUP BY product_id HAVING COUNT(*) > 1
To see all entry with the count of the product_id , you can do following
SELECT l1.product_id , last_change , Count_
FROM links l1
JOIN (SELECT product_id, COUNT(*) as Count_ from links GROUP BY product_id HAVING COUNT(*) > 1) l2
ON l1.product_id = l2.product_id
Try below statement
select id, product_id, count(product_id)
from links
group by (product_id)
having count(product_id)> 1;

How do I sum amount for specific user in mysql query?

I have three tables I am working with:
users table contains users id, name and status
supply_history table contains id, users_id, amount and status
rejected_deal table contains id, users_id, rejected_amount and also status.
What I am trying to find is to sum up each users supply_amount and rejected amount where users status, supply_history status and rejected_deal status is 1.
Here is what I have tried so far.
SELECT users.id, users.name,
sum(supply_history.amount) as supply_amount,
sum(rejected_deal.rejected_amount) as rejected_amount
from users
inner join supply_history on supply_history.users_id=users.id
inner join rejected_deal on rejected_deal.users_id=users.id
where users.status='1' and rejected_deal.status='1' and supply_history.status='1'
GROUP by users.id;
My current query result is bellow.
id
name
supply_amount
rejected_amount
1
Skipper
50
20
2
Private
200
20
The result I am looking for
id
name
supply_amount
rejected_amount
1
Skipper
50
10
2
Private
100
20
Bellow is my three tables
users table.
id
name
status
1
Skipper
1
2
Private
1
supply_history table.
id
users_id
amount
status
1
1
20
1
2
1
30
1
3
2
100
1
rejected_deal table.
id
users_id
rejected_amount
status
1
2
15
1
2
2
5
1
3
1
10
1
How do I solve this query please help. Thank you.
You are joining supply_history rows and rejected_deal rows to user rows. But supply_history and rejected_deal are not directly related. For one user, you create thus all combinations of supply_history and rejected_deal. The result is:
id
name
status
sh_id
users_id
amount
status
rd_id
users_id
rejected_amount
status
1
Skipper
1
1
1
20
1
2
2
5
1
1
Skipper
1
2
1
30
1
3
1
10
1
2
Private
1
3
2
100
1
1
2
15
1
2
Private
1
3
2
100
1
1
2
15
1
When you aggregate this data set, you add up amounts multifold. (Each value gets multiplied with the number of rows for the user in the other table).
Instead join the sums to the user:
select u.id, u.name, sh.supply_amount, rd.rejected_amount
from users u
left outer join
(
select users_id, sum(amount) as supply_amount
from supply_history
where status = 1
group by users_id
) sh on sh.users_id = u.id
left outer join
(
select users_id, sum(rejected_amount) as rejected_amount
from rejected_deal
where status = 1
group by users_id
) rd on rd.users_id = u.id
where u.status = 1
order by u.id;
I've turned your inner joins into outer joins for the case a user doesn't have entries in both tables. If you only want users that have data in both tables, then change this back to inner joins.
And if you want to enhance the query's readability, you may consider using WITH clauses: https://dev.mysql.com/doc/refman/8.0/en/with.html.
use Over for sum like this
SELECT users.id, users.name,
sum(supply_history.amount) OVER(PARTITION BY users.id, ORDER BY users.id) as supply_amount,
sum(rejected_deal.rejected_amount) OVER(PARTITION BY users.id, ORDER BY users.id) as rejected_amount
from users
inner join supply_history on supply_history.users_id=users.id
inner join rejected_deal on rejected_deal.users_id=users.id
where users.status='1' and rejected_deal.status='1' and supply_history.status='1'
try using sub queries to be something like that :
SELECT users.id, users.name,
(select sum(amount) from supply_history s where s.users_id = users.id and s.status = '1' ) as supply_amount ,
(select sum(rejected_amount) from rejected_deal r where r.users_id = users.id and r.status = '1' ) as rejected_amount
from users
where users.status='1';

MySQL - Display null column from child table if all values are not distinct

I have the following tables, for example:
invoices
ID Name
1 A
2 B
3 C
4 D
5 E
transactions
ID Invoice_ID User_ID
1 1 10
2 1 10
3 1 10
4 2 30
5 3 20
6 3 40
7 2 30
8 2 30
9 4 40
10 3 50
Now I want to make a select that will pull the invoices and the user_id from the related transactions, but of course if I do that I won't get all the ids, since they may be distinct but there will be only one column for that. What I want to do is that if there are distinct User_ids, I will display a pre-defined text in the column instead of the actual result.
select invoices.id, invoices.name, transactions.user_id(if there are distinct user_ids -> return null)
from invoices
left join transactions on invoices.id = transactions.invoice_id
and then this would be the result
ID Name User_ID
1 A 10
2 B 30
3 C null
4 D 40
5 E null
Is this possible?
You can do the following :
select
invoices.id,
invoices.name,
IF (
(SELECT COUNT(DISTINCT user_id) FROM transactions WHERE transactions.invoice_id = invoices.id) = 1,
(SELECT MAX(user_id) FROM transactions WHERE transactions.invoice_id = invoices.id),
null
) AS user_id
from invoices
Or, alternatively, you can use the GROUP_CONCAT function to output a comma-separated list of users for each invoice. It is not exactly what you asked, but maybe in fact it will be more useful :
select
invoices.id,
invoices.name,
GROUP_CONCAT(DISTINCT transactions.user_id SEPARATOR ',') AS user_ids
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
Try somethingh like:
select i.id, i.name, t.user_id
from invoices i left join
(
select invoice_ID, User_ID
from transactions
group by invoice_ID
having count(invoice_ID)=1
) t on i.id=t.invoice_id
SQL fiddle
You could list all the transactions that have multiple user ids, like this:
select invoices.id, invoices.name, null
from invoices
left join transactions on invoices.id = transactions.invoice_id having count(distinct transactions.user_id) > 1
Also, I think this CASE might suit your needs here:
select invoices.id, invoices.name,
case when count(distinct transactions.user_id) > 1 then null else transactions.user_id end
from invoices
left join transactions on invoices.id = transactions.invoice_id
group by invoices.id
although, I'm not sure this is syntactically correct

Group by different subsets of keys

I have a table of products sales
id product_id price_sold
1 1 500
2 1 300
3 2 100
4 3 200
5 3 100
I want to be able to sum the prices by different subsets of products, say: sum of prices per the group of proucts 1,2. and another calculation of sum of prices per the group of products 2,3, so the needed result will be:
group 1, 900
group 2, 400
Can you help with efficient and elegant way to do that?
Doing what you want is a bit challenging, because the groups overlap. You have two options. The first is to do conditional aggregation and put the results in columns:
select sum(case when product_id in (1, 2) then price_sold end) as group1,
sum(case when product_id in (2, 3) then price_sold end) as group2
from productsales ps;
To get the results on separate rows, you could then pivot this result. Or, you could do the calculation directly. For this to work, you need to construct a table describing the groups:
select pg.grpid, sum(ps.price_sold)
from productsales ps
join
(
select 1 as grpid, 1 as product_id
union all
select 1 as grpid, 2 as product_id
union all
select 2 as grpid, 2 as product_id
union all
select 2 as grpid, 3 as product_id
) pg on ps.product_id = pg.product_id
group by pg.grpid;
SQL Fiddle:
SELECT 'GROUP 1' AS `Group`, SUM(price_sold) AS PriceSum
FROM MyTable
WHERE product_id = 1 OR product_id = 2
UNION
SELECT 'GROUP 2', SUM(price_sold)
FROM MyTable
WHERE product_id = 2 OR product_id = 3
The result looks like:

MySQL: Select Order Only if all its Items are ready status

I will simplify the table like this:
table order:
order_id | name
1 a
2 b
3 c
Table order_item:
item_id | fk_order_id | status
1 1 0
2 1 1
3 2 1
4 2 1
5 3 0
Ready status let say is 1, so only order_id=2 has all its items are on ready status.
How can I query select it?
There are a couple ways of doing this -- here is one using COUNT -- gets COUNT of all and compares to COUNT of Status = 1:
SELECT fk_order_id
FROM (
SELECT fk_order_id,
COUNT(1) totCount,
COUNT(CASE WHEN Status = 1 THEN 1 END) statusCount
FROM Order_Item
GROUP BY fk_order_id
) t
WHERE totCount = statusCount
SQL Fiddle Demo
This could be consolidated into a single query, but I think it reads better using a subquery.
Try this:
SELECT order_id ,name FROM order,order_item
WHERE order.order_id =order_item.fk_order_id
group by order_id ,name having min(status)=1