I am trying to generate a report which calculates the margin from the below database. The problem is that the cost (existing in purchase_order_products table) of the product may change.
The cost of product with id 4022 on 2017-06-08 is 1110, however its cost is 1094 on 2017-07-25. This is confusing. I am unable to get the exact cost for each product sold.
I wrote a PHP algorithm which loops through all orders and purchase orders and used the oldest cost to newest cost. but the algorithm has a very high time complexity. Can this be done just using mysql query?
Please check below scenario:
Company created a purchase order for product X: quantity 3, cost 10. on day 1
Customers bought 2 product X sell price: 12 on day 1 (still have 1 item in inventory with cost 10)
Company created a purchase order for product X : quantity 4, cost 9. on day 2
Customers bought 3 product X sell price: 12 on day 2
Customers bought 2 product X sell price: 12 on day 3
Company created a purchase order for product X : quantity 2, cost 11. on day 3
Customers bought 2 product X sell price: 12 on day 3
The report:
day 1:
sold 2 product X for 12 , cost 10 , profit: 2 * (12 - 10)
day 2:
sold 3 product X for 12 , 1 item has a cost of 10, 2 items have a cost of 9 ,
profit: 1 * (12 - 10) + 2 * (12 - 9)
day 3:
sold 2 product X for 12 , cost 9 , profit: 2 * (12 - 9)
sold 2 product X for 12 , cost 11 , profit: 2 * (12 - 11)
Therefor the profit of newly sold products is calculated using the their corresponding cost. Hope you got my point.
Databse Structure:
4 Products From Database
Products Purchase orders for the above products
Sold Products
Dump File Attached here
Why don't you just take it easy and add a profit column to the orders table that is calculated in real time when a customer buys a product.This way you can calculate your marging solely from the sales orders given the fact that this is actualy already calculated somehow in order to generate the selling price. Of course this will work only for future sales but you can use your existing code with little modification to populate the profit column for old records and you will run this code only one time for the old transactions before the update.
To elaborate more:
Alter the table "sales_order" adding "profit" column . This way you can calculate the sum using the other related columns (total_paid, total_refund, total_due, grand_total) because you may want have more control over the report by including those monetary fields as needed in your calculation for example generating a report using total_payed only excluding tota_due or encluding it for different type of reports, in other words you can generate multiple reports types only from this table without overwhelming the DB system by adding this one column only.
Edit:
You can also add a cost column to this table for fast retrieving purpose and minimize joins and queries to other tables and if you want to take it a step further you can add a dedicated table for reports and it will be very helpful for example to generate a missing report from last month and checking old order status.
Some disclaimers:
this is an attempt to assist with the logic, so it's rough code(open to SQL injection attacks, so don't copy and paste this)
I can't test this query so there's probably mistakes in it, just trying to get you on the right track (and/or will make follow up edits)
This won't work if you need profit per order, only for profit per product. You could probably get a date range with a BETWEEN clause if needed.
That being said, I think something like this should work for you:
$productsIds = array('4022', '4023', '4160', '4548', '4601');
foreach($productIds as $pid){
$sql = "SELECT (soi.revenue - sum(pop.cost)) AS profit, sum(pop.cost) AS total_cost, sum(pop.quantity) AS total_purchased, soi.revenue, soi.total_sold
FROM purchase_order_products pop
JOIN (SELECT sum(price) AS revenue, sum(quantity_ordred) AS total_sold FROM sales_order_item WHERE product_id = ".$pid.") AS soi ON soi.product_id = pop.product_id
WHERE pop.product_id = ".$pid." GROUP BY pop.product_id HAVING sum(pop.quantity) < soi.total_sold ORDER BY pop.created_at ASC;";
$conn->query($sql);
//do what you want with results
}
The key thing here is using the HAVING clause after GROUP BY to determine where you cut off finding the sum of the purchase costs. You can sum them all as long as they're within that range, and you get the right dates ordering by created_at.
Again, I can't test this, and I wouldn't recommend using this code as is, just hoping this helps from a "here's a general idea of how to make this happen".
If I had time to recreate your databases I would, or if you provide sql dump files with example data, I could try to get you a working example.
The price of an object on a given time is given by the formula : "total price of the stock / total number in the stock".
To get this, you have two queries to execute :
The first one to know the amount of sold items (total price and quantities) :
sql:
SELECT SUM(row_total) sale_total_cost, SUM(quantity_ordered) sale_total_number
FROM sales_order_item soi
JOIN sales_order so ON soi.sales_order_id=so.id
WHERE so.purchase_date<'2017-06-07 15:03:30'
AND soi.product_id=4160;
the second one to know how much you have bought the products
sql:
SELECT SUM(pop.cost * pop.quantity) purchase_total_price, SUM(pop.quantity) purchase_total_number
FROM purchase_order_products pop
JOIN purchase_order po ON pop.purchase_order_id=po.id
WHERE po.created_at<'2017-06-07 15:03:30'
AND pop.product_id=4160;
The price of the product 4160 at 2017-02-01 14:23:35 is:
(purchase_total_price - sale_total_cost) / (purchase_total_number - sale_total_number)
The problem is that your "sales_order" table start on 2017-02-01 14:23:35, while your "purchase_order" table start on 2017-06-07 08:55:48. So the result will be incoherent as long as you can't track all your purchases from the start.
EDIT:
If you can modify you table structure and are only interested in future sells.
Adding the number of items sold in the purchase_order_products table
You have to modify purchase_order_products to have the consumption for each product:
ALTER TABLE `purchase_order_products` ADD COLUMN sold_items INT DEFAULT 0;
Initializing the data
In order for it to work, you have to make the sold_items column reflect your real stock
You should initialize your table with the following request
UPDATE `purchase_order_products` SET sold_items=quantity;
and then manually update the table with your exact stock for each product (which means that quantity_ordered-sold_items must reflect your real stock.
This has to be done only once.
adding the purchase price to the sales_order_item table
ALTER TABLE sales_order_item ADD total_purchase_price INT DEFAULT NULL
entering new sales order
When you enter new sales order, you will have to get the oldest purchase order with remaining items using the following command:
SELECT * FROM `purchase_order_products` WHERE quantity!=sold_items where product_id=4160 ORDER BY `purchase_order_id` LIMIT 1;
You will have then to increment the sold_items value, and calculate the total purchase price (sum) to fill the total_purchase_price column.
calculating margin
The margin will be easily calculated with the difference between row_total and total_purchase_price in the sales_order_item table
I appreciate you are using the FIFO method for the management of stock. However, this does not mean you need to use FIFO to calculate margins. The article https://en.wikipedia.org/wiki/Inventory_valuation gives an overview of the options. (Regulations in your country may exclude some options.)
I believe a repeatable solution for FIFO margin calculation of an individual sale is complex. There are complexities of opening balances, returns, partial deliveries, partial shipments, out-of-order processing, stock-take adjustments, damaged goods etc.
These issues do not seem to be addressed by the database structures in your question.
Typically, these issues are addressed by computing the margin/profit of a period (day, month etc) by calculating the change in value of the inventory over the period.
If you can use the average cost method, you can calculate the margin with pure SQL. I believe other methods would seem to require some iteration as there is no inherent order in SQL. (Your could improve performance by creating a new table and storing the previous period values.)
I would not be too worried about putting the whole solution in SQL as this would not appear to reduce the computational complexity of the problem. Still there might be speed advantages in doing as much of the calculation in the database engine, particularly if the data-set is large.
You might find this article interesting: Set-based Speed Phreakery: The FIFO Stock Inventory SQL Problem. (There are some clever people out there!)
Related
I'm facing issue to manage the product prices to get the accurate profit.
Currently I'm calculating the profit as.
Profit = (quantity*productSalePrice)-(quantity*productBuyPrice)
But let suppose price of the product is updated after two month. So when I will get the history of the last month then the profit will be calculated based on the new price, so it will not be accurate profit.
Let suppose there are total 15 stock of the product and 5 of them having the old price and next 10 have the latest price, how I can calculate the profit on this?
I have table named tb_product with three columns ProductID, Quantity and Price
if for example Product with ID 5 has a quantity of 20 and somebody bought one item out of it it should be remaining 19 items from the table. That is anytime an order is made product quantity should be subtracted by the number ordered till the product reaches 0 or finishes from the product table.
I have no idea about the it, please somebody help me.
update tb_product set quantity=quantity-1 where productid=productid
My question was partially answered by Raphul Chauham, because his answer does not give me exactly what i was looking for but subtracted the quantity column by 1 instead of by the variable representative from the order. Below is what I wanted exactly.
<?php
$updateProduct=mysql_query("UPDATE tb_products SET quantity=quantity-'$quantity' WHERE productID='$product_id' and quantity>0");
?>
The answer I considered favorable was only subtracting quantity by one whether order quantity is 20 but with this, the subtraction vary as user order varies by the variable $quantity
I'm building a web site for marketing company. As per their requirement, when a customer makes a booking. A certain amount of bonus is distributed between employees based on
their hierarchy. The distribution starts from 60 days after booking and bonus is given
for 24 months.
The tables are
bookings
bid book_date
1 2012-05-09
2 2012-05-10
bonus
bid empid amount
1 1 300
1 2 400
2 2 300
2 3 400
Is it possible to write mysql views that generates monthly bonus an employee gets
for every month. I didn't find solution on how to make update with mysql view. Any hint
will of great help.
Instead of view, I would suggest is write mysql function which will return the bonus by accepting the employee ID.
Using mysql function you will have more room to write logic and PL/SQL.
Inner join on bid and filter to only include eligible bonuses by comparing the book date to today's date. If today's date is less than 60 days after or more than 24 months plus 60 days after the original book date, exclude it. (You can go to mySQL.com to learn more about how to manipulate dates in mySQL. I forget...)
You will be left with multiple rows containing only emp id and amount. In the second round, use a "select sum(amount) from (...put your other query here...) group by empid" to get the aggregate bonus per employee.
This approach (and I think any solution) requires a nested SQL statement, and so if you're not comfortable with that syntax you can use that term to explore in google or SO. Cheers!
There are a few hundred of book records in the database and each record has a publish time. In the homepage of the website, I am required to write some codes to randomly pick 10 books and put them there. The requirement is that newer books need to have higher chances of getting displayed.
Since the time is an integer, I am thinking like this to calculate the probability for each book:
Probability of a book to be drawn = (current time - publish time of the book) / ((current time - publish time of the book1) + (current time - publish time of the book1) + ... (current time - publish time of the bookn))
After a book is drawn, the next round of the loop will minus the (current time - publish time of the book) from the denominator and recalculate the probability for each of the remaining books, the loop continues until 10 books have been drawn.
Is this algorithm a correct one?
By the way, the website is written in PHP.
Feel free to suggest some PHP codes if you have a better algorithm in your mind.
Many thanks to you all.
Here's a very similar question that may help: Random weighted choice The solution is in C# but the code is very readable and close to PHP syntax so it should be easy to adapt.
For example, here's how one could do this in MySQL:
First calculate the total age of all books and store it in a MySQL user variable:
SELECT SUM(TO_DAYS(CURDATE())-TO_DAYS(publish_date)) FROM books INTO #total;
Then choose books randomly, weighted by their age:
SELECT book_id FROM (
SELECT book_id, TO_DAYS(CURDATE())-TO_DAYS(publish_date) AS age FROM books
) b
WHERE book_id NOT IN (...list of book_ids chosen so far...)
AND RAND()*#total < b.age AND (#total:=#total-b.age)
ORDER BY b.publish_date DESC
LIMIT 10;
Note that the #total decreases only if a book has passed the random-selection test, because of short-circuiting of AND expressions.
This is not guaranteed to choose 10 books in one pass -- it's not even guaranteed to choose any books on a given pass. So you have to re-run the second step until you've found 10 books. The #total variable retains its decreased value so you don't have to recalculate it.
First off I think your formula will guarantee that earlier books get picked. Try to set your initial probabilities based on:
Age - days since publication
Max(Age) - oldest book in the sample
Book Age(i) - age of book i
... Prob (i) = [Max (age) + e - Book Age (i)] / sum over all i [ Max (age) + e - Book age(i) ]
The value e ensures that the oldest book has some probability of being selected. Now that that is done, you can always recalc the prob of any sample.
Now you have to find an UNBIASED way of picking books. Probably the best way would be to calculate the cumulative distribution using the above then pick a uniform (0,1) r.v. Find where that r.v. is in the cumulative distribution and pick the book nearest to it.
Can't help you on the coding. Make sense?
I am using MySQL and PHP. I have a table that contains the columns id and quantity. I would like to retrieve the id of the row that is the last to sum quantity as it reaches the number 40 at sum. To be more specific. I have 3 rows in the database. One with quantity 10, one with quantity 30 and one with quantity 20. So if I sum the quantities to have the result 40, I would sum up the first two witch means: 10 + 30 = 40. That way, the last Id that is used to sum the number 40 is 2. I just want to know the id of the last row that is used to complete the sum of 40.
I would give further details if asked. THANK YOU!!
Let me put it this way:
I really have 6 products in my hand. The first one came to my possession on the date of 10, the next 3 came on the date of 11 and the last 2 came on 12.
Now, I want to sell 3 products from my stock. And want to sell them in the order that they came. So for the customer that wants 3 products I would sell him the product that came on 10 and 2 products from the ones that came on 11.
For the next customer that wants 2 products, I would sell him one product from the date of 11 that remains from the last order of 3 products, and another one from the ones on 12.
The question is how would I know which price had each product I sold ? I thought that if I can find out which rows sums up every requested quantity, I would know where to start the sum every time I want to deliver an order. So first I would look which rows sums up 3 products and keep the entry id. For the next order I would start the count from that ID and sum until it sums up the second order of 2 products and so on. I thought that this way, I can keep track of the incoming prices that each product had. So I won't sell the products from the date of 12 at a price made up using the first prices.
I hope you understand. I just need to know what price had any of my products so I would know that the first products would have one price but as the product prices raises, I must raise my prices too...So the last products that came must be sold for a higher price. I can only achieve that if I keep track of this...
Thank you very much.
Nobody ? Or, even easier: MySQL should select the needed rows for SUM(quantity) to be higher or equal with 40 for example. And then to get me the id of the last row that participated at the sum process.
Have a third column with a running total. Then you can simply return the last row where the running total <= your target value.
So your table should look like:
ID Quantity RunningTotal
1 10 10
2 30 40
3 20 60
NOTE: If you delete a row in the table, remember to update all subsequent rows RunningTotal -= DeletedRow.Quantity!
I don't understand your question too well. Can you try rewording it more properly? From what I interpret, here's the structure of your database:
ProductID ArrivalDate
1 10
2 11
3 11
4 11
5 12
6 12
Now you are asking, "how would I know which price had each product I sold"? Which sorta confuses me, since each value in the database has no price attribute. Shouldn't your database look like this:
ProductID ArrivalDate Price
1 10 100
2 11 200
3 11 300
4 11 300
5 12 400
6 12 400
Personally, I think your idea to find out price sold is flawed. It would make more sense to add a few more fields to your database:
ProductID ArrivalDate Price InStock DateSold
1 10 100 Yes 17
2 11 200 Yes 17
3 11 300 Yes 18
4 11 300 Yes 18
5 12 400 no
6 12 400 no
In changing your database, you can easily keep track of when a product arrives, the date sold, its price, maybe quantity (I can't tell if its an actual field or not).
Furthermore, you can simplify and make your life easier by separating the sql queries, or even adding some code to do some of the work for you.
Relying on table ID's is probably a bad idea for this, but if that is how it is really done, you could try something like this (not tested):
SELECT yourTableA.id
FROM yourTable AS yourTableA
JOIN yourTable AS yourTableB
WHERE ( yourTableA.value + yourTableB.value ) = 40
AND yourTableA.id != yourTableB.id
ORDER BY yourTableA.id
This type of solution will only work if your expecting that you only need two rows ever to equal your target sum. Since this is most likely not the case, your best bet is probably to try and get all of the rows and do this programaticly on the returned data.
The Running Total solution posted by lc is also a good option although I generally try to avoid storing calculated data unless I absolutely have to.
Based on the updated information from this request, I have an alternate answer.
It doesn't sound so much like you care about the inventory. You care more about when the products came in.
SELECT *
FROM product
ORDER BY product.receivedData
Process each record as they come in, store the price for that record, and keep going for as long as you need to until you reach the number of items you need. You should end up with a list of items, the number of inventory at that level and the price at that level.