Get SUM of two fields in 2 different related tables - mysql

I'd like to get the SUM of the amount column in two related tables.
Invoices Table:
-----------------------------------------
| id | student_id | created | updated |
-----------------------------------------
| 5 | 25 | date | date |
-----------------------------------------
Invoice Items Table:
------------------------------
| id | invoice_id | amount |
------------------------------
| 1 | 5 | 250 |
------------------------------
| 2 | 5 | 100 |
------------------------------
| 3 | 5 | 40 |
------------------------------
Payments Table:
------------------------------
| id | invoice_id | amount |
------------------------------
| 1 | 5 | 100 |
------------------------------
| 2 | 5 | 290 |
------------------------------
Desired Output:
--------------------------------------
| id | invoiceTotal | paymentTotal |
--------------------------------------
| 1 | 390 | 390 |
--------------------------------------
The query I've tried
SELECT
i.id,
sum(ii.amount) as invoiceTotal,
sum(p.amount) as paymentTotal
FROM
invoices i
LEFT JOIN
invoice_items ii ON i.id = ii.invoice_id
LEFT JOIN
payments p ON i.id = p.invoice_id
WHERE
i.student_id = '25'
GROUP BY
i.id
What this seems to do is calculate the sum of the payments properly but the invoice_items.amount appears to have been duplicated by 6 (which is the number of payments there are).
I have read similar questions on SO here and here but the examples are so much more complex than what I'm trying to do and I can't figure out what to put where.

The join causes a problem with cartesian products. If a student has multiple invoice items and payments, then the totals will be wrong.
One approach that works best for all invoices is a union all/group by approach:
select i.id, sum(invoiceTotal) as invoiceTotal, sum(paymentTotal) as paymentTotal
from ((select i.id, 0 as invoiceTotal, 0 as paymentTotal
from invoices i
) union all
(select ii.invoiceId, sum(ii.amount) as invoiceTotal, NULL
from invoiceitems ii
group by ii.invoiceId
) union all
(select p.invoiceId, 0, sum(p.amount) as paymentTotal
from payments p
group by p.invoiceId
)
) iip
group by id;
For a single student, I would recommend correlated subqueries:
select i.id,
(select sum(ii.amount)
from invoiceitems ii
where ii.invoiceid = i.id
) as totalAmount,
(select sum(p.amount)
from payment p
where p.invoiceid = i.id
) as paymentAmount
from invoices i
where i.studentid = 25;

Related

MySQL query to count user orders by type

I have this 3 tables: users, orders and order_item.
When a user can have a order, and the order item can be event only, membership only or both, and so 2 line will be written to the order_items
users tables
id | user_id | name | phone
---|-----------|-------|------
1 | 123456789 | Jon | 555-55555
2 | 123456780 | Alice | 555-6666
orders tables
id | user_id | user_uid | user_info
---|---------|-----------|----------
1 | 1 | 123456789 | bla
2 | 2 | 123456780 | foo
3 | 2 | 123456780 | foo
order_items table
id | order_id | order_type | price
--- | -------- | ---------- | ------
1 | 1 | membership | 70
2 | 1 | event | 200
3 | 2 | event | 300
4 | 3 | membership | 70
The relationship is like this,
order_items.order_id -> orders.id
orders.user_id -> users.id
orders.user_uid -> users.user_id
I'm looking for a query which will produce this type of output,
user_id | name | count_membership | count_events | total_orders
-------- | ------ | ------------------ | -------------- | --------------
123456789 | Jon | 1 | 1 | 1
123456780 | Alice | 1 | 1 | 2
I like to count the total orders a user have, and count how many of each of item he have. in the end I like to filter out all users where count_membership = 0
Thanks in advance,
You can get the expected result set by using some conditional aggregation
select u.user_id,
u.name,
sum(oi.membership_count) membership_count,
sum(oi.event_count) event_count,
count(o.id) total_orders
from users u
join orders o on u.id = o.user_id
join (
select order_id,
sum(order_type = 'membership') membership_count,
sum(order_type = 'event') event_count
from order_items
group by order_id
) oi on o.id = oi.order_id
group by u.user_id,u.name
Demo
Your query needs to join the three tables on their common fields, which would be user_id and order_id.
Since you said you don't want any results where membership is not an order type, your results example doesn't make sense and the grouping into single results that way using a single mysql query makes things complicated to put order_type which is a single field into two separate columns. So I adjusted slightly.
select users.user_id, users.name, order_type, count(order_id) as total_orders from orders, order_items, users where users.user_id = orders.user_id and orders.id = order_items.order_id and order_type = "membership" group by users.user_id, users.name
working output from mysql terminal:
mysql> select users.user_id, users.name, order_type, count(order_id) as total_orders from orders, order_items, users where users.user_id = orders.user_id and orders.id = order_items.order_id and order_type = "membership" group by users.user_id, users.name;
+---------+------+------------+--------------+
| user_id | name | order_type | total_orders |
+---------+------+------------+--------------+
| 123 | Jon | membership | 1 |
| 1234 | Pam | membership | 2 |
+---------+------+------------+--------------+

Select SUM from multiple tables for every record in MySQL table

I'm having a table with main invoice data, and two table with invoice items:
items which are based on hourly work, with an hourly rate and an amount of hours
items which are products, with a unit count an unit price
For the invoice overview page, I'd like to retrieve all invoices and their total amounts with one query.
A simplified schema
invoices_main
| invoice_id |
| 1 |
| 2 |
| 3 |
invoices_items_products
| item_id | invoice_id | item_count | item_unit_price |
| 1 | 1 | 1 | 999.95 |
| 2 | 1 | 20 | 49.50 |
| 3 | 2 | 3 | 15.00 |
| 4 | 2 | 5 | 5.00 |
| 5 | 3 | 2 | 150.00 |
invoices_items_hourly
| item_id | invoice_id | item_hours | item_hourly_rate |
| 1 | 1 | 3.50 | 90.00 |
| 2 | 1 | 1.00 | 140.00 |
| 3 | 2 | 12.00 | 90.00 |
| 4 | 3 | 1.50 | 90.00 |
With the help of this question, I've constructed the following query:
SELECT
I.invoice_id,
IFNULL(
SUM(ROUND(P.item_unit_price * P.item_count, 2)),
0
) + IFNULL(
SUM(ROUND(H.item_hourly_rate * H.item_hours, 2)),
0
) AS invoice_total_amount
FROM
invoices_main I
LEFT JOIN invoices_items_products P ON I.invoice_id = P.invoice_id
LEFT JOIN invoices_items_hours H ON I.invoice_id = H.invoice_id
GROUP BY
I.invoice_id
It works kind of, but if an invoice has both products and hourly items, with at least multiple entries for one of both, items are duplicated due to the joins and the total amount becomes way too high.
Thus, in the above example schema, it goes wrong with invoice_id 1 and 2, but work with 3.
How can I retrieve a list of invoices with their respective total amounts, in a way that works even if an invoice has multiple products and multiple hourly items?
Try putting both left join's into a subquery instead.
SELECT
I.invoice_id,
IFNULL
(
(
SELECT SUM(ROUND(H.item_hourly_rate * H.item_hours, 2))
FROM invoices_items_hours AS H
WHERE H.invoice_id = I.invoice_id
)
, 0
) +
IFNULL
(
(
SELECT SUM(ROUND(P.item_unit_price * P.item_count, 2))
FROM invoices_items_products AS P
WHERE P.invoice_id = I.invoice_id
)
, 0
) AS invoice_total_amount
FROM invoices_main AS I
GROUP BY I.invoice_id
As mentioned in the comments, you should sum up the revenue in each table per invoice_id before doing the join. If you're looking to get the revenue from both of these places then you can add (B.unit_revenue + C.hourly_revenue) total_revenue to the first SELECT statement below.
SELECT A.invoice_id, B.unit_revenue, C.hourly_revenue FROM
invoices_main AS A
JOIN (
SELECT invoice_id, SUM(item_count * item_unit_price) unit_revenue
FROM invoices_items_products GROUP BY invoice_id
) B
ON
A.invoice_id = B.invoice_id
JOIN (
SELECT invoice_id, SUM(item_hours * item_hourly_rate) hourly_revenue FROM
invoices_items_hours GROUP BY invoice_id
) C
ON
A.invoice_id = C.invoice_id

How to SUM column in MySQL left join

So i have table cont_selling
---------------------------------
cont_selling_id | date |
---------------------------------
1 | 2015-05-24 |
2 | 2015-06-06 |
---------------------------------
table 02 cont_sold
----------------------------------------------------
cont_sold_id | cont_selling_id | price |
---------------------------------------------------
1 | 1 | 10 |
2 | 1 | 10 |
3 | 1 | 30 |
4 | 2 | 20 |
5 | 2 | 10 |
--------------------------------------------------
and table 03 payment
----------------------------------------------
payment_id | cont_selling_id | paid |
-----------------------------------------------
1 | 1 | 10 |
2 | 2 | 10 |
3 | 1 | 20 |
4 | 1 | 10 |
5 | 2 | 10 |
-----------------------------------------------
now i need to SELECT table based on
now i want to merge all these three tables based on cont_selling table cont_selling_id column
and want to SUM cont_sold table price column and payment table paid column
this is what i want to do
expecting output
---------------------------------------------
cont_selling_id | price | paid |
---------------------------------------------
1 | 50 | 40 |
2 | 30 | 20 |
---------------------------------------------
so i tried like this in mysql query but it give wrong sum result
SELECT
SUM(Z.price) as total,
SUM(P.amount) as paid
FROM cont_selling S
LEFT JOIN cont_sold Z
ON S.cont_selling_id = Z.cont_selling_id
LEFT JOIN payment P
ON S.cont_selling_id = P.cont_selling_id
GROUP BY S.cont_selling_id
for this above query i m getting output like this
---------------------------------------------
cont_selling_id | price | paid |
---------------------------------------------
1 | 150 | 40 |
2 | 60 | 120 |
---------------------------------------------
Here how you can do it using the aggegare part into inner queries and then join
select
cs.cont_selling_id,
price,
paid
from cont_selling cs
left join(
select sum(price) as price , cont_selling_id from cont_sold
group by cont_selling_id
)x on x.cont_selling_id = cs.cont_selling_id,
left join(
select sum(paid) as paid , cont_selling_id from payment
group by cont_selling_id
)y
on y.cont_selling_id = cs.cont_selling_id;
You should make two different queries with SUM and then combine them to get the desired result:
SELECT T1.cont_selling_id,T1.price,T2.paid
FROM
(SELECT c.cont_selling_id,SUM(cs.price) as price
FROM cont_selling c LEFT JOIN
cont_sold cs ON c.cont_selling_id=cs.cont_selling_id
GROUP BY c.cont_selling_id) as T1 JOIN
(SELECT c.cont_selling_id,SUM(p.paid) as paid
FROM cont_selling c LEFT JOIN
payment p ON p.cont_selling_id=c.cont_selling_id
GROUP BY c.cont_selling_id) as T2 ON T1.cont_selling_id=T2.cont_selling_id
Result:
cont_selling_id price paid
----------------------------
1 50 40
2 30 20
Sample result in SQL Fiddle.
This untested query should work:
with a as(select cont_selling_id , sum(price) as totalprice from cont_sold group by cont_selling_id),
with a as(select cont_selling_id , sum(paid) as totalpaid from payment group by cont_selling_id),
select c.cont_selling_id , totalprice, totalpaid from cont_selling c left join a.count_selling_id = c.count_selling_id
left join b.count_selling_id = c.count_selling_id
You have to create temporary tables, because there is no dependency between your table for price and paid.

MySql LEFT JOIN with COUNT

I have two tables, customers and sales. I want to count sales for each customer and create a table of sales per month for each store.
I would like to produce something like;
------------------------------
month | customers | sales |
------------------------------
1/2013 | 5 | 2 |
2/2013 | 21 | 9 |
3/2013 | 14 | 4 |
4/2013 | 9 | 3 |
but I am having trouble getting the sales count to be correct when using the following;
SELECT CONCAT(MONTH(c.added), '/', YEAR(c.added)), count(c.id), count(s.id)
FROM customers c
LEFT JOIN sales s
ON s.customer_id = c.id AND MONTH(c.added) = MONTH(s.added) AND YEAR(c.added) = YEAR(s.added)
WHERE c.store_id = 1
GROUP BY YEAR(c.added), MONTH(c.added);
Customers table;
-------------------------------
id | store_id | added |
-------------------------------
1 | 1 |2013-02-01 |
2 | 1 |2013-02-02 |
3 | 1 |2013-03-16 |
sales table;
---------------------------------
id | added | customer_id |
---------------------------------
1 | 2013-02-18 | 3 |
2 | 2013-03-02 | 2 |
3 | 2013-03-16 | 3 |
Can anyone help here?
thanks
(Updated) The existing query will only count sales made in the same month that the customer was added. Try this, instead:
SELECT CONCAT(MONTH(sq.added), '/', YEAR(sq.added)) month_year,
sum(sq.customer_count),
sum(sq.sales_count)
FROM (select s.added, 0 customer_count, 1 sales_count
from customers c
JOIN sales s ON s.customer_id = c.id
WHERE c.store_id = 1
union all
select added, 1 customer_count, 0 sales_count
from customers
WHERE store_id = 1) sq
GROUP BY YEAR(sq.added), MONTH(sq.added);
SELECT c.* , s.sales_count<br>
FROM customers c<br>
LEFT JOIN (SELECT customer_id, count(id) as sales_count FROM sales GROUP BY customer_id) s on c.id=s.customer_id<br>
WHERE c.store_id = 1<br>

Add column from another table, but not affect count()

I have already a query with multiple JOINs, simple list of reservations
SELECT reservation.reservation_id, customer.customer_id, customer.name, count(ordered_services.reservation_id) AS num_of_ordered_services
FROM reservations
JOIN customers ON reservations.customer_id = customer.customer_id
LEFT JOIN ordered_services ON reservations.reservation_id = ordered_services.reservation_id
GROUP BY reservation.reservation_id, customer.customer_id, customer.name
ORDER BY reservation.reservation_id
which outputs something like
reservation_id | customer_id | name | num_of_ordered_services
1 | 1909091202 | John | 2
2 | 2512541508 | Jane | 3
I would like to add another column with information about payment, but simple JOIN, LEFT JOIN interferes with existing count() column. Like
SELECT reservation.reservation_id, count(payments.reservation_id) AS num_of_payments
FROM reservations
LEFT JOIN payments ON reservations.reservation_id = payments.reservation_id
GROUP BY reservation.reservation_id
ORDER BY reservation.reservation_id
reservation_id | num_of_payments
1 | 0
2 | 2
but in both a single result. How to achieve this?
PS: num_of_payments is not necessary, I only need to know if the payment for certain reservation exists or not (1, 0).
Thank you
tbl structure, nothing special:
reservations
reservation_id | customer_id | added
1 | 1909091202 | 2011-11-04 02:37:28
2 | 2512541508 | 2011-11-04 14:27:01
customers
customer_id | name | personal information columns ...
1909091202 | John | | |
2512541508 | Jane | | |
... | ... | | |
payments
payment_id | reservation_id | customer_id | total | added
1 | 2 | 1909091202 | 199 | 2011-11-04 02:37:28
2 | 2 | 2512541508 | 50 | 2011-11-04 14:27:01
You could use a subselect for the additional field.
SELECT reservation.reservation_id, customer.customer_id, customer.name,
count(ordered_services.reservation_id) AS num_of_ordered_services,
(SELECT count(*) FROM payments WHERE reservation.reservation_id=payments.reservation_id) AS num_of_payments
FROM reservations
JOIN customers ON reservations.customer_id = customer.customer_id
LEFT JOIN ordered_services ON reservations.reservation_id = ordered_services.reservation_id
GROUP BY reservation.reservation_id, customer.customer_id, customer.name
ORDER BY reservation.reservation_id
Something like the following should work:
select
reservation.reservation_id,
(case when exists (select * from payments p1 where p1.reservation_id = reservation.reservation_id) then 1 else 0 end) as one_or_many_payments_made
from reservation
GROUP BY reservation.reservation_id
ORDER BY reservation.reservation_id
But without your data, there is some guesswork here.