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

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.

Related

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

MySQL Calculate minimum price of a service with varying starting time

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.

MySQL query based on time range, group users, and sum values over a sliding window

I want to create a new Table B based on the information from another existing Table A. I'm wondering if MySQL has the functionality to take into account a range of time and group column A values then only sum up the values in a column B based on those groups in column A.
Table A stores logs of events like a journal for users. There can be multiple events from a single user in a single day. Say hypothetically I'm keeping track of when my users eat fruit and I want to know how many fruit they eat in a week (7days) and also how many apples they eat.
So in Table B I want to count for each entry in Table A, the previous 7 day total # of fruit and apples.
EDIT:
I'm sorry I over simplified my given information and didn't thoroughly think my example.
I'm initially have only Table A. I'm trying to create Table B from a query.
Assume:
User/id can log an entry multiple times in a day.
sum counts should be for id between date and date - 7 days
fruit column stands for the total # of fruit during the 7 day interval ( apples and bananas are both fruit)
The data doesn't only start at 2013-9-5. It can date back 2000 and I want to use the 7 day sliding window over all the dates between 2000 to 2013.
The sum count is over a sliding window of 7 days
Here's an example:
Table A:
| id | date-time | apples | banana |
---------------------------------------------
| 1 | 2013-9-5 08:00:00 | 1 | 1 |
| 2 | 2013-9-5 09:00:00 | 1 | 0 |
| 1 | 2013-9-5 16:00:00 | 1 | 0 |
| 1 | 2013-9-6 08:00:00 | 0 | 1 |
| 2 | 2013-9-9 08:00:00 | 1 | 1 |
| 1 | 2013-9-11 08:00:00 | 0 | 1 |
| 1 | 2013-9-12 08:00:00 | 0 | 1 |
| 2 | 2013-9-13 08:00:00 | 1 | 1 |
note: user 1 logged 2 entries on 2013-9-5
The result after the query should be Table B.
Table B
| id | date-time | apples | fruit |
--------------------------------------------
| 1 | 2013-9-5 08:00:00 | 1 | 2 |
| 2 | 2013-9-5 09:00:00 | 1 | 1 |
| 1 | 2013-9-5 16:00:00 | 2 | 3 |
| 1 | 2013-9-6 08:00:00 | 2 | 4 |
| 2 | 2013-9-9 08:00:00 | 2 | 3 |
| 1 | 2013-9-11 08:00:00 | 2 | 5 |
| 1 | 2013-9-12 08:00:00 | 0 | 3 |
| 2 | 2013-9-13 08:00:00 | 2 | 4 |
At 2013-9-12 the sliding window moves and only includes 9-6 to 9-12. That's why id 1 goes from a sum of 2 apples to 0 apples.
You need years in your data to be able to use date arithmetic correctly. I added them.
There's an odd thing in your data. You seem to have multiple log entries for each person for each day. You're assuming an implicit order setting the later log entries somehow "after" the earlier ones. If SQL and MySQL do that, it's only by accident: there's no implicit ordering of rows in a table. Plus if we duplicate date/id combinations, the self join (read on) has lots of duplicate rows and ruins the sums.
So we need to start by creating a daily summary table of your data, like so:
select id, `date`, sum(apples) as apples, sum(banana) as banana
from fruit
group by id, `date`
This summary will contain at most one row per id per day.
Next we need to do a limited cross product self-join, so we get seven days' worth of fruit eating.
select --whatever--
from (
-- summary query --
) as a
join (
-- same summary query once again
) as b
on ( a.id = b.id
and b.`date` between a.`date` - interval 6 day AND a.`date` )
The between clause in the on gives us the seven days (today, and the six days prior). Notice that the table in the join with the alias b is the seven day stuff, and the a table is the today stuff.
Finally, we have to summarize that result according to your specification. The resulting query is this.
select a.id, a.`date`,
sum(b.apples) + sum(b.banana) as fruit_last_week,
a.apples as apple_today
from (
select id, `date`, sum(apples) as apples, sum(banana) as banana
from fruit
group by id, `date`
) as a
join (
select id, `date`, sum(apples) as apples, sum(banana) as banana
from fruit
group by id, `date`
) as b on (a.id = b.id and
b.`date` between a.`date` - interval 6 day AND a.`date` )
group by a.id, a.`date`, a.apples
order by a.`date`, a.id
Here's a fiddle: http://sqlfiddle.com/#!2/670b2/15/0
Assumptions:
one row per id/date
the counts should be for id between date and date - 7 days
"fruit" = "banana"
the "date" column is actually a date (including year) and not just month/day
then this SQL should do the trick:
INSERT INTO B
SELECT a1.id, a1.date, SUM( a2.banana ), SUM( a2.apples )
FROM (SELECT DISTINCT id, date
FROM A
WHERE date > NOW() - INTERVAL 7 DAY
) a1
JOIN A a2
ON a2.id = a1.id
AND a2.date <= a1.date
AND a2.date >= a1.date - INTERVAL 7 DAY
GROUP BY a1.id, a1.date
Some questions:
Are the above assumptions correct?
Does table A contain more fruits than just Bananas and Apples? If so, what does the real structure look like?

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;