MySQL - joins with SUM() on mixed colmns - mysql

My tables structure is as follows:
units {id, owner_id}
contracts {id, rent, unit_id}
rents {paid, contract_id}
What i need, is the sum of two things: all rents.paid and the sum of contracts.rent.
My query is:
SELECT
Sum(Rent.paid) AS Rent__summed_rents
FROM
rents AS Rent
LEFT JOIN contracts
ON contracts.id = Rent.id
LEFT JOIN units
ON contracts.unit_id = units.id AND units.owner_id = 29
I tried to add contracts.rent * Rent.paid AS Rent__summed_expected_rents, but it didn't work.
edit
Each contract has a column rent, which contains the payment expected for each month.
Each rent record has a column paid which contains the actual payment made.
The query should get all the paid records and sum them (summed_rents), it also should tell me what was the expected income (which is contracts.rent * rents.paid)
* edit #2 *
rents data:
id | paid | contract_id
1 | 200 | 6
1 | 300 | 6
1 | 500 | 4
1 | 200 | 4
contracts data:
id | rent | unit_id
6 | 500 | 22
4 | 600 | 22
units data:
id | owner_id
22 | 29
What i expect is:
summed rents = 1,200 (sum of all paid)
expected rents = 2,200 = (500 * 2) + (600 *2) (sum of contract.rent, one for each rents record)

Here's how I'd build the query:
start with units for an owner. simple enough:
SELECT u.*
FROM units u
WHERE u.owner_id = 29
next step, get the contracts on those units:
SELECT c.*
, u.*
FROM units u
JOIN contracts c
ON c.unit_id = u.id
WHERE u.owner_id = 29
next step, get the rents paid on those contracts
SELECT r.*
, c.*
, u.*
FROM units u
JOIN contracts c
ON c.unit_id = u.id
JOIN rents r
ON r.contract_id = c.id
WHERE u.owner_id = 29
-- aggregate the rows from that result by contracts id (per the specification, we need the count from rents by contract number, to multiply by the contracts rent amount)
SELECT SUM(r.paid) AS total_paid
, SUM(c.rent) AS total_rent
FROM units u
JOIN contracts c
ON c.unit_id = u.id
JOIN rents r
ON r.contract_id = c.id
WHERE u.owner_id = 29
GROUP BY c.id
-- get grand totals by wrapping that result as an inline view, and aggregating the total_paid and total rent
SELECT SUM(t.total_paid) AS total_paid
, SUM(t.total_rent) AS total_rent
FROM (
SELECT SUM(r.paid) AS total_paid
, SUM(c.rent) AS total_rent
FROM units u
JOIN contracts c
ON c.unit_id = u.id
JOIN rents r
ON r.contract_id = c.id
WHERE u.owner_id = 29
GROUP BY c.id
) t
NOTE: counting the number of rows in rents for a given contract seems an odd way to get the multiplier for rent. But if that's the specification, we make sure our code does that.
We'd only need outer joins to generate "zero" paid and "zero" rent. If we introduce a LEFT JOIN to the rents table, then we'd need to adjust the expression to get total_rent. We'd want to use something like:
c.rent*COUNT(r.contract_id) AS total_rent
Something like this:
SELECT IFNULL(SUM(t.total_paid),0) AS total_paid
, IFNULL(SUM(t.total_rent),0) AS total_rent
FROM (
SELECT SUM(r.paid) AS total_paid
, c.rent*COUNT(r.contract_id) AS total_rent
FROM units u
JOIN contracts c
ON c.unit_id = u.id
LEFT
JOIN rents r
ON r.contract_id = c.id
WHERE u.owner_id = 29
GROUP BY c.id
) t

It's as easy as:
select sum(r.paid) as rent_paid,
sum(c.rent) as expected_rent
from rents r
join contracts c on (r.contract_id = c.id)
join units u on (c.unit_id = u.id)
where u.owner_id = 29;
As seen on sqlfiddle

Related

Sum the result of a SELECT that returns several rows

i am trying to sum the result of a select that return several rows:
i have four tables (user, team, pilot, result):
user (id_user)
team (id_user, id_team)
pilot (id_pilot, id_team)
result (id_pilot, id_gp, points)
When i do that:
SELECT p.id_team, SUM( r.points ) AS TOTAL
FROM pilot p
LEFT JOIN result r ON p.id_pilot = r.id_pilot
WHERE r.id_grand_prix = '1'
GROUP BY p.id_team
i get:
id_team TOTAL
-------------
1 10
2 15
3 5
4 6
5 7
what i want is, if id_user has id_team 1 and 2 has 25 points.
i try that:
UPDATE user AS u
JOIN has_team ON u.id_user = has_team.id_user
JOIN (
SELECT p.id_team, SUM( r.position ) AS TOTAL
FROM pilot p
LEFT JOIN result r ON p.id_pilot = r.id_pilot
WHERE r.id_grand_prix = '1'
GROUP BY p.id_team
) AS grp
ON grp.id_team = has_team.id_team
SET u.points = grp.TOTAL + u.points
but i only have the points of one team.
thanx very much in advance how can help me.
Are you looking to get the SUM of points for each user, instead of each team?
If so, your query might be something like this:
SELECT u.id_user, SUM( r.points ) AS TOTAL
FROM pilot p
LEFT JOIN result r ON p.id_pilot = r.id_pilot
INNER JOIN team t ON p.id_team = t.id_team
INNER JOIN user u ON t.id_user = u.id_user
WHERE r.id_grand_prix = '1'
GROUP BY u.id_user
If not, could you clarify what you need to display?

SQL Union query simplification

I don't know if this question is apropriate on this forum.
I have a huge query :
SELECT threshold.id, brand.id, COUNT(brand.id), threshold
FROM current_stock, article, product, brand, delivery, threshold
WHERE current_stock.article_id = article.id
AND article.product_product_code = product.product_code
AND product.brand_id = brand.id
AND article.delivery_id = delivery.id
AND delivery.store_id = 'E260'
AND threshold.brand_id = brand.id
GROUP BY brand.id
HAVING COUNT(brand.id) <= threshold
UNION
SELECT threshold.id, brand.id, 0, threshold
FROM current_stock, article, product, brand, delivery, threshold
WHERE threshold.store_id = 'E260'
AND threshold.brand_id NOT IN (
SELECT brand_id FROM current_stock, article, product, delivery
WHERE current_stock.article_id = article.id
AND article.product_product_code = product.product_code
AND article.delivery_id = delivery.id
AND delivery.store_id = 'E260')
And I think it's possible to do better but after a entire day of try I haven't found a better query giving the same result.
For clarify, I have a stock (with current_stock, article, product and delivery). I also have thresholds. what I want is to check for each thresholds if there is the given minimum amount of stock for the given brand.
My problem is that if there is 0 article of a brand, the first part of the query will not take care about the threshold on this brand. It's why I have added an uggly Union.
Someone have an idea for a better way to do this ?
EDIT
This what I have done after the reading of comments and answers :
SELECT t.id, b.id, t.threshold, count(b.id) stock
FROM threshold t
inner join brand b on b.id = t.brand_id
left join product p on p.brand_id = b.id
inner join article a on a.product_product_code = p.product_code
inner join delivery d on d.id = a.delivery_id
inner join current_stock cs on cs.article_id = a.id
WHERE
t.store_id = 'E260' AND
d.store_id = 'E260'
GROUP BY b.id
HAVING stock <= t.threshold
My problem is that it don't gives all threholds... only ones that have at least one 'current_stock'. I have perhaps don't understand how joins are working.
Here an example of threshold table :
| id | brand_id | threshold |
-----------------------------
| 1 | 86 | 1 |
| 2 | 28 | 1 |
| 3 | 12 | 1 |
What I want as result this :
# with 2 entries in 'current_stock' for the brand id 28, 1 for 12 and 0 for 86
| t.id | b.id | threshold | stock |
-----------------------------------
| 1 | 86 | 1 | 0 |
| 3 | 12 | 1 | 1 |
Guessing a few parts here since you've used implicit joins in your sample. An explicit version would look something like this (provided I guessed correctly for how you are joining the threshold table).
SELECT
t.id,
b.id,
COUNT(b.id),
t.threshold
FROM
current_stock c
inner join article a on a.id = c.article_id
inner join delivery d on d.id = a.delivery_id
inner join product p on p.product_code = a.product_product_code
inner join brand b on b.id = p.brand_id
inner join threshold t on t.brand_id = b.id
WHERE
d.store_id = 'E260'
GROUP BY b.id
HAVING COUNT(b.id) <= t.threshold
Now to get your results to include rows where there aren't any 'articles' you can start switching out the inner joins for left joins. However, you can't simply use left outer join article... in the example above, because the store_id in the WHERE clause will just turn it back into a pseudo inner join.
Instead, is there a different field you can join the delivery table on from current_stock?
EDIT - 07/29/15
I think you're close, you may just have one too many filters and you're counting from 'b' when the wanted outcome suggests you should be counting from 'cs' instead. Try this:
SELECT t.id, b.id, t.threshold, count(cs.id) stock
FROM
threshold t
inner join brand b on b.id = t.brand_id
inner join product p on p.brand_id = b.id
inner join article a on a.product_product_code = p.product_code
left outer join delivery d on d.store_id = t.store_id
left outer join current_stock cs on cs.article_id = a.id
WHERE
t.store_id = 'E260'
GROUP BY b.id
HAVING stock <= t.threshold

Intersection in mysql for multiple values

The intersect keyword is not available in mysql. I want to know how to implement the following in mysql db. My tables are:
customer(cid,city,name,state)
orders(cid,oid,date)
product(pid,price,productname)
lineitem(lid,pid,oid,totalquantity,totalprice)
I want the products bought by all the customers of a particular city 'X'. i.e. every customer in city 'x' should have bought the product. I managed to select the oid's and the pid's of customers living in that particular city. Now I should select the pid's which is present in all the oid's.
Example.
Oid Pid
2400 1
2400 2
2401 3
2401 1
2402 1
2403 1
2403 3
The answer from the above input should be 1 because it is present in all oid's. The query which I used to get the oid's and pid's:
select t.oid,l.pid
from lineitem l
join (select o.oid,c1.cid
from orders o
join (select c.cid
from customer c
where c.city='X') c1
where o.cid=c1.cid) t on l.oid=t.oid
Now I need to intersect all the oid's and get the result.The query should not be dependent on data.
Try:
select pid, count(*)
from (select t.oid, l.pid
from lineitem l
join (select o.oid, c1.cid
from orders o
join (select c.cid from customer c where c.city = 'X') c1
where o.cid = c1.cid) t
on l.oid = t.oid) x
group by pid
having count(*) = (select count(*)
from (select distinct oid
from lineitem l
join (select o.oid, c1.cid
from orders o
join (select c.cid
from customer c
where c.city = 'X') c1
where o.cid = c1.cid) t
on l.oid = t.oid) y) z
I think you can achieve what you want by using IN

How to query for suggested books like amazon does?

I'm working on developing a web portal for a bookstore, and I want to have the ability to suggest books to a user.
I want something similar to amazon.com, when a user orders book A, the system should provide a list of other suggested books. Book B is suggested if there exists a user Bob that bought both A and B. Additionally, I want my system to return the suggested books sorted on decreasing sales count, and only count sales to users that have bought both books (like Bob).
Here are the important tables:
Book(ISBN, title, publicationYear, etc..)
Orders(orderID, loginName, date)
BooksOrdered(orderID, ISBN, count)
This query is more complex than anything I've previously tried.
Current thoughts:
First find all the users that have ordered the same book (ISBN)
Join all three tables on Book.ISBN = BooksOrdered.ISBN AND Orders.orderID = BooksOrdered.ISBN
WHERE Book.ISBN = bookInQuestionISBN
GROUP BY Orders.loginName
Project out loginName
So something like:
SELECT Orders.loginName as otherBuyerLoginName
FROM Book, Orders, BooksOrdered,
WHERE Book.ISBN = bookInQuestionISBN AND Orders.orderID = BooksOrdered.ISBN
GROUP BY Orders.loginName
Then I could grab all the books these loginNames have ordered, group them by loginName, sum count and ORDER BY DESC SUM(BooksOrdered.count).
However, I'm thinking that the first result will most likely be the book in question. I don't want to suggest the same book the user has just bought.
What do you suggest? Maybe I should start over from scratch?
EDIT:
Here is some data:
BooksOrdered contains:
orderID ISBN count
3 FakeISBN 3
7 FakeISBN 3
8 FakeISBN 100
11 FakeISBN2 40
7 FakeISBN2 4
10 FakeISBN2 20
10 FakeISBN3 34
11 TesterISBN 3
9 TesterISBN 1
Orders contains:
orderID loginName date
2 Tester 2012-03-15 19:43:27
3 Tester 2012-03-16 15:56:55
6 Tester2 2012-03-16 17:28:02
7 Tester 2012-03-16 17:31:21
8 ni3hao3 2012-03-16 23:18:15
9 ni3hao3 2012-03-17 13:12:38
10 ni3hao3 2012-03-17 13:13:55
11 Bobby 2012-03-17 13:28:14
Alright, now I want to know the top suggestions for the book with ISBN = "TesterISBN"
Two people have ordered "TesterISBN": ni3hao3 and Bobby
ni3hao3's total sales history:
1 copy of "TesterISBN"
100 copies of "FakeISBN"
20 copies of "FakeISBN2"
34 copies of "FakeISBN3"
Bobby's total sales history:
3 copies of "TesterISBN"
40 copies of "FakeISBN2"
So the totals of sales for purchasers of "TesterISBN" are as follows:
4 copies of "TesterISBN"
100 copies of "FakeISBN"
60 copies of "FakeISBN2"
34 copies of "FakeISBN3"
So I'd like the results to return:
FakeISBN
FakeISBN2
FakeISBN3
In that order.
EDIT:
I believe I've figured it out:
SELECT Bo.ISBN, B.title, SUM(Bo.count)
FROM BooksOrdered Bo, Orders O, Book B
WHERE Bo.orderID = O.orderID AND Bo.ISBN = B.ISBN
AND Bo.ISBN != 'TesterISBN'
AND O.loginName IN ( SELECT DISTINCT(Orders.loginName) as otherBuyerLoginName
FROM Orders, BooksOrdered
WHERE BooksOrdered.ISBN = 'TesterISBN'
AND Orders.orderID = BooksOrdered.orderID)
GROUP BY Bo.ISBN
ORDER BY SUM(Bo.count) DESC
I've written this on SQL Server, but converting to MySQL syntax should be trivial.
This query returns recommendations based on books in the orderID in question (#currentOrderID):
select b.ISBN, b.title, sum(bo.count) as ranking
from Book b inner join
BooksOrdered bo on b.ISBN = bo.ISBN inner join
( select distinct orderID
from BooksOrdered bo
where bo.ISBN in ( select ISBN
from BooksOrdered bo1
where bo1.orderID = #currentOrderID )
) o on bo.orderID = o.orderID
where b.ISBN not in ( select ISBN
from BooksOrdered bo1
where bo1.orderID = #currentOrderID )
group by b.ISBN, b.title
order by sum(bo.count) desc
This one returns recommendations based on the entire order history of the login that made the current order:
select b.ISBN, b.title, sum(bo.count) as ranking
from Book b inner join
BooksOrdered bo on b.ISBN = bo.ISBN inner join
( select bo.orderID
from BooksOrdered bo
where bo.ISBN in ( select ISBN
from BooksOrdered bo1
where bo1.orderID = #currentOrderID )
) o on bo.orderID = o.orderID
where b.ISBN not in ( select ISBN
from Orders o inner join
BooksOrdered bo on o.orderID = bo.orderID
where o.loginName = ( select loginName
from Orders
where orderID = #currentOrderID ) )
group by b.ISBN, b.title
order by sum(bo.count) desc
Hope these give you what you're looking for!
It seems to me that you can approach this in two steps:
Find which users bought the book in question and get the ids of all their orders
Group all other books in those orders by ISBN and count number of occurrences (i.e. number of orders they have been bought together with the book in question)
It would translate to something like this:
SELECT bo.ISBN, COUNT(*) AS timesBoughtTogether
FROM (SELECT DISTINCT(o.orderID) FROM Orders o
LEFT JOIN BooksOrdered bo ON o.orderID = bo.orderID
WHERE bo.ISBN = 'ISBN to provide suggestions for') relevantOrders
LEFT JOIN BooksOrdered bo ON relevantOrders.orderID = bo.orderID
WHERE bo.ISBN != 'ISBN to provide suggestions for'
GROYP BY bo.ISBN
ORDER BY timesBoughtTogether DESC
I didn't actually run this, so I hope the syntax isn't off.
SELECT Bo.ISBN, B.title, SUM(Bo.count)
FROM BooksOrdered Bo, Orders O, Book B
WHERE Bo.orderID = O.orderID AND Bo.ISBN = B.ISBN
AND Bo.ISBN != 'TesterISBN'
AND O.loginName IN ( SELECT DISTINCT(Orders.loginName) as otherBuyerLoginName
FROM Orders, BooksOrdered
WHERE BooksOrdered.ISBN = 'TesterISBN'
AND Orders.orderID = BooksOrdered.orderID)
GROUP BY Bo.ISBN
ORDER BY SUM(Bo.count) DESC
This seems to do the trick

Mysql queries issue

I have 3 tables in my mysql DB to query.
Users_rates (fields: id,userid,raterid,rate,eventid) containing all of the rates(rate) that have been assigned to users(userid), participating to specific events(eventid), by other users(raterid)
Events_participants (fields:id,userid,eventid) containing all of the users(userid) participating to each event(eventid)
Users (fields:id,name,lastname)containing all the user relative data
I need to query those three tables to retrieve an event-specific rank for the users' rates.
Ex. John,Erik and Mark participated to 'eventid=31'.
John received 1 rate from Mark, and 2 from Erik.
Mark received 1 rate from Erik.
Nobody has rated Erik though.
I need to retrieve for each user name,lastname and the sum of the rates received for eventid=31
I tried with this:
SELECT events_participants.userid,users.name,users.lastname,
(SELECT SUM(rate)FROM users_rates WHERE users_rates.eventid=31 AND users_rates.userid=events_participants.userid)AS rate
FROM(( events_participants INNER JOIN users ON events_participants.userid=users.id)
LEFT OUTER JOIN users_rates ON events_participants.userid=users_rates.userid )
WHERE events_participants.eventid=31
But I receive:
userid | name | lastname | rate
1 | luca | silvestro | 1
3 | claudio | buricchi | 6
3 | claudio | buricchi | 6
What's the right query?
Thanks
Luca
Try this:
SELECT users.userid, users.name, users.lastname, temp.sum as rate
FROM users LEFT JOIN (
SELECT userid, SUM(rate) as sum FROM users_rates WHERE eventid = 31 GROUP BY userid
) as temp USING (userid)
It might give an error, this might work instead:
SELECT users.userid, users.name, users.lastname, temp.sum as rate
FROM users, (
SELECT userid, SUM(rate) as sum FROM users_rates WHERE eventid = 31 GROUP BY userid
) as temp WHERE users.userid = temp.userid
I don't know if I got the problem right, but maybe something like:
SELECT u.id, u.name, u.lastname, SUM(ur.rate) AS rate
FROM users AS u
INNER JOIN users_rates AS ur ON ur.userid = u.id
WHERE ur.eventid = 31
GROUP BY u.id
edit: If you want to receive a list with all users regardless of whether they have any rates at all, you could also join the users_participants table and replace the INNER JOIN of users_rates by a LEFT JOIN. The WHERE clause has to reference events_participants then (not users_rates anymore as it could be NULL):
SELECT u.id, u.name, u.lastname, SUM(ur.rate) AS rate
FROM users AS u
INNER JOIN events_participants AS ep ON ep.userid = u.id
LEFT JOIN users_rates AS ur ON ur.userid = u.id AND ur.eventid = ep.eventid
WHERE ep.eventid = 31
GROUP BY u.id