fast and slow moving products in mysql - php

Can anyone help me out about fast and slow moving products on MySQL?
I want to know how to SELECT CLAUSE all the fast moving products and the slow moving products seperately. Here are my tables.
**product**
productID
productname
price
**sales**
transactionID
productID
quantity
subtotal
**transaction**
transactionID
datetransact
I cut some of the columns to make it look simple.
FAST MOVING PRODUCTS is a product that have been sold often in a specific period of time.
SLOW MOVING PRODUCTS is a product that sell not so often and sit on the shelves on a long period of time.

You will want to group by product and select the min(datetransact) and max(datetransact). The difference of these two will give you the number of products sold and the timespan between the first and last sale date. Then you can divide these to get an average.
Updated to calculate on quantity sold.
select sum(sales.quantity) as productssold,
min(transaction.datetransact) as firstsale,
max(transaction.datetransact) as lastsale,
max(transaction.datetransact) - min(transaction.datetransact) as timespan,
sum(sales.quantity) / max(transaction.datetransact) - min(transaction.datetransact) as averagesold
from product
join sales on product.productid = sales.productid
join transaction on sales.transactionid = transaction.transactionid
group by product.productid
having averagesold >= 'desired value'

Scott's answer is good as far as it goes. First, you seem to be concerned about the quantity of the products sold not just the number of transactions containing the product. And, the question (which has perhaps been revised) is about a particular date range.
To get the answer for a particular range of dates, simply use a where clause or conditional aggregation. The following uses filtering and includes products with no sales:
select p.*, sum(s.quantity) as productssold,
sum(s.quantity) / datediff(#datelast, #datefirst)) as AvgPerDay
from product p left join
sales s
on p.productid = s.productid left join
transaction t
on s.transactionid = t.transactionid
where t.datetransact between #datefirst and #datelast
group by p.productid
order by AvgPerDay;
If you don't want products that never sold, simple change the left join back to inner joins.
The problem with the filtering approach is that some products may have had their first sale after beginning of your period. To handle this, you want to measure the average since the first sales date (or perhaps since some release date in the product table). This basically moves the date condition from the where clause to the having clause:
select p.*, sum(case when t.datetransact between #datefirst and #datelast then s.quantity else 0 end
) as productssold,
(sum(case when t.datetransact between #datefirst and #datelast then s.quantity else 0 end) /
datediff(#datelast, least(#datefirst, max(t.datetransact)))
) as AvgPerDay
from product p left join
sales s
on p.productid = s.productid left join
transaction t
on s.transactionid = t.transactionid
group by p.productid
order by AvgPerDay;

Related

Mysql using GROUP BY and SUM(without group by) in the same query without SUBQUERY. Possible?

I had a query made up from two tables that have a One to Many relationship.
Products table
product_id
1234
Products_destinations table
product_id
destinations_id
1234
1
1234
2
I made a query to select product id ant all the related destinations, which is pretty easy.
SELECT
p.product_id, GROUP_CONCAT(DISTINCT pd.destinations_id) as destinations_list
FROM
`products` p
INNER JOIN products_destinations pd ON pd.product_id = p.product_id
GROUP BY p.product_id
The result is:
product_id
destinations_list
1234
1,2
Now a new table enters the query, that can has a One to Many relationship with product as well. It does not however have any relationshio with products_destinations dable.
Products_prices table
product_id
price
1234
200
The updated query looks like this:
SELECT
p.product_id, GROUP_CONCAT(DISTINCT pd.destinations_id) as destinations_list, SUM(pc.price) as all_prices
FROM
`products` p
INNER JOIN products_destinations pd ON pd.product_id = p.product_id
INNER JOIN products_prices pc ON pc.product_id = p.product_id
GROUP BY p.product_id
And now the end result looks like this:
product_id
destinations_list
all_prices
1234
1,2
400
As you can see the price is showing 400 instead of 200, because of the grouping of two destinations that the product has. Is it possible to count the SUM of the products prices in this type of query? One solution is to use SUBQUERY to count the SUM of prices, however this is just an example and the real tables are super large and full of data... Subqueries increase the query time drastically.
UPDATED:
"count the SUM" is bad english, sorry for that. The problem with this query is that the product price is not correct when there are multiple destinations for the product. For example the product can be related to multiple destinations and can have multiple prices. In the same query I need to select the list of destinations and the Total price of the product. In this example product has one price which is 200, but because I need to retrieve the list of destinations also I have to GROUP BY the product which causes the price to be incremented by each destination also. IF the product would have more prices the results would be even worse. In the end the result should look like this:
product_id
destinations_list
all_prices
1234
1,2
200
instead of this:
product_id
destinations_list
all_prices
1234
1,2
400
The basic problem you're running into is that there is a cartesian explosion between prices and destinations. Any time you are joining in any more than one single table that enjoys a 1:M relationship with product, you'll start to multiple the rows; two prices and two destinations will become 4 rows, three prices and 4 destinations will become 12 rows
The simplest solution is to make sure you only join in rows that are 1:1:
SELECT
p.product_id, pd.destinations_list, pc.sumprices
FROM
products p
INNER JOIN (
SELECT product_id, GROUP_CONCAT(destinations_id) as destinations_list
FROM products_destinations
GROUP BY product_id
) pd ON pd.product_id = p.product_id
INNER JOIN (
SELECT product_id, SUM(price) as sumprices
FROM products_prices
GROUP BY product_id
) pc ON pc.product_id = p.product_id
You can get away with only doing one of these subqueries, but I don't see much point because you'll only have to group the outer and handle repetition there. It's easier to just handle aggregation on a per table basis so that ultimately you're joining everything on a 1:1 in the outer. Once you have this, run an explain plan and work out how you can wisely index to improve things (you haven't posted any where clauses). I appreciate that you said "without subquery" but you're causing a problem (cartesian explosion) that you're then having to find hacky ways to solve, and that will only go so far; it's better to not cause the problem in the first place than find ways to bodge it up after you've caused it. Don't fret too much about how the query appears; it'll likely be considerably rewritten by the optimizer anyway, so getting the plan of how it's actually executing and tuning the setup for that would be more productive
Another approach is correlated subqueries:
SELECT p.product_id,
(SELECT GROUP_CONCAT(DISTINCT pd.destinations_id)
FROM products_destinations pd
WHERE pd.product_id = p.product_id
) as destinations_list,
(SELECT SUM(pc.price)
FROM products_prices pc
WHERE pc.product_id = p.product_id
) as all_prices
FROM products p ;
Note: This keeps all products, even those that might be missing prices or destinations.
This version has the advantage that if you are filtering down the number of products with a WHERE clause, then it should have very good performance -- assuming that product_id is indexed in the two junction tables.

CodeIgniter: Join more than 3 tables

I have five tables:
Bill: no(pk),date, time, total
BillOrderRelation: no(fk), order_id(fk)
Order: order_id(pk), menu_id(fk), quantities, total
Menu: menu_id(pk), category_id(fk), menu_name, stock, price
Category: category_id(pk), category_name, colour
In my case, I have to retrieve which menu that has a highest sales in one day range, 7 days range, and 30 days range.
I've already succeed retrieve those information, but i think it's too complicated. First I have to retrieve the date on Bill, and then find the order in BillOrderRelation, and then find the Menu, and find the Category name. It includes a lot of queries and complex way to do the summing stuff for the same menu.
My question is, is that possible to query all those table in one query to retrieve just menu.menu_name, order.quantities, order.total, category.name and it's included the sum stuff for the same menu retrieved?
I've already succeed make a query for three table without using time range like this..
SELECT
menu.menu_name as top_item,
SUM(order.quantities) AS count_sold,
SUM(order.total) AS amount,
category.nama AS categories
FROM
menu, order, category
WHERE
menu.mennu_id = bill.menu_id
AND category.category_id = menu.category_id
GROUP BY
bill.menu_id, menu.menu_name, category.category_name
ORDER BY
count_sold DESC
Is there any tricky way for the case above?
Update for PostgreSQL
I believe you want something like this:
SELECT
m.menu_name AS top_item
, c.name AS category
, SUM(o.quantities) AS sum_quantity
, SUM(o.total) AS sum_total
FROM
menu m
JOIN
category c
ON c.category_id=m.category_id
JOIN
order o
ON o.menu_id=m.menu_id
JOIN
billorderrelation bor
ON bor.order_id=o.order_id
JOIN
bill b
ON b.no=bor.no
WHERE
b.date >= (CURRENT_DATE - INTERVAL '7 days')
GROUP BY
m.menu_id
ORDER BY
sum_quantity DESC
;
CURRENT_DATE allows you to get the current date (according to the timezone specified in your database). Read more:
https://www.postgresql.org/docs/8.2/static/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT

Crossreferencing two SQL results, finding overlap?

I have a table of customer orders: inside are zero cost orders (things I have given away to customers) as well as paid orders. I want to find out how many customers who have received a free order have also purchased an order.
SELECT customer_id
FROM orders
WHERE total = 0
GROUP BY customer_id
SELECT customer_id
FROM orders
WHERE total != 0
GROUP BY customer_id
These two queries give me a list of customers who received a free order and a list of customers who paid for an order. I want to count the overlapping instances, for example:
//free orders
1,5,2,9,3,11,7
//paid orders
1,5,4,8,7,9,3,12,10,13
The intersect between these two sets is {1,5,9,3,7}, which is five element. I need a way to do this strictly using SQL preferred.
Just use a having clause. This is probably the simplest way, assuming total has no negative values:
SELECT customer_id
FROM orders
GROUP BY customer_id
HAVING MIN(total) = 0 AND MAX(total) > 0;
Try this (not tested, sorry)
SELECT count(distinct customer_id)
FROM orders a JOIN orders b
ON a.customer_id = b.customer_id
WHERE a.total = 0 and b.total <>0

mysql to resturn all details of orders where a product has been ordered

I want to be able to list all items that have been purchased in every order for all orders where an item within that order is a specific item.
Eg: I want to be able to do a search for all orders where tomato ketchup has been ordered and list all the products in these orders.
I want to be able to say, therefore, for all customers that purchased tomato ketchup, what other products accompanied that item.
Its similar to what you would expect to see in any cyber market place where "others also bought..."
The database I am using has a table with products and an order ID. I imagine the simplest way is to write a PHP script to perform 2 queries, one to return all orders containing the item in question and another to list all items for each of the order IDs. But I had hoped there was a "clever" query that could replace that.
My ultimate goal would be to search for all orders where a list of items exist. The idea is to try and whittle down the results to only show those where the specified combination of products has been found.
Assuming:
products(id integer, name varchar(25) and orders (id integer) and order_items (order_id integer, product_id integer)
Use this:
SELECT oi.order_id, p2.name
FROM products p
INNER JOIN order_items oi
ON p.id = oi.product_id
INNER JOIN order_items oi2
ON oi.order_id = oi2.order_id
INNER JOIN products p2
ON oi2.product_id = p2.id
WHERE p.name = 'Carrot'
Fiddle example: http://sqlfiddle.com/#!9/0b9f4/2

MySQL sum up numbers in a relational database

I'm making a web application to make customers order items for anything. For that I've made a MySQL database which has the following tables:
customers
orders
order-items
products
In the customers table is all the information about the person such as:
The customer ID, for the primary key and auto increment (id)
The first name (first_name)
The last name (last_name)
The email address (email_adress)
Information about the customer (customer_info)
Example:
In the orders table is all the specific information about it such as:
The order ID, for the primary key and auto increment (id)
Which customer ordered it, linked with id field from the customers table (customer_id)
Order information (order_info)
The location where the order needs to go to (location)
When the order was created (created)
Example:
In the order-items table are all the items which every customer ordered, this is being linked by the order-id from the previous table.
The ID, for primary key and auto increment, not used for any relation (id)
The order ID, used for which product is for which order. This is linked with the id field from the orders table (order_id)
The product ID, this is used for what product they ordered, this is linked with the id field from the products table. (product_id)
The amount of this product they ordered (quantity)
Example:
In the products table is all the information about the products:
The ID, for primary key and auto incrementing, This is linked with the product_id field from the order_items table (id)
The name of the product (name)
The description of the product (description)
The price of the product (price)
Example:
The problem
Bob ordered product_id 2, times 3. Which is the sandwich with beef with the price of 2.50, which we have to multiply by 3 because it has been ordered 3 times. Which is 7.50
Bob also ordered product_id 3, times 5. Which is the sandwich with chicken with the price of 3.00, which we have to multiply by 5 because it has been ordered 5 times. Which comes out on 15.00
Now I need to sum these up. Which is 15.00 + 7.50 = 22.50
The question
How do I get the product_id linked with the actual price of the product_id? which I can then multiply by the quantity.
And then sum up all those values with the same order_id
For the first order we get product 2 (Quantity 3) and product 3 (Quantity 5), which should add 2.503 + 3.005 = 22.50
You asked, "
How do i get the product_id linked with the actual price of the product_id? Which i can then multiply by the quantity... And then sum up all those values with the same order_id."
Like this:
SELECT OI.Order_ID, Sum(OI.Quantity * P.Price) Total_Price
FROM `order-items` OI
INNER JOIN Products P
on OI.Products_Id = P.ID
GROUP BY OI.Order_ID
Expected output for sample data in question:
ORDER_ID Total_price
1 22.50
This is why I asked about sample Output. I'm not sure what columns you were trying to return so I returned just the sum total for each order.
Now what this says
Return a row for each order showing the order ID and the total_price which is the sum of (quantity ordered * price all lines for that order.
This is accomplished by looking at the order-items table (in back tic's because I'm not sure if mySQL like's dashes (-) in names.). Joining these tables based on the product_Id between the ordered items table and the price table. and then group the results by the ordered item allowing the sum to aggregate all the rows for an order times the price of the item on that line of the order together.
You can build an intermediate table that has the totals for each order, then JOIN that with the orders table to get the customerID of that order, which you join with customers to get the customer name
SELECT C.FirstName, C.LastName, Totals.Order_ID, Totals.NetCost
FROM (SELECT OI.order_id, SUM(OI.quantity * P.price) as NetCost
FROM orderitems as OI INNER JOIN products as P ON OI.products_id = P.id
GROUP BY OI.order_id
) as Totals
INNER JOIN orders as O on O.ID = Totals.order_id
INNER JOIN customers as C on C.ID = O.customer_id
And the problem #xQbert is talking about is that if your customer places an order on Monday, you raise your price on Tuesday, and run this report on Wed, the report will show a total different from what the customer saw when he approved the order. A better design would be to store orderprice in orderitem, or at least the ordertotal in orders so subsequent price changes don't affect historic orders. The former is better in case a customer wants a refund on one item.
EDIT: I mean the problem #xQbert mentions in his comment, his answer came in ahead of mine but I missed it on the refresh
EDIT 2: More explanation, as requested by #Bas
It's easiest to think of this query from the inside and work outward, so let me start with getting the prices of items.
I want to link an item in orderitem with it's price, so JOIN orderitem (I give it an alias of OI) to products (I give an alias of P) on the product ID. Product ID is called products_id in OrderItem, but just ID in products.
Once I have that, I can multiply the price from Products by the quantity in OrderItems to get a cost for just that item
Because I want the total of everything for each order, I use a GROUP BY clause on this sub query, with the thing I'm grouping by being order_id. With a GROUP BY, you usually take the field or fields you are grouping by, and some "aggregate" fields where you take the sum, or max, or average, or whatever of them, and it's the sum or whatever for all rows that are in the same group.
So at this point we have defined the query inside the parenthesis. I named that Totals, and it acts just like a real table, but it goes away when your query is over.
The next step is to combine this "totals" table with your orders table so we can look up the customer ID, and then combine it with Customers so we can look up the customer name. I used aliases for Customer and Orders (C and O respectively), but they are not necessary. In fact, this could be rewritten most if not all of the aliases. I think it makes it more readable, but if they confuse you, you could use the following instead, it's the same. I think Totals is necessary, but maybe not even that.
SELECT FirstName, LastName, Totals.Order_ID, Totals.NetCost
FROM (SELECT order_id, SUM(quantity * price) as NetCost
FROM orderitems INNER JOIN products ON products_id = products.id
GROUP BY order_id
) as Totals
INNER JOIN orders on orders.ID = Totals.order_id
INNER JOIN customers on customers.ID = orders.customer_id
Hopefully this is clear, but if not, leave more comments and I'll further explain tonight or tomorrow.

Categories