Calculate average from data only stored in MySQL on change - mysql

We have a database where we only store data values and timestamp (as a row in MySQL) when the data value changes. There are therefore no fixed interval between the timestamps in the database. The table looks something like this:
MySQLTimestamp Data
2014-10-01 18:01 1
2014-10-03 16:13 2
2014-10-07 15:45 1
2014-10-09 10:08 3
THE PROBLEM: We want to calculate the average over time assuming that the data value continues to be i.e. 2 until the value changes on the next row in the database.
A simple AVG won't do the trick because it will only calculate the average between the number of rows. This would not take in to count that a value can continue to be the same value for a long period of time before the next row states a change in data value.
Would really appreciate your help!

Self join and calculate the duration of date or time as weight to Data.
select
sum(data*duration_of_date)/sum(duration_of_date) as avg_over_date,
sum(data*duration_of_hour)/sum(duration_of_hour) as avg_over_hour,
sum(data*duration_of_sec)/sum(duration_of_sec) as avg_over_sec
from (
select
t1.MySQLTimestamp,
t1.data,
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) as next_tm,
datediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp) as duration_of_date,
TIME_TO_SEC(timediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp))/60/60 as duration_of_hour,
TIME_TO_SEC(timediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp)) as duration_of_sec
from
your_table t1
cross join
your_table t2
group by
t1.MySQLTimestamp,
t1.data
) as t
Use datediff to calculate days interval as weight. If you want hours or minutes as interval, you could use timediff and transfer the result to hours, minutes or seconds.
Here is the sql fiddle demo and the results:
AVG_OVER_DATE AVG_OVER_HOUR AVG_OVER_SEC
1.5 1.51887 1.5189
Another version by left join:
select
sum(data*duration_of_date)/sum(duration_of_date) as avg_over_date,
sum(data*duration_of_hour)/sum(duration_of_hour) as avg_over_hour,
sum(data*duration_of_sec)/sum(duration_of_sec) as avg_over_sec
from (
select
t1.MySQLTimestamp,
t1.data,
min(t2.MySQLTimestamp) as next_tm,
datediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp) as duration_of_date,
TIME_TO_SEC(timediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp))/60/60 as duration_of_hour,
TIME_TO_SEC(timediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp)) as duration_of_sec
from
your_table t1
left join
your_table t2
on
t1.MySQLTimestamp<t2.MySQLTimestamp
group by
t1.MySQLTimestamp,
t1.data
) as t

Related

A query for getting results separated by a date gap

ID
TIMESTAMP
1
2020-01-01 12:00:00
2
2020-02-01 12:00:00
3
2020-05-01 12:00:00
4
2020-06-01 12:00:00
5
2020-07-01 12:00:00
I am looking for a way to get records in a MySQL database that are within a certain range of each other. In the above example, notice that there is a month between the first two records, then a three month gap, before we see another three records with a month between.
What is a way to group these into two result sets, so I will get Ids 1, 2 and 3, 4, 5 A solution using days would be probably work the best as thats easier to modify.
You can use lag() and then logic to see where a gap is big enough to start a new set of records. A cumulative sum gives you the groups you want:
select t.*,
sum(case when prev_timestamp >= timestamp - interval 1 month then 0 else 1 end) over (order by timestamp) as grouping
from (select t.*,
lag(timestamp) over (order by timestamp) as prev_timestamp
from t
) t;
If you want to summarize this with a start and end date:
select min(timestamp), max(timestamp)
from (select t.*,
sum(case when prev_timestamp >= timestamp - interval 1 month then 0 else 1 end) over (order by timestamp) as grouping
from (select t.*,
lag(timestamp) over (order by timestamp) as prev_timestamp
from t
) t
) t
group by grouping;
For example, the following query:
select group_concat(ID)
from (
select w1.ID,w1.TS,w2.ID flag
from work1 w1 left outer join work1 w2
on timestampdiff(month,w2.TS,w1.TS)=1
order by w1.ID
) w
group by
case when flag is null then #str:=ID else #str end
See db fiddle

How to calculate Sum of values per Quarters based on task start date and end date in MySQL?

I have an Activity table with Type,StartDate,EndDate and Amount.
I want to calculate the sum of amounts grouped by Type for all the quarters using start date and enddates.
For example, if I have StartDate and EndDate falls between Jan to Mar then sum of all the records' amounts
related to that quarter should be calculated.
And I want to do the same for the remaining records.
CREATE TABLE activity
(id int(11)
,Type varchar(10)
,StartDate date
,EndDate date
,Amount int(11)
);
INSERT INTO activity VALUES
(1,'Type1','2021-01-15','2021-02-25',10000),
(2,'Type1','2021-01-25','2021-02-25',10000),
(3,'Type2','2021-08-05','2021-09-25',15000),
(4,'Type3','2021-10-15','2021-12-25',5000);
This is the expected output.
EXPECTED OUTPUT:
Type T1 T2 T3 T4
Type1 20000 0 0 0
Type2 0 0 15000 0
Type3 0 0 0 5000
T1,T2,T3,T4 are quarters of Year
T1 -> Jan TO Mar
T2 -> April TO June
T3 -> July TO September
T4 -> October TO December
I have tried a query. I have given that query in the online editor.
This is the Online editor link with sample data.
Is there anything I'm missing in my query to fetch the correct response?
You may handle this requirement with the help of a calendar table, which in this case maintains the date ranges for each quarter:
SELECT
a.Type,
SUM(CASE WHEN quarter = 'T1' THEN a.Amount ELSE 0 END) AS T1,
SUM(CASE WHEN quarter = 'T2' THEN a.Amount ELSE 0 END) AS T2,
SUM(CASE WHEN quarter = 'T3' THEN a.Amount ELSE 0 END) AS T3,
SUM(CASE WHEN quarter = 'T4' THEN a.Amount ELSE 0 END) AS T4
FROM Activity a
INNER JOIN
(
SELECT 'T1' AS quarter, '2021-01-01' AS QuarterStart, '2021-04-01' AS QuarterEnd UNION ALL
SELECT 'T2', '2021-04-01', '2021-07-01' UNION ALL
SELECT 'T3', '2021-07-01', '2021-10-01' UNION ALL
SELECT 'T4', '2021-10-01', '2022-01-01'
) q
ON a.StartDate < q.QuarterEnd AND a.EndDate >= q.QuarterStart
GROUP BY
a.Type;
Demo
Note that I changed your sample data, because the current Type 2 data actually spans both the 2nd and 3rd quarters, and you did not make it clear how the accounting should work in this case. So, I changed the end date for that data to 2020-06-30 to ensure that it only falls in the 2nd quarter.

Multiple Select Subquery Count based on Hour of Day, Would Like to Add Table/Column

Right now, I have a multiple select subquery that is grabbing data based on hour of the day that's a count. What I want to do now, is to introduce another table into that query, and count based on an id as well as the datetime in the original table.
What I have right now is:
select
(
select count(a_date)
from t1
where d_date
between '2013-01-07 00:00:00' and '2013-01-07 00:59:59'
) AS '00:00 to 00:59',
(
select count(a_date)
from t1
where d_date
between '2013-01-07 01:00:00' and '2013-01-07 01:59:59'
) AS '01:00 to 01:59'
and so on, till the end of the day.
I have another query that's giving me the count based on the id and datetime, but there's only two columns, one which is showing the c_name and the other showing the count for the hour.
Ex.
select t2.c_name, count(t1.a_date)
from t2 join t1
on t2.t1_key = t1.t2_key
where t1.d_date
between '2013-01-07 00:00:00' and '2013-01-07 00:59:59'
group by t2.c_id
Basically, I'd like to combine these two queries into one that can show the c_name and all of the hours of the day.
Any suggestions?
I would look into using the CASE statement.
Try something like this (adding your additional 23 columns):
select c_name,
SUM(case when HOUR(d_date) = 0 then 1 else 0 end) '00:00 to 00:59',
SUM(case when HOUR(d_date) = 1 then 1 else 0 end) '01:00 to 01:59'
from t2
join t1 on t2.t1_key = t1.t2_key
group by c_name
And here is the SQL Fiddle.
You just need to add your WHERE criteria for d_date -- something like:
where d_date between '2013-01-07 00:00:00' and '2013-01-07 23:59:59'
or
where Date(d_date) = '2013-01-07'
Good luck!

Select single column multiple times at differents point in time

I have a simple table with 4 columns - ID, Date, Category, Value.
I have 5 distinct categories that have certain values daily. I would like to select value column at different points in time and display result along with the appropriate category.
This is the code that I'm using:
select
Category,
case when date=DATE_SUB(CURDATE(),INTERVAL 1 DAY) then value else 0 end as Today,
case when date=DATE_SUB(CURDATE(),INTERVAL 1 MONTH) then value else 0 end as "Month Ago",
case when date=DATE_SUB(CURDATE(),INTERVAL 1 Year) then value else 0 end as "Year Ago"
from table
group by category
It's not working. I'm using mysql database but will run the query in SSRS through an ODBC connection.
The problem with your query is that, as written, the case statements need to be embedded in aggregation functions:
select Category,
avg(case when date=DATE_SUB(CURDATE(),INTERVAL 1 DAY) then value end) as Today,
avg(case when date=DATE_SUB(CURDATE(),INTERVAL 1 MONTH) then value end) as "Month Ago",
avg(case when date=DATE_SUB(CURDATE(),INTERVAL 1 Year) then value end) as "Year Ago"
from table
group by category
I chose "avg" since this seems reasonable if there are multiple values and the "value" column is numeric. You might prefer min() or max() to get other values.
Also, I removed the "else 0" clause, so you will see NULL rather than 0 when there is no value.
This type of query is best done with three separate queries:
SELECT 'Today' AS `When`, Category, value FROM `table`
WHERE date = DATE_SUB(CURDATE(),INTERVAL 1 DAY)
UNION ALL
SELECT 'Month Ago' AS `When`, Category, value FROM `table`
WHERE date = DATE_SUB(CURDATE(),INTERVAL 1 MONTH)
UNION ALL
SELECT 'Year Ago' AS `When`, Category, value FROM `table`
WHERE date = DATE_SUB(CURDATE(),INTERVAL 1 YEAR)
try something like this:
SELECT
t1.Category, t1.Value, t2.Value, t3.Value
FROM YourTable t1
LEFT OUTER JOIN YourTable t2 ON t1.Category=t2.Category
AND Date=DATE_SUB(CURDATE(),INTERVAL 1 Month)
LEFT OUTER JOIN YourTable t3 ON t1.Category=t3.Category
AND Date=DATE_SUB(CURDATE(),INTERVAL 1 Year)
WHERE Date=DATE_SUB(CURDATE(),INTERVAL 1 DAY)
this assumes that you have only one row per your interval. if you have multiple rows per interval, you need to decide which value you want to show for that interval (min, max, etc). you then need to aggergate your multiple rows. if this is the case the OP should provide some sample data and expected query output so testing is possible.

Very complex structuration of a MySQL query

please take a look of this query:
SELECT DATE(datetime), COUNT(1) as numVisits
FROM ".table_stats."
WHERE type='profile_visit'
AND user_url = '".$_GET['ref']."'
AND id_user='".$_SESSION['user_code']."'
GROUP BY DATE(DATE_SUB(datetime, INTERVAL 1 DAY))
This query counts the number of times that type is equal to 'profile_visit' by each date, as a result it gives me two rows (DATE(datetime), numVisits). This is a screen capture of the table table_stats:
Table_Stats
Ok, until now you can understand that every time a user comes to the site a new element is inserted on the table with type=profile_visit and the datetime field with the date and time of the visit, thats why i use a GROUP BY DATE(datetime) to count the total number of visits by day.
Here comes the complex part, when the type field is equal to 'click' and the origin is 'imp' that means that a user hits a particular button on the page, i will like to know how many times that button was clicked (no matter the ip) by day, just like i did with the profile visits.
I can make two querys, one to know the total visits (like the one before) and another similar just by grouping by datetime when type is 'click' and origin is 'imp'.
The problem is that i will like to make this just in one call in order to count the total visits by date in the row NumVisits like i did before and a new row call NumClick with the total of clicks made. This is why i dont want more calculations on my php server, if its possible will be great to make all the calculation on the sql server.
So finally, if you call this query to the table:
SELECT DATE( DATETIME ) , COUNT( 1 ) AS numVisits
FROM stats_ram
WHERE TYPE = 'profile_visit'
AND user_url = 'xxx'
AND id_user = '88e91'
GROUP BY DATE( DATE_SUB( DATETIME, INTERVAL 1
DAY ) )
LIMIT 0 , 30
You will get:
DATE(datetime) numVisits
2011-11-16 7
How can i add another row with the total type=click AND origin=imp made by DATE(datetime)???
Thanks for any help!!!
SELECT
DATE(DATETIME),
SUM(CASE WHEN type = 'profile_visit' THEN 1 ELSE 0 END) AS numVisits,
SUM(CASE WHEN type = 'click' AND origin = 'imp' THEN 1 ELSE 0 END) numClicks
FROM stats_ram
WHERE user_url = 'xxx'
AND id_user = '88e91'
GROUP BY DATE(DATE_SUB(DATETIME, INTERVAL 1 DAY))
LIMIT 0, 30