Summing order totals Mysql - mysql

I'm fairly new to Mysql and need help trying to combine two mysql queries that give a "total" for each "storeid" from the orders total. I'm currently using two queries to get the result:
SELECT storeid, storenum, name FROM store ORDER BY storeid DESC
SELECT SUM((1+0.07125)*qty*discprice) as total FROM items WHERE orderid IN (SELECT orderid FROM orders WHERE store = '".$row['storeid']."' AND date >= '2012-01-01' AND date < '2013-01-01')
I'm running a while loop and running the second query with the "storeid". However, I know I can do this is one query and group by "storeid" and create a total for all stores combined. But I can't figure it out.
Thanks!

SELECT s.storeid, s.storenum, s.name
SUM((1+0.07125)*i.qty*i.discprice) AS total
FROM items AS i
LEFT JOIN orders AS o
ON i.orderid=o.orderid
LEFT JOIN stores AS s
ON o.store=s.storeid
WHERE o.date >= '2012-01-01'
AND o.date < '2013-01-01'
GROUP BY s.storeid, s.storenum, s.name;

The trick is to join the three tables and then use an aggregate function on the items table.
SELECT stores.storeid, stores.storenum, stores.name, SUM((1+0.07125)*items.qty*items.discprice) as total
FROM stores
LEFT JOIN orders ON orders.storeid=stores.storeid AND orders.date>='2012-01-01' AND orders.date<'2013-01-01'
LEFT JOIN items ON items.orderid=orders.orderid
GROUP BY stores.storeid, stores.storenum, stores.name
What this does it this:
It will select every store from the stores table, and sum up the orders in that store. I chose a LEFT JOIN instead of straight JOINs, so that stores without any order in that time span will still show up with a total of NULL.
P.S. I don't have a copy of your database's schema, above SQL query might not actually work as expected - it is just supposed to point you in the right direction.

Related

Join With Where Clause - Nested Join/Where?

I've been trying to look for examples that better match my specific needs but I can't seem to find any.
I've got the following SQL statement, which works like a charm:
SELECT
customers.id,
customers.customer_name,
SUM(shipments.balance) AS shipmentBalance
FROM customers
LEFT JOIN shipments ON customers.id = shipments.bill_to
GROUP BY customers.id, customers.customer_name
ORDER BY shipmentBalance DESC;
But, I would like to be able to add a where condition to the JOIN, as I don't want ALL of the shipments balances being SUMMED up, rather only the ones that have balances greater than their related payment distribution amounts.
At this point, in a separate query, I can pull the shipments with balances that are greater than their payment distribution amounts using the following query:
SELECT
shipments.id,
shipments.pro_number,
shipments.balance,
SUM(payments_distributions.amount) AS Sum
FROM
shipments
LEFT JOIN payments_distributions ON shipments.pro_number = payments_distributions.shipment_id
WHERE balance > (SELECT IFNULL(SUM(payments_distributions.amount),0) FROM payments_distributions WHERE payments_distributions.shipment_id = pro_number)
GROUP BY shipments.id,shipments.pro_number;
But I'm not sure how to combine them.
Place the filter of the Shipment table in the ON clause:
SELECT
customers.id,
customers.customer_name,
SUM(shipments.balance) AS shipmentBalance
FROM customers
LEFT JOIN shipments ON customers.id = shipments.bill_to
AND balance > (SELECT IFNULL(SUM(payments_distributions.amount),0)
FROM payments_distributions
WHERE payments_distributions.shipment_id = pro_number)
GROUP BY customers.id, customers.customer_name
ORDER BY shipmentBalance DESC;

Optimising MySql Query with LEFT JOINS

I am trying to get a list of customer who haven't ordered for 6months or more. I have 4 tables which I have used in the query
accounts (account_id)
stores (store_id, account_id)
customers (store_id, customer_id)
orders (order_id, customer_id, store_id)
The customer and orders table are very big, 3M and 26M rows respectively, so using left joins in my query make the query time extremely long. I believe I have index my tables correctly
here is my query i have used
SELECT cus.customer_id, MAX(o.order_date), cus.store_id, s.account_id, store_name
FROM customers cus
LEFT JOIN stores s ON s.store_id=cus.store_id
LEFT JOIN orders o ON o.customer_id=cus.customer_id AND o.store_id=cus.store_id
WHERE account_id=26 AND
(SELECT order_id
FROM orders o
WHERE o.customer_id=cus.customer_id
AND o.store_id=cus.store_id
AND o.order_date < CURRENT_DATE() - INTERVAL 6 MONTH
ORDER BY order_id DESC LIMIT 0,1) IS NOT NULL
GROUP BY cus.customer_id, cus.client_id;
I need to get the last order date and this is the reason why I have joined the orders table, however since the customers can have multiple orders it is returning multiple rows of the customer and that is why I have used the group by clause.
If anyone can assist me with my query.
Start with this:
SELECT customer_id, MAX(order_date) AS last_order_date
FROM orders
GROUP BY customer_id
HAVING last_order_date < NOW() - INTERVAL 6 MONTH;
Assuming that gives you the relevant customer_ids, then move on to
SELECT ...
FROM ( that-select-as-a-subquery ) AS old
JOIN other-tables-as-needed ON USING(customer_id)
If necessary, JOIN back to orders to get more info. Do not try to get other columns in that subquery. (That's a "groupwise max" problem.)
Your strategy of using an ordered and limited subquery on your orders table is probably responsible for your poor performance.
This subquery will generate a virtual table showing the date of the most recent order for each distinct customer. (I guess a distinct customer is distinguished by the pair customer_id, store_id).
SELECT MAX(order_date) recent_order_date,
customer_id, store_id
FROM orders
GROUP BY customer_id, store_id
Then, you can use that subquery as if it were a table in your query.
SELECT cus.customer_id, summary.recent_order_date,
cus.store_id, s.account_id, store_name
FROM customers cus
JOIN stores s ON s.store_id=cus.store_id
JOIN (
SELECT MAX(order_date) recent_order_date,
customer_id, store_id
FROM orders
GROUP BY customer_id, store_id
) summary ON summary.customer_id = cus.customer_id
AND summary.store_id = s.store_id
WHERE summary.recent_order_date < CURRENT_DATE - INTERVAL 6 MONTH
AND store.account_id = 26
This approach moves the GROUP BY to an inner query, and eliminates the wasteful ORDER BY ... LIMIT query pattern. The inner query doesn't have to be remade for every row in the outer query.
I don't understand why you used LEFT JOIN operations in your query.
And, by the way, most people, when they're new to SQL, don't have great intuition about which indexes are useful and which aren't. So, when asking for help, it's always good to show your indexes. In the meantime, read this:
http://use-the-index-luke.com/

Trying to do a MySQL Sum based on joining results from second table query

I have two tables (Zen Cart).
One table has the order totals, the other has the order dates.
The two tables are linked by a common orders_id.
I want to take a sum of all the order totals within a date range.
OK, while working on this question, I figured out the answer. Not quite sure if it's done right but the numbers are correct. I'll just leave it up in case it's useful for anyone else.
Is my method correct here?
select sum(value) from orders_total
INNER JOIN
orders
ON
(orders.orders_id=orders_total.orders_id
AND orders.date_purchased between '2008/1/1' AND '2012/1/1'
AND orders_total.class='ot_subtotal');
select sum(value) as orders_total
from orders_date od
inner join orders_total ot on od.orderid= ot.orderid
where date_format(od.date_purchased, "%Y-%m-%d") between ("2013-04-01") and ("2013-11-30")
First need to fetch the orderids from the orders_date table to get orderid within specified daterange.. then inner join the orders_total table.

MySQL Left Join Taking awhile

I am trying to get a list of possible customers along with the sum of their order history (ltv)
Without the order by, this query loads in under a second. With the order by and the query is taking over 90 seconds.
SELECT a.customerid,a.firstname,a.lastname,Orders.ltv
FROM customers a
LEFT JOIN (
SELECT customerid,
SUM(amount) as ltv
FROM orders
GROUP BY customerid) Orders
ON Orders.customerid=a.customerid
ORDER BY
Orders.ltv DESC
LIMIT 0,10
Any ideas how this could be sped up?
EDIT: I guess I cleaned up the query a little too much. The query is acually a little more complicated then this version. Other data is selected from the customers table, and can be sorted against as well.
Without the actual schema it is a bit hard to know how data is related but I guess this query should be equivalent and more performant:
SELECT a.customerid, coalesce(sum(o.amount), 0) TotalLtv FROM customers a
LEFT JOIN orders o ON a.customerid = o.cusomterid
GROUP BY a.customerid
ORDER BY TotalLtv DESC
LIMIT 10
The coalesce will make sure you return 0 for the customers without orders.
As #ypercube made me notice, an index on amount won't help either. You could give it a try to:
ALTER TABLE orders ADD INDEX(customer, amount)
After your question update
If you need to add more fields that functionally depend on the a.customerid in the select clause you can use the non-standard MySQL group by clause. This will result in better performance than grouping by a.customerid, a.firstname, a.lastname:
SELECT a.customerid, a.firstname, a.lastname, coalesce(sum(o.amount), 0) TotalLtv
FROM customers a
LEFT JOIN orders o ON a.customerid = o.cusomterid
GROUP BY a.customerid
ORDER BY TotalLtv DESC
LIMIT 10
A few things here. First it doesn't appear that you need to join the customers table at all here since you are only using it for the customerid, which already exists in orders table. If you have more than 10 customer id's with corresponding amounts, you will never even need to see the list of customer id's which don;t have amounts that you would get with LEFT JOIN from customers. As such, you should be able to reduce your query to this:
SELECT customerid, SUM(amount) AS ltv
FROM orders
GROUP BY customerid
ORDER BY ltv DESC LIMIT 0,10
You would need an index on customerid. Unfortunately, the sort is on a calculated field, so there is not a lot you can do to speed this up from that point.
I see the updated question. Since you do need additional fields from customers, I will revise my answer to include the customer table
SELECT c.customerid, c.firstname, c.lastname, coalesce(o.ltv, 0) AS total
FROM customers AS c
LEFT JOIN (
SELECT customerid, SUM(amount) as ltv
FROM orders
GROUP BY customerid
ORDER BY ltv DESC LIMIT 0,10) AS o
ON c.customerid = o.customerid
Note that I am joining on a sub-selected table as you were doing in your original query, however I have performed the sort and limit on the sub-selected table so you don't have to sort all the records without any entries on orders table.
Two things. First, don't use an inner query. MySQL does allow ORDER BY on a projection alias. Second, you should get a considerable improvment by having a B-TREE index on the composed key (customerid, amount). Then the engine will be able to execute this query by a simple traversal of the index, without fetching any row data.

MySQL to return query based on multiple dates and if no other records exist

I'm creating a php/mysql app and I'm trying to run a query based on a user supplied date, then using those results return the query if another date does not exist or match.
I have multiple orders that I want to query based on a few things, like so:
The user supplies a date.
MySQL uses supplied date and returns the rows only if there are NO "Orders" that have a more present date associated to that Customer_ID. So I only want a value if there are NO newer orders based on the user supplied date.
Here's what I've been playing with:
SELECT
o.Customer_ID, o.ShippingCompanyName, cg.Category, cd.Category_ID, o.Order_ID,
SUM(o.CustomerOrderTotal) as TOTAL,
COUNT(o.Order_ID) as ORDERS,
MAX(o.OrderPlaceServerTime) as LASTORDER
FROM Orders o
LEFT JOIN CustomerDetails cd ON o.Customer_ID = cd.Customer_ID
LEFT JOIN _CustomerCategory cg ON cg.Category_ID = cd.Category_ID
WHERE (
o.OrderPlaceServerTime <= '".$BEFORE."'
AND o.OrderPlaceServerTime NOT BETWEEN '".$BEFORE."' AND NOW()
)
AND o.IsVOID = 0
AND o.IsPENDING = 0
GROUP BY o.Customer_ID
ORDER BY TOTAL DESC
I'm not getting the results I want. I gives me Customer Orders that also have newer orders than the user supplied date.
Also the "Dates" are like '2010-10-10 10:05:55' so with data & time.
I'm a bit lost so I'm hoping someone can help me here or point me in the right direction.
Thanks.
You are mixing two things in the same query.
Each time the WHERE expressions run they check a SINGLE row! They do not check other rows. So when you check the dates you are basically saying WHERE i is less then 5 and i is not greater than 5. Which is pointless - you already said less then 5 in the first expression.
Change the WHERE to:
o.OrderPlaceServerTime <= '$BEFORE' AND
NOT EXISTS (SELECT * FROM Orders WHERE Orders = o.Customer_ID AND OrderPlaceServerTime > '$BEFORE')