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!
Related
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!)
So I have a table that looks like this:
Person Product Date Quantity
1 A 1/11/2014 1
2 A 1/11/2014 2
1 A 1/20/2014 2
3 A 1/21/2014 1
3 B 1/21/2014 1
1 A 1/25/2014 1
I want to find the Count of Quantity where Product is A and Person has a Count > 1 WITHIN ANY SLIDING 30 DAY RANGE. Another key is that once two records meet the criteria, they should not add to the count again. For example, Person 1 will have a count of 3 for 1/11 and 1/20, but will not have a count of 3 for 1/20 and 1/25. Person 2 will have a count of 2. Person 3 will not show up in the results, because the second product is B. This query will run within a specific date range also (e.g, 1/1/2014 - 10/27/2014).
My product is written in MySQL and PHP and I would prefer to do this exclusively in MySQL, but this seems more like an OLAP problem. I greatly appreciate any guidance.
Another key is that once two records meet the criteria, they should not add to the count again.
This is not relational. In order for this to be meaningful, we have to define the order in which records are evaluated. While SQL does have ORDER BY, that's for display purposes only. It does not affect the order in which the query is computed. The order of evaluation is not meant to matter.
I do not believe this can be expressed as a SELECT query at all. If I am correct, that leaves you with plSQL or a non-SQL language.
If you're willing to drop this requirement (and perhaps implement it in post-processing, see below), this becomes doable. Start with a view of all the relevant date ranges:
CREATE VIEW date_ranges(
start_date, -- DATE
end_date -- DATE
) AS
SELECT DISTINCT date, DATE_ADD(date, INTERVAL 30 day)
FROM your_table;
Now, create a view of relevant counts:
CREATE VIEW product_counts(
person, -- INTEGER REFERENCES your_table(person)
count, -- INTEGER
start_date, -- DATE
end_date -- DATE
) AS
SELECT y.person,
sum(y.quantity),
r.start_date,
r.end_date
FROM date_ranges r
JOIN your_table y
ON y.date BETWEEN r.start_date AND r.end_date
GROUP BY y.person
HAVING sum(y.quantity) > 1;
For post-processing, you need to look at each row in the product_counts view and look up the purchase orders (rows of your_table) which correspond to it. Check whether you've seen any of those orders before (using a hash set), and if so, exclude them from consideration, reducing the count of the current item and possibly eliminating it entirely. This is best done in a procedural language other than SQL.
I have an accommodation booking engine, which follows these rules:
Users can book up to 7 rooms for any date.
Users must book only blocks of 2 days, 4 days and 6 days.
Users cannot book for Thursday (closed for cleaning).
Sometimes entire weeks will be unavailable due to corporate group bookings.
Here are some examples:
A 2 day block might be Friday and Saturday, Sunday and Monday or Tuesday and Wednesday.
A 4 day block might be Friday to Monday or Sunday to Wednesday.
A 6 day block might be from Friday to Wednesday.
The system has a simple table in the database that is a list of each date with the 7 rooms. The field for each room can be 0 for unavailable or 1 for available.
Table is called vg_booking_availability. Here is a snapshot.
I need a way to search the table against a users search selection.
The might search as any combination of 2, 4 or days and between 1 and 7 rooms.
I'm not sure if the solution is to do a database lookup on all dates and all rooms, then creating a multi-dimensional array and cross checking with the user's search is the way to go forward. And if it is how I would do this?
Here is a way to do this in SQL, for just two-day bookings, for room_1:
SELECT
avail1.date start_date,
"room_1" room_name
FROM vg_booking_availability avail1
/* This row is the next day to the first one */
INNER JOIN vg_booking_availability avail2 ON (avail1.date + 1 = avail2.date)
WHERE
avail1.room_1 = 0
AND avail2.room_1 = 0
/* Add in a clause to check future dates only here */
;
You could add all the rooms in this as bracketed OR statements, but I'd be inclined to run that as a separate query (otherwise you'd have to re-search your result in PHP to determine which room returned as available)
We are getting into a bit of trouble here because all the rooms are denormalised - they would be better in another table where they can be treated much more generically.
This example can be expanded by adding more aliased rows for 4-day and 7-day searches respectively. The unavailability of rooms on a Thursday (or whatever other rules) is not directly relevant to the problem, since you can just create future rows of availability (based on how far into the future people book) and then make rooms unavailable according to those rules. That's a separate (and trivial) problem.
I'd also be inclined to change this, so you use NULL as available and a foreign key to a customer table as unavailable. That will then give you useful information about why a room is unavailable, and will allow you to make it available again easily if a specific customer cancels their booking.
Lastly, this solution has the capacity for a large number of joins, and so testing this against data sets is essential. If you were to run this on a 10K row table it'd be fine, but it might not be if you have 1M rows (depending on your hardware and load, of course). So, once you have this working, I recommend you create a data generator (PHP is good for this) and ensure you can get the performance you require.
I am creating now a new booking system, the part of the private rooms was done and I have now the part of the Dorm (sharing room).
The system works like that:
Insert coming day + leaving day + how many persons in the dorm
the system checks in the DB (of MYSQL by the way) if the is free room.
now how it checks? in the DB has coulmn of dates - one date is person. for example we have a dorm with those dates: 05/08, 05/08, 05/08, 05/08, 06/08, 06/08, 06/08, 06/08, 07/08, 07/08, 07/08, 07/08 - so there is 4 persons in dorm 05-07/08. for example if this room with 10 persons there is 4 of 10. now if you insert 7 people you dont have place. (it will be 11 of 10).
The system returns available rooms.
Now I want to do the next thing:
If there is 2 dorms. all of them place to 10 persons. all of them with 8 persons of 10. now coming 4 persons and want to register.. it wont give them because it checks dorm-dorm.. I want to do that the system will calculate the free places (there is 10-8 + 10-8 = 4 in 2 dorms).
there is ideas?
My english its not very good I hope you will understand me,
THANK YOU VERY MUCH!
EDIT:
When I add dorm in the database:
ID | room_type| persons | dates
------------------------------------------------------------------------
X | Dorm |How many the dorm contain. not change.| of persons coming
if you have 5 dates same = there is 5 persons in the dorm in this date. for example.
OK so i'm typing this out of memory so it may need some fine-tuning but it should serve as a guide. Hope it helps.
1) First, let's create a temporary table that stores all of the ocuppied slots for any given room per date:
CREATE TEMPORARY TABLE room_availability
SELECT ID, DISTINCT dates, COUNT(dates) as full_slots FROM table
WHERE UNIX_TIMESTAMP(CAST(dates AS DATE)) - UNIX_TIMESTAMP(NOW()) > 0
GROUP BY dates ORDER BY ID;
2) Let's get all the available slots for any room:
SELECT table.*, table.persons - room_availability.full_slots as free_slots
FROM table INNER JOIN room_availability ON table.ID = room_availability.ID
where table.persons - room_availability.full_slots > 0
3) Finally, get rid of the temporary table and you're done
PS: the unix timestamp is there to help you list just the future dates.
I'm making a digg-like website that is going to have a homepage with different categories. I want to display the most popular submissions.
Our rating system is simply "likes", like "I like this" and whatnot. We basically want to display the submissions with the highest number of "likes" per time. We want to have three categories: all-time popularity, last week, and last day.
Does anybody know of a way to help? I have no idea how to go about doing this and making it efficient. I thought that we could use some sort of cron-job to run every 10 minutes and pull in the number of likes per the last 10 minutes...but I've been told that's pretty inefficient?
Help?
Thanks!
Typically Digg and Reddit-like sites go by the date of the submission and not the times of the votes. This way all it takes is a simple SQL query to find the top submissions for X time period. Here's a pseudo-query to find the 10 most popular links from the past 24 hours using this method:
select * from submissions
where (current_time - post_time) < 86400
order by score desc limit 10
Basically, this query says to find all the submissions where the number of seconds between now and the time it was posted is less than 86400, which is 24 hours in UNIX time.
If you really want to measure popularity within X time interval, you'll need to store the post and time for every vote in another table:
create table votes (
post foreign key references submissions(id),
time datetime,
vote integer); -- +1 for upvote, -1 for downvote
Then you can generate a list of the most popular posts between X and Y times like so:
select sum(vote), post from votes
where X < time and time < Y
group by post
order by sum(vote) desc limit 10;
From here you're just a hop, skip, and inner join away from getting the post data tied to the returned ids.
Do you have a decent DB setup? Can we please hear about your CREATE TABLE details and indices? Assuming a sane setup, the DB should be able to pull the counts you require fast enough to suit your needs! For example (net of indices and keys, that somewhat depend on what DB engine you're using), given two tables:
CREATE TABLE submissions (subid INT, when DATETIME, etc etc)
CREATE TABLE likes (subid INT, when DATETIME, etc etc)
you can get the top 33 all-time popular submissions as
SELECT *, COUNT(likes.subid) AS score
FROM submissions
JOIN likes USING(subid)
GROUP BY submissions.subid
ORDER BY COUNT(likes.subid) DESC
LIMIT 33
and those voted for within a certain time range as
SELECT *, COUNT(likes.subid) AS score
FROM submissions
JOIN likes USING(subid)
WHERE likes.when BETWEEN initial_time AND final_time
GROUP BY submissions.subid
ORDER BY COUNT(likes.subid) DESC
LIMIT 33
If you were storing "votes" (positive or negative) in likes, instead of just counting each entry there as +1, you could simply use SUM(likes.vote) instead of the COUNTs.
For stable list like alltime, lastweek, because they are not supposed to change really fast so that I think you should save the list in your cache with expiration time is around 1 days or longer.
If you concern about correct count in real time, you can check at every page view by comparing the page with lowest page in the cache.
All you need to do is to care for synchronizing between the cache and actual database.
thethanghn
Queries where the order is some function of the current time can become real performance problems. Things get much simpler if you can bucket by calendar time and update scores for each bucket as people vote.
To complete nobody_'s answer I would suggest you read up on the documentation (if you are using MySQL of course).