MySQL Calculate minimum price of a service with varying starting time - mysql

Suppose I have a table like this:
Prices
-----------------------------------------------------------
| service_id | starting_time | ending_time | price_per_hour |
-----------------------------------------------------------
| 1 | 08:00:00 | 10:00:00 | 90 |
-----------------------------------------------------------
| 1 | 10:00:00 | 11:00:00 | 50 |
-----------------------------------------------------------
| 1 | 11:00:00 | 15:00:00 | 80 |
-----------------------------------------------------------
Now a user of my web page wants to find service providers with lowest price. They tell me starting time of the service, duration and (here's the tricky part) tolerance for the starting time. Say, service should start at 10am, should last 2 hours but they are willing to come one hour earlier or later (+/- 1 hour to starting time).
I cannot seem to come up with an idea of one query to get me the lowest possible price, depending if the service starts at 9am, 10am or 11am. starting time tolerance may vary. It's easy, if there's no tolerance, you just select all rows from prices that apply to the service time, calculate their share in price and then sum the subquery. But how do I introduce the variable starting time factor into that?
This is a simplified query to calculate price knowing exact starting point of the service. :service_start and service_end are placeholders.
SELECT SUM(t1.price) FROM
(SELECT price_per_hour * TIME_TO_SEC( TIMEDIFF( IF(ending_time < :service_end,
ending_time, :service_end), IF(starting_time < :service_start, :service_start,
starting_time) ) ) / 3600 AS price FROM prices WHERE service_id = 1 AND NOT
(starting_time >= :service_end OR ending_time <= :service_start)) AS t1
And now I have no idea where to go on from here. How to introduce variability of service starting time.
EDIT:
Ok, I think I was too lazy to think deeper about that before. I believe it can't be done using MySQL. I've broken it down to mathematical equasions that would solve it and I believe it's not something you can implement in MySQL (two equasions with many variables). I think it's going to have to be some brute force calculations on PHP side to get what I want.
In this instance I would want to minimise x * 90 + y * 50 + z * 80, where x, y, z is number of hours taken from each price range given some conditions, such as x,y,z%0,5 = 0 (half an hour step), x+y+z = 2, etc.
EDIT 2:
I'll describe the issue in more detail, as it seems I wansn't specific enough:
I have three tables that are relevant to this issue:
services
-----------------------------------------------------------------------------------------------
| service_id | service_type_id | provider_id | name | starting_time | ending_time |
-----------------------------------------------------------------------------------------------
| 2 | 1 | 3 | 2014-02-07 08:00:00 | 2014-02-07 23:00:00 |
-----------------------------------------------------------------------------------------------
prices (more a price list)
------------------------------------------------------------------------------
| service_type_id | provider_id | starting_time | ending_time | price_per_hour |
------------------------------------------------------------------------------
| 1 | 3 | 08:00:00 | 10:00:00 | 90 |
------------------------------------------------------------------------------
| 1 | 3 | 10:00:00 | 11:00:00 | 50 |
------------------------------------------------------------------------------
| 1 | 3 | 11:00:00 | 15:00:00 | 80 |
------------------------------------------------------------------------------
| 1 | 3 | 15:00:00 | 23:00:00 | 70 |
------------------------------------------------------------------------------
service_slots
-----------------------------------------------------------------------------------
| service_slot_id | service_id | starting_time | ending_time | booked |
-----------------------------------------------------------------------------------
| 1 | 2 | 2014-02-07 08:00:00 | 2014-02-07 09:00:00 | 1 |
| 2 | 2 | 2014-02-07 09:00:00 | 2014-02-07 17:00:00 | 0 |
| 3 | 2 | 2014-02-07 17:00:00 | 2014-02-07 19:00:00 | 1 |
| 4 | 2 | 2014-02-07 19:00:00 | 2014-02-07 21:00:00 | 1 |
| 5 | 2 | 2014-02-07 21:00:00 | 2014-02-07 23:00:00 | 0 |
-----------------------------------------------------------------------------------
The first table contains service schedule of a provider. The provider says they're offering the service between 8am and 11pm. You can book as much time off that service as you want (think tennis court booking).
They also have a pricing for those services. This pricing says that every day (during price list validity), the service costs this and that between this and that hour. You can book the service across price ranges. That is, if the price list says that the service costs 90 between 8am and 10am and 50 between 10am and 11am, it's perfectly fine for you to book between 9 and 11. Then two price ranges apply to your booking.
The third table holds information on which chunk (slot) of a particular service has been booked.
What I do is first query service_slots for available chunks then I need to get the price for the entire duration of the chunk, knowing that price may change in the middle of the chunk's duration.
I hope I mad myself more clear this time :)

HEAVILY EDITED, SO OLD POST IS GONE!
First, check out my codes.
My test table :
CREATE TABLE IF NOT EXISTS price(
id INT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
pid INT(20) NOT NULL,
staring_time DATETIME,
ending_time DATETIME,
price INT(20) NOT NULL
);
And the queries:
SET #start_pref = '2014-02-07 02:00:00';
SET #end_pref = '2014-02-07 04:00:00';
SELECT
*,
(
SELECT id
FROM price
WHERE
pid = p.pid AND
starting_time <= #start_pref AND
ending_time > #start_pref
) AS id_first,
(
SELECT id
FROM price
WHERE
pid = p.pid AND
ending_time >= #end_pref AND
starting_time < #end_pref
) AS id_last,
(
SELECT IF(
id_first = id_last,
(
SELECT HOUR(TIMEDIFF(#end_pref, #start_pref)*price)
FROM price
WHERE id = id_first AND pid = p.pid
),
(
(
SELECT HOUR(TIMEDIFF(ending_time, #start_pref)*price)
FROM price
WHERE id = id_first AND pid = p.pid
) + (
SELECT HOUR(TIMEDIFF(#end_pref, starting_time)*price)
FROM price
WHERE id = id_last AND pid = p.pid
)
)
) FROM price WHERE pid = p.pid AND id = id_first
) AS total
FROM price p
WHERE
starting_time <= #start_pref AND #start_pref < ending_time
ORDER BY total ASC;
This only works for, when #start_pref and #end_pref covers 1 - 2 successive time-sched.
However, it does not work for, when the pref time covers more than 2 records.
Example:
Starting pref time: 1am
Ending pref time: 5am
For one specific provider
1am - 2am - $10
2am - 3am - $20
4am - 5am - $30
This is because I didn't think of it beforehand when I started and finished my query.
Now if it is two or more, you'd need more subquery to find those id then add them on my SELECT IF( subquery.
Also, notice how I have/added the column id? So that I can tell easily if that id belongs to the same pid on the current query.
PID is Provider ID.
The whole sql script.

Related

SQL: Get daily/weekly/monthly consumption of electric meter by SQL-Query

I have a MySQL-table (called Item1) with two columns:
"Time" [datetime] and "Value" [float]. Every 5 Minutes the total amount of my electric meter is appended to the data table, like that:
| Time | Value |
| 2018-07-22 21:55:00 | 202660.199951 |
| 2018-07-22 22:00:00 | 202673.899902 |
| 2018-07-22 22:05:00 | 202684.699951 |
| 2018-07-22 22:10:00 | 202691.534534 |
| 2018-07-22 22:15:00 | 202710.334253 |
How can I calculate the power consumption per day / week / month with one sql-query, getting the results as a new table. I tried:
SELECT * FROM Item1 WHERE HOUR(Time)=0 AND MINUTE(Time)=0
which gives me the values every midnight, but how can i subtract these values?
You can use a join:
select date(i.time), i.value, iprev.value,
(i.value - iprev.value) as diff
from item_1 i left join
item_1 iprev
on date(iprev.time) = date(i.time) - interval 1 day and
hour(iprev.time) = 0 and minute(iprev.time) = 0
where hour(i.time) = 0 and minute(i.time) = 0
group by date(i.time), i.value, iprev.value;
You would change the timeframe for iprev to get differences of weeks or months.

mysql - Calculating profit of sales between dates

I have a table that looks like this:
+--------+---------------------+-------+--------+-----------+
| PartNo | Date | Inv | Retail | Wholesale |
+--------+---------------------+-------+--------+-----------+
| 1 | 2018-05-12 00:00:00 | 15 | $100 | $90 |
| 2 | 2018-05-12 00:00:00 | 20 | $200 | $150 |
| 3 | 2018-05-12 00:00:00 | 25 | $300 | $200 |
| 1 | 2018-05-13 00:00:00 | 10 | $95 | $90 |
| 2 | 2018-05-14 00:00:00 | 15 | $200 | $150 |
| 3 | 2018-05-14 00:00:00 | 20 | $300 | $200 |
+--------+---------------------+-------+--------+-----------+
And I want it to look like this with a Mysql query:
+--------+------+--------+
| PartNo | Sold | Profit |
+--------+------+--------+
| 1 | 5 | $25 |
| 2 | 5 | $250 |
| 3 | 5 | $500 |
+--------+------+--------+
I need to group by PartNo while calculating the difference between totals and profits over a date range.
The unit profit has to be calculated by subtracting the wholesale from retail on the last day (or record) of the date range.
I feel like this should be easy but the differences over the date ranges are confusing me and handling records within the date range that don't start or end exactly on the date range input are losing me.
Any help would be super appreciated.
Thank you.
You can look up the situation at the start and at the end of the period If no start situation is found, assume no stock. If no end situation is found, that means no sales during the period.
For example for the period starting 2018-05-13 and ending 2018-05-14:
select parts.PartNo
, coalesce(FirstSale.Total, 0) - coalesce(LastSale.Total, FirstSale.Total, 0) as Sold
, (coalesce(FirstSale.Total, 0) - coalesce(LastSale.Total, FirstSale.Total, 0)) *
coalesce(LastSale.Retail - LastSale.Wholesale, 0) as Profit
from (
select PartNo
, max(case when Date < '2018-05-13' then Date end) as FirstEntry
, max(case when Date <= '2018-05-14' then Date end) as LastEntry
from Sales
group by
PartNo
) parts
left join
Sales FirstSale
on FirstSale.PartNo = parts.PartNo
and FirstSale.Date = parts.FirstEntry
left join
Sales LastSale
on LastSale.PartNo = parts.PartNo
and LastSale.Date = parts.LastEntry
Example at SQL Fiddle.
SELECT c.partno as partno,MAX(c.inv)-MIN(c.inv) as sold,SUM(CASE WHEN c.date = c.last_date THEN profit else 0 END)*(MAX(c.inv)-MIN(c.inv)) as profit
FROM (SELECT partno,date,inv,retail-wholesale as profit,MAX(date) OVER (partition by partno) AS last_date FROM test1)c
GROUP BY c.partno
ORDER BY c.partno;
Using the window function, first append a new column to track the max date for each partno. So the inner query inside FROM will produce rows like these with one column added to the the original dataset,
| 1 | 2018-05-12 00:00:00 | 15 | $100 | $90 | **2018-05-13 00:00:00** |
The highlighted field is the one added to the dataset which is the last date in the date range for that part number!
Now from this result, we can pull out profit by checking for the row in which date column is equal to the new column we appended, which is essentially calculating the profit for the last date by subtracting wholesale from retail and multiplying with items sold.
PS : The logic for items sold is grouping by partno and subtracting MIN(Inv) from MAX(Inv)
Link to SQL Fiddle

SQL calculate timediff between intervals including a time from a separate table

I have 2 different tables called observations and intervals.
observations:
id | type, | start
------------------------------------
1 | classroom | 2017-06-07 16:18:40
2 | classroom | 2017-06-01 15:12:00
intervals:
+----+----------------+--------+------+---------------------+
| id | observation_id | number | task | time |
+----+----------------+--------+------+---------------------+
| 1 | 1 | 1 | 1 | 07/06/2017 16:18:48 |
| 2 | 1 | 2 | 0 | 07/06/2017 16:18:55 |
| 3 | 1 | 3 | 1 | 07/06/2017 16:19:00 |
| 4 | 2 | 1 | 3 | 01/06/2017 15:12:10 |
| 5 | 2 | 2 | 1 | 01/06/2017 15:12:15 |
+----+----------------+--------+------+---------------------+
I want a view that will display:
observation_id | time_on_task (total time in seconds where task = 1)
1 | 13
2 | 5
So I must first check to see if the first observation has task = 1, if it is I must record the difference between the current interval and the start from the observations table, then add that to the total time. From there on after if the task = 1, I just add the time difference from the current interval and previous interval.
I know I can use:
select observation_id, TIME_TO_SEC(TIMEDIFF(max(time),min(time)))
from your_table
group by observation_id
to find the total time in the intervals table between all intervals outside of the first one.
But
1. I need to only include interval times where task = 1. (The endtime for the interval is the one listed)
2. Need the timediff between the first interval and initial start (from observations table) if number = 1
I'm still new to the Stackoverflow community, but you could try to use SQL
LAG() function
For instance
Using an outer Select Statement
SELECT COl1, COL2, (DATEDIFF(mi, Inner.prevtime, Currentdatetime,0)) AS Difference
FROM ( SELECT LAG(Created_Datetime) OVER (ORDER BY Created_Datetime) AS prevtime
From MyTable
Where SomeCondition) as Inner
Sorry if it looks goofy, still trying to learn to format code here.
https://explainextended.com/2009/03/12/analytic-functions-optimizing-lag-lead-first_value-last_value/
Hope it helps

Convert a table with infrequent values for specific days to a table with all days in a period

I got the following MySQL table storing a value to a specific date:
| date | value |
======================
| 2009-03-29 | 0.5 |
| 2010-01-30 | 0.55 |
| 2011-08-12 | 0.67 |
Now I need another table that maps those values to every day in the range of the first and last date in the table (fill the gaps). So in the end it should look like this:
| date | value |
======================
| 2009-03-29 | 0.5 |
| 2009-03-30 | 0.5 |
| 2009-04-01 | 0.5 |
| ... | ... |
| 2010-01-30 | 0.55 |
| 2010-02-01 | 0.55 |
| 2010-02-02 | 0.55 |
| ... | ... |
| 2011-08-10 | 0.55 |
| 2011-08-11 | 0.55 |
| 2011-08-12 | 0.67 |
Is there a way to solve this problem in a SQL query?
From the other case, and what you appear to be doing is to show the trend of values across some period of time, even though a value may not be provided every day... So, you may have from Oct 1, 2011 to Dec 1, 2011 (or any other date range for that matter).
You can easily SIMULATE an entire range of dates by using mysql variables and joining to ANY table (where I have "AnyOtherTableInYourDatabase") that has at least the number of days you would want to look back at. In the example for just the dates, I am starting with whatever the current date is, and running back say... 60 days. You could also change the "starting date" with something else and go back/forward as needed.
select JustDates.OneDate
from
( SELECT
#r:= date_add(#r, interval 1 day ) OneDate
FROM
(select #r := current_date()) vars,
AnyOtherTableInYourDatabase limit 30 ) JustDates
Now, to get the most recent "reading", tack on a column via a select/from. THEN, re-join THAT result set to the original readings table to get the value in question
select
PreQuery.OneDate,
PreQuery.LatestReading,
Y2.Value
from
( select JustDates.OneDate,
( select max( YRT.Date )
from YourReadingsTable YRT
where YRT.Date <= JustDates.OneDate ) as LatestReading
from
(SELECT #r:= date_add(#r, interval 1 day ) OneDate
FROM (select #r := current_date()) vars,
AnyOtherTableInYourDatabase limit 30 ) JustDates ) PreQuery
JOIN YourReadingsTable Y2
on PreQuery.LatestReading = Y2.Date

MySQL Query for obtaining count per hour

I need to obtain a count of how many actions occur on an hourly basis.
My database keeps a log by timestamp of the actions.
I understand that I could do a
SELECT table.time COUNT(table.time) from table t group by t.time
However, there are periods of time where no actions take place. For example if I have 10 actions during 8:00AM, no actions during 9:00AM and 4 actions during 10:00AM,
That query would return:
8:00 10
10:00 4
Skipping 9:00AM because it has no entries.
How can I make a query that will take into account 0-count entries.
I also need to make the same query for entries by days of the week, but I assume that by answering the first question I can easily handle the other.
Thanks in advance!
you can solve this by creating a table that will contain 24 values for hours (00:00, 01:00 etc) and perform a left (or right) join with it and your table allowing nulls so you will have all 24 rows even if your table contains 0 rows at all, then group by should work fine.
Dont forget to truncate everything but hour from your table when you perform join so result of func you call & perform join on can be equal to value of this help table.
you can use following query to do the job after populating testtime table with 24 test_time values
select test_time,sum(sign(coalesce(idFromYourTable,0))) as count from testtime
left join yourTable on test_time=hour(yourTableTime)
group by test_time
This will provide 0 as count if there are no values matching row from test table, while having count(*) will provide 24 rows with 1s instead of 0s even if your table is empty, also if there is just 1 row in your table it is impossible to distinguish the difference between 0 rows cause results will look the same for following 2 different rows
23 NULL
23 1
cause will both provide same result row count equal to 1 , while sum technique treats this rows differently
A simple way to do the same without creating any table would be as follows
SELECT
HOUR(time) 'hr', COUNT(DISTINCT id)
FROM schema.table
WHERE time BETWEEN '2016-01-23 00:00:00' AND '2016-01-24 00:00:00'
GROUP BY hr;
Hour function in mysql gives the hour from a datetime or timestamp data type. This query is grouping them based on particular hour withing the date range. Distinct is not mandatory but if you are looking for unique order or id in the time range per hour. This is the query.
You can use Valentin's solution but without the need to create a table of time slots. The idea is to generate the time slots on the fly and then JOIN them to your table as he suggests.
WITH RECURSIVE timeSlots (t) AS (
SELECT 0
UNION ALL
SELECT t + 3600 FROM timeSlots WHERE t < (23 * 3600)
)
SELECT t, TIME_FORMAT(SEC_TO_TIME(t), '%H:%i:%s') FROM timeSlots;
Gives:
+-------+----------+
| t | Time |
+-------+----------+
| 0 | 00:00:00 |
| 3600 | 01:00:00 |
| 7200 | 02:00:00 |
| 10800 | 03:00:00 |
| 14400 | 04:00:00 |
| 18000 | 05:00:00 |
| 21600 | 06:00:00 |
| 25200 | 07:00:00 |
| 28800 | 08:00:00 |
| 32400 | 09:00:00 |
| 36000 | 10:00:00 |
| 39600 | 11:00:00 |
| 43200 | 12:00:00 |
| 46800 | 13:00:00 |
| 50400 | 14:00:00 |
| 54000 | 15:00:00 |
| 57600 | 16:00:00 |
| 61200 | 17:00:00 |
| 64800 | 18:00:00 |
| 68400 | 19:00:00 |
| 72000 | 20:00:00 |
| 75600 | 21:00:00 |
| 79200 | 22:00:00 |
| 82800 | 23:00:00 |
+-------+----------+
If you want to change your time slot buckets you can just fiddle with the generator arithmetic rather than having to create another table.
An alternative is to use LIMIT:
WITH RECURSIVE timeSlots (t) AS (
SELECT 0
UNION ALL
SELECT t + 3600 FROM timeSlots LIMIT 24
)
SELECT t, TIME_FORMAT(SEC_TO_TIME(t), '%H:%i:%s') AS Time FROM timeSlots;