MySQL SUM of one column, DISTINCT of ID column - mysql

I'm trying to create a summary report of our orders but having trouble extracting all my required data in a single query.
The data I'd like to extract:
subtotal - SUM of all sale prices
delivery total - SUM of all orders deliveryTotal
orders - COUNT of DISTINCT orderIds
quantity - SUM of all quantity ordered
Orders table (simplified for this example)
| orderId | deliveryTotal | total |
|---------|---------------|-------|
| 1 | 5 | 15 |
| 2 | 5 | 15 |
| 3 | 7.50 | 27.50 |
Order items table
| orderItemId | orderId | productId | salePrice | quantity |
|-------------|---------|-----------|-----------|----------|
| 1 | 1 | 1 | 10 | 1 |
| 2 | 2 | 1 | 10 | 1 |
| 3 | 3 | 1 | 10 | 1 |
| 4 | 3 | 2 | 10 | 1 |
My current query for extracting this data is
SELECT
SUM(i.salePrice * i.quantity) as subtotal,
SUM(DISTINCT o.deliveryTotal) as deliveryTotal,
COUNT(DISTINCT o.orderId) as orders,
SUM(i.quantity) as quantity
FROM orderItems i
INNER JOIN orders o ON o.orderId = i.orderId
Which results in a correct subtotal, order count and quantity sum. But delivery total is returned as 12.50 when I'm after 17.50. If I do SUM(o.deliveryTotal) it will return 25.
EDIT: Desired results
| subtotal | deliveryTotal | orders | quantity |
|----------|---------------|--------|----------|
| 40.00 | 17.50 | 3 | 4 |

https://tiaashish.wordpress.com/2014/01/31/mysql-sum-for-distinct-rows-with-left-join/
Here is a blog post that shows exactly what I was looking for. Maybe this can help others too.
The formula is something like this:
SUM(o.deliveryTotal) * COUNT(DISTINCT o.orderId) / COUNT(*)

Because of the join, the SUM(DISTINCT deliveryTotal) aggregate is being applied to a rowset including the values 5, 5, 7.5, 7.5 (distinct 5 + 7.5 = 12.5).
The rows your SUM() acted on become more apparent if you simply do
SELECT o.*
FROM orderItems i
INNER JOIN orders o ON o.orderId = i.orderId
Instead you are asking for the SUM() of all the values in deliveryTotal, irrespective of their position in the join with orderItems. That means you need to apply the aggregate at a different level.
Since you are not intending to add a GROUP BY later, the easiest way to do that is to use a subselect whose purpose is only to get the SUM() across the whole table.
SELECT
SUM(i.salePrice * i.quantity) as subtotal,
-- deliveryTotal sum as a subselect
(SELECT SUM(deliveryTotal) FROM orders) as deliveryTotal,
COUNT(DISTINCT o.orderId) as orders,
SUM(i.quantity) as quantity
FROM orderItems i
INNER JOIN orders o ON o.orderId = i.orderId
Subselects are usually discouraged but there won't be a significant performance penalty for the subselect, none different from the alternative methods of using a join for it. The calculation has to be done on a separate aggregate from the existing join no matter what. Other methods would place a subquery CROSS JOIN in the FROM clause, which performs the same thing we placed here in the subselect. Performance would be the same.

Select per Order in the Inner Select and than sum it up
Select
SUM(subtotal) as subtotal,
sum(deliveryTotal) as deliveryTotal,
count(1) as orders,
sum(quantity) as quantity
from (
SELECT
SUM(i.salePrice * i.quantity) as subtotal,
o.deliveryTotal as deliveryTotal,
SUM(i.quantity) as quantity
FROM orders o
INNER JOIN orderItems i ON o.orderId = i.orderId
group by o.orderId) as sub

The below query results exactly what you need
SELECT SUM(conctable.subtotal),
SUM(conctable.deliveryTotal),
SUM(conctable.orders),
SUM(conctable.quantity) from
(SELECT SUM(i.salePrice * i.quantity) as subtotal,
o.deliveryTotal as deliveryTotal,
COUNT(DISTINCT o.orderId) as orders,
SUM(i.quantity) as quantity
FROM orderItems i
JOIN orders o ON o.orderId = i.orderId group by i.orderid) as conctable;

Related

MySQL query resulting in the substraction of the sum of 2 columns in different tables

I have a MySQL DB with the following Tables:
Products:
Product_ID | Product_Name
1 | Blaster
2 | Faser
3 | BFG
Orders:
Order_ID | Product_ID | Order_Product_Qnt
1 | 1 | 10
2 | 2 | 5
3 | 3 | 7
4 | 2 | 10
Sells:
Sell_ID | Product_ID | Sel_Product_Qnt
1 | 2 | 5
2 | 1 | 1
3 | 3 | 2
What I want to do is a query that lists all the products followed by their amount.
The result should be:
Product_Name | Quantity
BFG | 5
Blaster | 9
Faser | 10
Following Barnar's suggestion I got to this piece of code:
SELECT
Products.Product_Name,
COALESCE (SUM(Orders.Order_Product_Qnt), 0) - COALESCE (SUM(Sells.Sells_Product_Qnt), 0) AS Quantity
FROM
Products
LEFT JOIN
Orders ON Products.Product_ID = Orders.Product_ID
LEFT JOIN
Sells ON Products.Product_ID = Sells.Product_ID
GROUP BY
Products.Product_Name
The query works but it returns wrong values.
For example, I have a product that has 6 orders, and 1 sell, logic dictates that 6-1=5, but that query gives me 4 as a result.
Or another one with 18 Orders and 6 Sells, returns 60 (should be 12).
Any advise is appreciated.
Maybe something like this?
SELECT
product_name,
orders_cnt - sales_cnt AS Quantity
FROM (
SELECT product_name,
SUM(orders) AS orders_cnt,
SUM(sales) AS sales_cnt
FROM (
SELECT products.product_name,
ifnull(orders.order_product_qnt, 0) orders,
ifnull(sells.sells_product_qnt,0) sales
FROM products
LEFT JOIN orders ON products.product_id = orders.product_id
LEFT JOIN sells ON products.product_id = sells.product_id
) t1
GROUP BY product_name ) t2
Finally got it working, forgot to post my solution here:
SELECT
Products.Product_ID,
Products.Product_Name,
IFNULL(b.SB - c.SC, 0) AS Quantity,
FROM Produtos_Table
LEFT JOIN (
SELECT
Product_ID,
SUM(Quantity) AS SB
FROM
Orders
GROUP BY Product_ID
) b
ON Products.Product_ID = b.Product_ID
LEFT JOIN (
SELECT
Product_ID,
SUM(Sell_Product_Qnt) AS SC
FROM
Sells
GROUP BY
Product_ID
) c
ON Products.Product_ID = c.Product_ID
GROUP BY
Products.Product_Name

MySQL Aggregate Function with group by and join

I have the following tables schemas and I want to get the sum of amount column for each category and the count of employees in the corresponding categories.
employee
id | name | category
1 | SC | G 1.2
2 | BK | G 2.2
3 | LM | G 2.2
payroll_histories
id | employee_id | amount
1 | 1 | 1000
2 | 1 | 500
3 | 2 | 200
4 | 2 | 100
5 | 3 | 300
Output table should look like this:
category | total | count
G 1.2 | 1500 | 1
G 2.2 | 600 | 2
I have tried this query below its summing up and grouping but I cannot get the count to work.
SELECT
employee_id,
category,
SUM(amount) from payroll_histories,employees
WHERE employees.id=payroll_histories.employee_id
GROUP BY category;
I have tried the COUNT(category) but that one too is not working.
You are, I believe, seeking two different summaries of your data. One is a sum of salaries by category, and the other is a count of employees, also by category.
You need to use, and then join, separate aggregate queries to get this.
SELECT a.category, a.amount, b.cnt
FROM (
SELECT e.category, SUM(p.amount) amount
FROM employees e
JOIN payroll_histories p ON e.id = p.employee_id
GROUP BY e.category
) a
JOIN (
SELECT category, COUNT(*) cnt
FROM employees
GROUP BY category
) b ON a.category = b.category
The general principle here is to avoid trying to use just one aggregate query to aggregate more than one kind of detail entity. Your amount aggregates payroll totals, whereas your count aggregates employees.
Alternatively for your specific case, this query will also work. But it doesn't generalize well or necessary perform well.
SELECT e.category, SUM(p.amount) amount, COUNT(DISTINCT e.id) cnt
FROM employees e
JOIN payroll_histories p ON e.id = p.employee_id
GROUP BY e.category
The COUNT(DISTINCT....) will fix the combinatorial explosion that comes from the join.
(Pro tip: use the explicit join rather than the outmoded table,table WHERE form of the join. It's easier to read.)

How to select the SUM of the multiplication of two different table fields specifying the value of other two fields?

Based on this table schema:
products
+----+------+-------+--------+--------------+-------+-------+------+-------+
| Id | Name | Price | Detail | Product_type | Image | Color | Size | Stock |
+----+------+-------+--------+--------------+-------+-------+------+-------+
order_details
+----+------------+--------+------+-------+----------+
| Id | Product_id | Amount | Size | Color | Order_id |
+----+------------+--------+------+-------+----------+
orders
+----+-----------+------------+----------+
| Id | Client_id | Date_start | Date_end |
+----+-----------+------------+----------+
How can I select the SUM() (if this function it's even necessary) of products.Price * order_details.Amount specifying the client and the order id?
I've tried with this query, among others:
SELECT SUM((SELECT pr.Price FROM products pr WHERE pr.Id = od.Product_id) * od.Amount) AS Total
FROM order_details od
WHERE (SELECT o.Client_id FROM orders o WHERE o.Id = $order) = $client
But it's returning a wrong result and I can't figure out how to do it. Also please note I want to use subqueries.
Thanks.
Dno't use a subselect, use a join:
SELECT orders.Id, SUM(products.Price * order_details.amount)
FROM orders
LEFT JOIN orders_details ON orders.Id = order_details.Order_id
LEFT JOIN products ON products.Id = order_details.Product_id
GROUP By orders.Clien_id, orders_details.Product_id

Select and summarize data from three tables

i have three tables
customer
id | name
1 | john
orders
id | customer_id | date
1 | 1 | 2013-01-01
2 | 1 | 2013-02-01
3 | 2 | 2013-03-01
order_details
id | order_id | qty | cost
1 | 1 | 2 | 10
2 | 1 | 5 | 10
3 | 2 | 2 | 10
4 | 2 | 2 | 15
5 | 3 | 3 | 15
6 | 3 | 3 | 15
i need to select data so i can get the output for each order_id the summary of the order
sample output. I will query the database with a specific customer id
output
date | amount | qty | order_id
2013-01-01 | 70 | 7 | 1
2013-02-01 | 50 | 4 | 2
this is what i tried
SELECT
orders.id, orders.date,
SUM(order_details.qty * order_details.cost) AS amount,
SUM(order_details.qty) AS qty
FROM orders
LEFT OUTER JOIN order_details ON order_details.order_id=orders.id AND orders.customer_id = 1
GROUP BY orders.date
but this returns the same rows for all customers, only that the qty and cost dont hav values
Maybe
SELECT
orders.id, orders.date,
SUM(order_details.qty * order_details.cost) AS amount,
SUM(order_details.qty) AS qty
FROM orders
LEFT JOIN order_details ON order_details.order_id=orders.id
AND orders.customer_id = 1
GROUP BY orders.date
HAVING amount is not null AND qty is not null
SQL Fiddle
NOTE: In the following query, it is assumed that the dates are stored in the database as a string in the format specified in the OP. If they are actually stored as some type of date with time then you'll want to modify this query such that the time is truncated from the date so the date represents the whole day. You can use the date or date_format functions. But then you'll need to make sure that you modify the query appropriately so the group by and select clauses still work. I added this modification as comments inside the query.
select
o.date -- or date(o.date) as date
, sum(odtc.total_cost) as amount
, sum(odtc.qty) as qty
, o.order_id
from
orders o
inner join (
select
od.id
, od.order_id
, od.qty
, od.qty * od.cost as total_cost
from
order_details od
inner join orders _o on _o.id = od.order_id
where
_o.customer_id = :customer_id
group by
od.id
, od.order_id
, od.qty
, od.cost
) odtc on odtc.order_id = o.id
where
o.customer_id = :customer_id
group by
o.date -- or date(o.date)
, o.order_id
;
I don't think you want an outer join just a simple inner join on all 3 tables:
FROM orders, order_details, customer
WHERE orders.customer_id=customer.id
AND order_details.order_id=orders.id

left join, return non matching rows, where clause on right table, group by

Sorry about the complicated title.
I have two tables, customers and orders:
customers - names may be duplicated, ids are unique:
name | cid
a | 1
a | 2
b | 3
b | 4
c | 5
orders - pid is unique, join on cid:
pid | cid | date
1 | 1 | 01/01/2012
2 | 1 | 01/01/2012
3 | 2 | 01/01/2012
4 | 3 | 01/01/2012
5 | 3 | 01/01/2012
6 | 3 | 01/01/2012
So I used this code to get a count:
select customers.name, orders.date, count(*) as count
from customers
left JOIN orders ON customers.cid = orders.cid
where date between '01/01/2012' and '02/02/2012'
group by name,date
which worked fine but didnt give me null rows when the cid of customers didnt match a cid in orders, e.g. name-c, id-5
select customers.name, orders.date, count(*) as count
from customers
left JOIN orders ON customers.cid = orders.cid
AND date between '01/01/2012' and '02/02/2012'
group by name,date
So I changed the where to apply to the join instead, which works fine, it gives me the null rows.
So in this example I would get:
name | date | count
a | 01/01/2012 | 3
b | null | 1
b | 01/01/2012 | 3
c | null | 1
But because names have different cid's it is giving me a null row even if the name itself does have rows in orders, which I don't want.
So I'm looking for a way for the null rows to only be returned when any other cid's that share the same name also do not have any rows in orders.
Thanks for any help.
---EDIT---
I have edited the counts for null rows, count never returns null but 1.
The result of
select * from (select customers.name, orders.date, count(*) as count
from customers
left JOIN orders ON customers.cid = orders.cid
AND date between '01/01/2012' and '02/02/2012'
group by name,date) as t1 group by name
is
name | date | count
a | 01/01/2012 | 3
b | null | 1
c | null | 1
First, select your date grouped by (name, date), excluding NULLs, then join with a set of distinct names:
SELECT names.name, grouped.date, grouped.count
FROM ( SELECT DISTINCT name FROM customers ) as names
LEFT JOIN (
SELECT customers.name, orders.date, COUNT(*) as count
FROM customers
LEFT JOIN orders ON customers.cid = orders.cid
WHERE date BETWEEN '01/01/2012' AND '02/02/2012'
GROUP BY name,date
) grouped
ON names.name = grouped.name
The best approach would be Group them together based on Cid's and then other parameters.
So you would get the proper output with NULL values based on Left Outer Join.