How do I calculate the hourly 4 week moving average by weekday? - mysql

Given a table with a datetime column, I want to get the 4 week moving average number of entries per hour with the day of week for each result.
So for instance, between Oct 1st and Oct 13th, I'd like to get back a result that shows the 4 week rolling average for number of rows grouped by hour and dayofweek.
What I have so far gets me the 4 week hourly totals, but not rolling totals:
SELECT
DAYOFWEEK(start_time) as DOW,
date_format( start_time, '%H' ) as 'HOUR',
count( * ) as 'count'
FROM mytable
WHERE start_time >='2017-08-01' and start_time <= '2017-08-29'
GROUP BY DAYOFWEEK(start_time),date_format( start_time, '%H' )

Here is a partially tested approach.
It uses date parameters to ensure consistency of the where clauses. Other parameters are also used to control the hourly bucket (I used 3 in limited testing), and the number of weeks (I used 0 in testing as I had a very small set of rows).
The first subquery is used to produce "ranges" which when joined to the source rows will place those rows into each "rolling n hourly range". Those ranges are defined by using a date_format output YYYYMMDDHH which are strings, and then the data is also forced to this same string format for joining, so if being used on a large table this may cause performance issues (yes, not sargable, I don't like it either).
This solution may be seen working here at SQL Fiddle
Schema Setup:
CREATE TABLE `myTable` (
`id` mediumint(8) unsigned NOT NULL auto_increment,
`start_time` datetime,
PRIMARY KEY (`id`)
) AUTO_INCREMENT=1;
INSERT INTO MyTable
(`start_time`)
VALUES
('2017-08-01 00:01:00'),
('2017-08-01 00:15:00'),
('2017-08-01 00:29:00'),
## more here, 3 rows per hour over a narrow date range
('2017-08-03 08:01:00'),
('2017-08-03 08:15:00'),
('2017-08-03 08:29:00')
;
Query
set #start_time := '2017-08-02';
set #num_hrs := 4; -- controls length of rolling period e.g. 4 hours each
set #num_weeks := 4; -- controls the date date
set #end_time := date_add(#start_time, INTERVAL ((7 * #num_weeks)+1) DAY);
SELECT
DOW
, hour_of_day
, COUNT(*) period_count
, (COUNT(*) * 1.0) / #num_hrs rolling_av
FROM (
## build a set of ranges in YYYYMMDDHH format differing by the wanted number of hours
SELECT
id
, DATE_FORMAT(date_add(start_time, INTERVAL (#num_hrs*-1) HOUR), '%Y%m%d%H') as range_start
, DATE_FORMAT(start_time, '%Y%m%d%H') as range_end
FROM mytable
WHERE start_time >= #start_time and start_time < #end_time
) R
INNER JOIN (
SELECT
start_time
, DAYOFWEEK(start_time) as DOW
, date_format(start_time, '%H' ) as hour_of_day
FROM MyTable
WHERE start_time >= #start_time and start_time < #end_time
) T ON DATE_FORMAT(T.start_time, '%Y%m%d%H') >= R.range_start
AND DATE_FORMAT(T.start_time, '%Y%m%d%H') <= R.range_end
GROUP BY
DOW, hour_of_day
ORDER BY
DOW, hour_of_day
;
Results:
| DOW | hour_of_day | period_count | rolling_av |
|-----|-------------|--------------|------------|
| 4 | 00 | 36 | 12 |
| 4 | 01 | 36 | 12 |
| 4 | 02 | 36 | 12 |
| 4 | 03 | 36 | 12 |
| 4 | 04 | 36 | 12 |
| 4 | 05 | 36 | 12 |
| 4 | 06 | 36 | 12 |
| 4 | 07 | 36 | 12 |
| 4 | 08 | 36 | 12 |
| 4 | 09 | 36 | 12 |
| 4 | 10 | 36 | 12 |
| 4 | 11 | 36 | 12 |
| 4 | 12 | 36 | 12 |
| 4 | 13 | 36 | 12 |
| 4 | 14 | 36 | 12 |
| 4 | 15 | 36 | 12 |
| 4 | 16 | 36 | 12 |
| 4 | 17 | 36 | 12 |
| 4 | 18 | 36 | 12 |
| 4 | 19 | 36 | 12 |
| 4 | 20 | 36 | 12 |
| 4 | 21 | 27 | 9 |
| 4 | 22 | 18 | 6 |
| 4 | 23 | 9 | 3 |

Related

Mysql - sum time difference and group per hour

I've ask recently question about grouping data per hour, but I will try to extend and explain more.
Currently I've managed to organized the structure like this:
ChangeDate | ChangeTime | timediff |
+------------+------------+----------+
| 2020-10-07 | 19:51:26 | 46 |
| 2020-10-07 | 19:53:13 | 48 |
| 2020-10-07 | 19:54:20 | 21 |
| 2020-10-07 | 19:54:56 | 105 |
| 2020-10-07 | 20:13:53 | 209 |
| 2020-10-07 | 20:52:28 | 45 |
| 2020-10-07 | 20:53:43 | 210 |
| 2020-10-07 | 20:56:08 | 258 |
| 2020-10-07 | 20:59:43 | 13 |
The desire result is to group the data per HOUR of ChangeTime column and SUM timediff which is already done(those are seconds)
So the new table structure would looks like this:
| ChangeDate | ChangeTime | timediff |
+------------+------------+----------+
| 2020-10-07 | 19:51:26 or 19-20 if possible | 48 + 46 + 21 + 105 |
| 2020-10-07 | 20:13:53 or 20-21 | 209 + 45 + 210 + 258 + 13 |
Second column it's optiona XX-YY format for example 19-20, cuz there can stay any particular value from that interval, again for this example 19:51:26...
Seconds also can stay, jus i need the sum i will convert them to minutes:seconds format...
This is so far what I've tried:
select DISTINCT MAX(t.ChangeDate) ChangeDate, MAX(t.ChangeTime) ChangeTime , t.timediff
FROM
(
select DISTINCT ChangeDate, HOUR(ChangeTime) ChangeTime, SUM(TimeDiff(CurrentTime,ChangeTime)) as timediff
from pins
where serial="6381872047252543"
and CurrentDate >= '2020-10-07' and CurrentDate <= '2020-10-08'
group by ChangeDate, ChangeTime
) t
group by t.timediff
order by ChangeDate, ChangeTime asc
Br,
You can use hour():
select changedate, hour(changetime) changehour,
sum(timediff(currenttime,changetime)) as sum_timediff
from pins
group by changedate, changehour

Finding the MAX value for each day in a month over multiple years in MySQL [duplicate]

This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 6 years ago.
I've got a database full of weather data...specifically the date, max temp, min temp, and daily rainfall for more than 100 years. I'm trying to find the maximum temperature for each day and the specific date that it occurred over the entire 100+ years.
My table is set up similar to below...
+-------+------------+------+------+------+
| id | date | thi | tlo | rain |
+-------+------------+------+------+------+
| 42856 | 2016-01-01 | 49 | 39 | 0.00 |
| 42857 | 2016-01-02 | 51 | 38 | 0.00 |
| 42858 | 2016-01-03 | 60 | 37 | 0.00 |
| 42859 | 2016-01-04 | 54 | 32 | 0.00 |
| 42860 | 2016-01-05 | 47 | 32 | 0.00 |
+-------+------------+------+------+------+
5 rows in set (0.01 sec)
I want to find the max(thi) for each day of the year and the date in which it occurred. This data goes back to 1899 so there are 117 January's in the database and so on for each year.
I have come up with the following so far...
select date, max(thi),
-> DAY(date)
-> from dfw where MONTH(date)='01'
-> group by DAY(date);
+------------+----------+-----------+
| date | max(thi) | DAY(date) |
+------------+----------+-----------+
| 1899-01-01 | 83 | 1 |
| 1899-01-02 | 78 | 2 |
| 1899-01-03 | 84 | 3 |
| 1899-01-04 | 81 | 4 |
| 1899-01-05 | 82 | 5 |
| 1899-01-06 | 79 | 6 |
| 1899-01-07 | 83 | 7 |
| 1899-01-08 | 88 | 8 |
| 1899-01-09 | 82 | 9 |
| 1899-01-10 | 79 | 10 |
| 1899-01-11 | 83 | 11 |
| 1899-01-12 | 82 | 12 |
| 1899-01-13 | 78 | 13 |
| 1899-01-14 | 79 | 14 |
| 1899-01-15 | 80 | 15 |
| 1899-01-16 | 81 | 16 |
| 1899-01-17 | 79 | 17 |
| 1899-01-18 | 80 | 18 |
| 1899-01-19 | 84 | 19 |
| 1899-01-20 | 83 | 20 |
| 1899-01-21 | 79 | 21 |
| 1899-01-22 | 85 | 22 |
| 1899-01-23 | 88 | 23 |
| 1899-01-24 | 82 | 24 |
| 1899-01-25 | 84 | 25 |
| 1899-01-26 | 82 | 26 |
| 1899-01-27 | 81 | 27 |
| 1899-01-28 | 85 | 28 |
| 1899-01-29 | 84 | 29 |
| 1899-01-30 | 86 | 30 |
| 1899-01-31 | 93 | 31 |
+------------+----------+-----------+
31 rows in set (0.01 sec)
This gives me the maximum for each day in January which is good...but I need the date on which it occurred. For some reason all I am getting is 1899.
For example on January 31...the max(thi) is 93 but it occurred on 1911-01-31. There are also times in which the max(thi) could have occurred in multiple years. On January 30...the max(thi) is 86 which occurred on 1906-01-30 and 1994-01-30.
Is there a way to do this in MySQL or am I just out of luck? Thanks in advance!
The value returned for date expression in your SELECT is indeterminate. MySQL is free to return a date value from any row in the group. (Other databases would throw an error with this query. A MySQL specific extension to GROUP BY allows the query to run, but we can get MySQL to more closely conform to the SQL standard, and throw an error with this query, by including ONLY_FULL_GROUP_BY in sql_mode.)
You've got a good start.
SELECT DATE_FORMAT(n.date,'%m%d') AS mmdd
, MAX(n.thi) AS max_thi
FROM dfw
GROUP BY DATE_FORMAT(n.date,'%m%d')
To get the year, there's a couple of approaches. One is to use the query as an inline view, and join to the original table to find a matching row, one with the same month and day, and the same thi value.
You can use either the MAX() or MIN() aggregate to get the latest or earliest date.
SELECT m.mmdd
, m.thi
, MAX(t.date) AS latest_date
, MIN(t.date) AS earliest_date
FROM (
SELECT DATE_FORMAT(n.date,'%m%d') AS mmdd
, MAX(n.thi) AS thi
FROM dfw
GROUP BY DATE_FORMAT(n.date,'%m%d')
) m
JOIN dfw t
ON t.thi = m.thi
AND DATE_FORMAT(t.date,'%m%d') = m.mmdd
GROUP BY m.mmdd
ORDER BY m.mmdd
If you want to return all years for a given mmdd that the highest thi occurred, remove the GROUP BY clause, and the aggregate from around t.date
SELECT m.mmdd
, m.thi
, t.date
FROM (
SELECT DATE_FORMAT(n.date,'%m%d') AS mmdd
, MAX(n.thi) AS thi
FROM dfw
GROUP BY DATE_FORMAT(n.date,'%m%d')
) m
JOIN dfw t
ON t.thi = m.thi
AND DATE_FORMAT(t.date,'%m%d') = m.mmdd
ORDER BY m.mmdd, t.date
As another alternative, to get the earliest date that thi occurred, you could use a correlated subquery in the SELECT list:
SELECT DATE_FORMAT(n.date,'%m%d') AS mmdd
, MAX(n.thi) AS thi
, ( SELECT t.date
FROM dfw t
WHERE DATE_FORMAT(t.date,'%m%d') = DATE_FORMAT(n.date,'%m%d')
AND t.thi = n.thi
ORDER BY t.date
LIMIT 0,1
) AS earliest_date
FROM dfw n
GROUP BY DATE_FORMAT(n.date,'%m%d')
ORDER BY DATE_FORMAT(n.date,'%m%d')

How to find MySQL rows based on sum of values on adjacent dates?

I have a table which contains time entries for different employees, across different dates, and the activity which their time is recorded against. I want to find all rows where they have a minimum amount of time spent on the same activity within, for example, a 3 day period.
Here's a simplified version of the table which I'll be querying:
CREATE TABLE `time_entries` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`employee_id` int(11) NOT NULL,
`activity_id` int(11) NOT NULL,
`work_date` date NOT NULL,
`time_spent` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Time, in minutes, spent on the current activity',
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
And some sample data:
+----+-------------+-------------+------------+------------+
| id | employee_id | activity_id | work_date | time_spent |
+----+-------------+-------------+------------+------------+
| 10 | 1 | 2 | 2016-06-11 | 120 |
| 16 | 1 | 3 | 2016-06-21 | 450 |
| 29 | 1 | 4 | 2016-06-22 | 450 |
| 17 | 1 | 4 | 2016-06-23 | 450 |
| 12 | 3 | 4 | 2016-06-23 | 450 |
| 4 | 1 | 4 | 2016-06-24 | 450 |
| 22 | 1 | 4 | 2016-06-26 | 60 |
| 9 | 1 | 6 | 2016-06-27 | 450 |
+----+-------------+-------------+------------+------------+
The time_spent is in minutes, and I essentially want to select all rows which form a block of at least 3 days, with time_spent = n days* 450 minutes spent, on the same activity_id and employee_id
In the example above, I want to retrieve rows 29, 17, 4. Row 16 would not be included as this is a different activity_id, nor would row 12 as this is a different employee_id. Row 22 misses a date and therefore would 'break' the dates.
I guess I could create a view or temporary table to give me a sequence of dates, and use some aggregate functions to group the rows based on the SUM(time_spent) where the work_date is between the given date and work_date + 3 days
It's not really something I've had to achieve before now, but thinking about it, could form a useful tool for analysis in future.
With the following schema from you and my test data:
Schema
CREATE TABLE `time_entries` (
`id` int(11) AUTO_INCREMENT PRIMARY KEY,
`employee_id` int(11) NOT NULL,
`activity_id` int(11) NOT NULL,
`work_date` date NOT NULL,
`time_spent` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Time, in minutes, spent on the current activity'
) ENGINE=InnoDB;
Test Data
Please note that for simplicity on constructing test data, I use auto increments and allow the db to assign the id. As opposed to inserting the id's directly. I did show the id number to the far right, below, such as -- 7
insert time_entries(employee_id,activity_id,work_date,time_spent) values
(1,2,'2016-06-11',120), -- 1
(1,3,'2016-06-21',450), -- 2
(1,13,'2016-06-21',450), -- 3
(1,14,'2016-06-21',450), -- 4
(1,15,'2016-06-21',450), -- 5
(1,4,'2016-06-22',450), -- 6
(1,4,'2016-06-23',450), -- 7
(3,4,'2016-06-23',450), -- 8
(1,4,'2016-06-24',450), -- 9
(1,16,'2016-06-25',450), -- 10
(1,17,'2016-06-25',450), -- 11
(1,4,'2016-06-26',60), -- 12
(1,6,'2016-06-27',450), -- 13
(3,4,'2016-06-27',450), -- 14
(3,4,'2016-06-28',450), -- 15
(3,4,'2016-06-29',450), -- 16
(4,4,'2016-06-28',200), -- 17
(4,4,'2016-06-29',200), -- 18
(4,4,'2016-06-30',200), -- 19
(4,4,'2016-07-01',200), -- 20
(4,4,'2016-07-03',200), -- 21
(5,4,'2016-07-08',200), -- 22
(5,4,'2016-07-09',200), -- 23
(5,4,'2016-07-10',200), -- 24
(5,4,'2016-07-12',200), -- 25
(5,4,'2016-07-13',200), -- 26
(5,4,'2016-07-14',200), -- 27
(5,4,'2016-07-15',200), -- 28
(6,6,'2016-08-01',500), -- 29
(6,6,'2016-08-02',500), -- 30
(6,6,'2016-08-04',500), -- 31
(6,6,'2016-08-05',500), -- 32
(7,6,'2016-08-21',500), -- 33
(7,6,'2016-08-22',500), -- 34
(7,6,'2016-08-23',500), -- 35
(7,6,'2016-08-25',500), -- 36
(7,6,'2016-08-26',500); -- 37
Final Query
select distinct t4.id,t4.employee_id,t4.activity_id,t4.work_date,t4.time_spent
from time_entries t4
join
( select t3.id,t3.employee_id,t3.activity_id,t3.work_date
from time_entries t3
join
( select t1.id,count(*) as rowcount,sum(t2.time_spent) as timeworked
from time_entries t1
join time_entries t2
on t2.employee_id=t1.employee_id
and t2.activity_id=t1.activity_id
and datediff(t2.work_date,t1.work_date)<=2
and t2.work_date>=t1.work_date
group by t1.id
having rowcount=3 and timeworked>=450
) xDerived1
on t3.id=xDerived1.id
) xDerived2
on t4.employee_id=xDerived2.employee_id
and t4.activity_id=xDerived2.activity_id
and datediff(t4.work_date,xDerived2.work_date)<=2
and datediff(t4.work_date,xDerived2.work_date)>=0
order by t4.employee_id,t4.activity_id,t4.work_date;
Results
+----+-------------+-------------+------------+------------+
| id | employee_id | activity_id | work_date | time_spent |
+----+-------------+-------------+------------+------------+
| 6 | 1 | 4 | 2016-06-22 | 450 |
| 7 | 1 | 4 | 2016-06-23 | 450 |
| 9 | 1 | 4 | 2016-06-24 | 450 |
| 14 | 3 | 4 | 2016-06-27 | 450 |
| 15 | 3 | 4 | 2016-06-28 | 450 |
| 16 | 3 | 4 | 2016-06-29 | 450 |
| 17 | 4 | 4 | 2016-06-28 | 200 |
| 18 | 4 | 4 | 2016-06-29 | 200 |
| 19 | 4 | 4 | 2016-06-30 | 200 |
| 20 | 4 | 4 | 2016-07-01 | 200 |
| 22 | 5 | 4 | 2016-07-08 | 200 |
| 23 | 5 | 4 | 2016-07-09 | 200 |
| 24 | 5 | 4 | 2016-07-10 | 200 |
| 25 | 5 | 4 | 2016-07-12 | 200 |
| 26 | 5 | 4 | 2016-07-13 | 200 |
| 27 | 5 | 4 | 2016-07-14 | 200 |
| 28 | 5 | 4 | 2016-07-15 | 200 |
| 33 | 7 | 6 | 2016-08-21 | 500 |
| 34 | 7 | 6 | 2016-08-22 | 500 |
| 35 | 7 | 6 | 2016-08-23 | 500 |
+----+-------------+-------------+------------+------------+
20 rows in set (0.00 sec)
About half the rows qualify. Based on the requirement of "showing the rows where ..." it could, say, show rows such that if there are 4 days in a row (for a given worker / activity / sum of hours), more than 3 could appear in the results. Meaning, if there was a block of 4, the first 3 could qualify, and the last 3 could qualify. That is shown in the results.
For a visualization of xDerived1, see the following:
Here's another way of solving it, ordering the innermost subquery and using variables to link consecutive entries together before selecting groups of more than three. I have to say though, I think I prefer Drew's solution.
SELECT t4.* FROM time_entries t4
JOIN
(SELECT employee_id, activity_id, MIN(work_date) min, MAX(work_date) max FROM
(SELECT t.id,
#employee_id := t.employee_id employee_id,
#activity_id := t.activity_id activity_id,
#work_date := t.work_date work_date,
#i i
FROM (SELECT * FROM time_entries ORDER BY employee_id, activity_id, work_date) t
JOIN (SELECT #employee_id := 0, #work_date := NULL, #i := 0) tmp
WHERE
CASE WHEN #employee_id = employee_id
AND #activity_id = activity_id
AND work_date = DATE_ADD(#work_date, INTERVAL 1 DAY)
THEN #i ELSE #i := #i + 1 END) t2
GROUP BY t2.i, t2.employee_id, t2.activity_id
HAVING COUNT(*) >= 3) t3
WHERE t4.employee_id = t3.employee_id
AND t4.activity_id = t3.activity_id
AND t4.work_date BETWEEN t3.min AND t3.max;

Select highest value for each foreign key using time interval

I have a database of measurements for different locations that is taken every 1 hour.
ID | LOCATION_ID | CMS | DATE
6 | C | 7 | 2014-11-27 12:00:00
5 | B | 3 | 2014-11-27 12:00:00
4 | A | 19 | 2014-11-27 12:00:00
3 | C | 9 | 2014-11-27 11:00:00
2 | B | 8 | 2014-11-27 11:00:00
1 | A | 11 | 2014-11-27 11:00:00
I need to select the highest cms for each unique location, within the last 3 hours. For example;
ID | LOCATION_ID | CMS | DATE
3 | C | 9 | 2014-11-27 11:00:00
2 | B | 8 | 2014-11-27 11:00:00
4 | A | 19 | 2014-11-27 12:00:00
I am using the below MySQL to return the max, but I am missing the final piece. What do I need to complete the statement?
SELECT MAX(cms) as value_of_rain
FROM `rainfall`
WHERE `date` >= SUBDATE( NOW( ) , INTERVAL 3 HOUR )
You are missing the grouping statement using GROUP BY like
SELECT MAX(cms) as value_of_rain
FROM `rainfall`
WHERE `date` >= SUBDATE( NOW( ) , INTERVAL 10 MINUTE )
GROUP BY LOCATION_ID

get amount between range [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Closed 8 years ago.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Improve this question
This a simple my table
+-----------+----------------+-----------+
| id | date | meter |
------------+----------------+-----------+
| 1 | 2103-11-01 | 5 |
| 2 | 2103-11-10 | 8 |
| 4 | 2103-11-14 | 10 |
| 6 | 2103-11-20 | 18 |
| 7 | 2103-11-25 | 25 |
| 10 | 2103-11-29 | 30 |
+-----------+----------------+-----------+
how do I get the results to the use of meters between two ranges of the results of recording time,
like bellow
+----------------+----------------+-------+-----+--------+
| date1 | date2 | start | end | amount |
+----------------+----------------+-------+-----+--------+
| 2013-11-01 | 2013-11-10 | 5 | 8 | 3 |
| 2013-11-10 | 2013-11-14 | 8 | 10 | 2 |
| 2013-11-14 | 2013-11-20 | 10 | 18 | 8 |
| 2013-11-20 | 2013-11-25 | 18 | 25 | 7 |
| 2013-11-25 | 2013-11-29 | 25 | 30 | 5 |
+----------------+----------------+-------+-----+--------+
Edit:
I got it:
select meters1.date as date1, min(meters2.date) as date2, meters1.meter as start,
meters2.meter as end, (meters2.meter - meters1.meter) as amount
from meters meters1, meters meters2 where meters1.date < meters2.date
group by date1;
Outputs:
+------------+------------+-------+-----+--------+
| date1 | date2 | start | end | amount |
+------------+------------+-------+-----+--------+
| 2013-11-01 | 2013-11-10 | 5 | 8 | 3 |
| 2013-11-10 | 2013-11-14 | 8 | 10 | 2 |
| 2013-11-14 | 2013-11-20 | 10 | 18 | 8 |
| 2013-11-20 | 2013-11-25 | 18 | 25 | 7 |
| 2013-11-25 | 2013-11-29 | 25 | 30 | 5 |
+------------+------------+-------+-----+--------+
Original Post:
This is most of the way there:
select meters1.date as date1, meters2.date as date2, meters1.meter as start,
meters2.meter as end, (meters2.meter - meters1.meter) as amount
from meters meters1, meters meters2 having date1 < date2 order by date1;
It outputs:
+------------+------------+-------+-----+--------+
| date1 | date2 | start | end | amount |
+------------+------------+-------+-----+--------+
| 2013-11-01 | 2013-11-10 | 5 | 8 | 3 |
| 2013-11-01 | 2013-11-20 | 5 | 18 | 13 |
| 2013-11-01 | 2013-11-29 | 5 | 30 | 25 |
| 2013-11-01 | 2013-11-14 | 5 | 10 | 5 |
| 2013-11-01 | 2013-11-25 | 5 | 25 | 20 |
| 2013-11-10 | 2013-11-20 | 8 | 18 | 10 |
| 2013-11-10 | 2013-11-29 | 8 | 30 | 22 |
| 2013-11-10 | 2013-11-14 | 8 | 10 | 2 |
| 2013-11-10 | 2013-11-25 | 8 | 25 | 17 |
| 2013-11-14 | 2013-11-25 | 10 | 25 | 15 |
| 2013-11-14 | 2013-11-20 | 10 | 18 | 8 |
| 2013-11-14 | 2013-11-29 | 10 | 30 | 20 |
| 2013-11-20 | 2013-11-25 | 18 | 25 | 7 |
| 2013-11-20 | 2013-11-29 | 18 | 30 | 12 |
| 2013-11-25 | 2013-11-29 | 25 | 30 | 5 |
+------------+------------+-------+-----+--------+
If it's SQL server try it this way
WITH cte AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY date) rnum
FROM table1
)
SELECT c.date date1, p.date date2, c.meter [start], p.meter [end], p.meter - c.meter amount
FROM cte c JOIN cte p
ON c.rnum = p.rnum - 1
Here is SQLFiddle demo
If it's MySQL then you can do
SELECT date1, date2, meter1, meter2, meter2 - meter1 amount
FROM
(
SELECT #d date2, date date1, #m meter2, meter meter1, #d := date, #m := meter
FROM table1 CROSS JOIN (SELECT #d := NULL, #m := NULL) i
ORDER BY date DESC
) q
WHERE date2 IS NOT NULL
ORDER BY date1
Here is SQLFiddle demo
Output in both cases:
| DATE1 | DATE2 | START | END | AMOUNT |
|------------|------------|-------|-----|--------|
| 2103-11-01 | 2103-11-10 | 5 | 8 | 3 |
| 2103-11-10 | 2103-11-14 | 8 | 10 | 2 |
| 2103-11-14 | 2103-11-20 | 10 | 18 | 8 |
| 2103-11-20 | 2103-11-25 | 18 | 25 | 7 |
| 2103-11-25 | 2103-11-29 | 25 | 30 | 5 |
MySql
SELECT DATES.date1,
DATES.date2,
m1.meter as start,
m2.meter as end,
m2.meter - m1.meter as amount
FROM
(SELECT date as date1,
(SELECT min(date)
FROM tableName t2
WHERE t2.date > t1.date) as date2
FROM tableName t1
)DATES,
tableName m1,
tableName m2
WHERE DATES.date2 IS NOT NULL
AND m1.date = DATES.date1
AND m2.date = DATES.date2
ORDER BY DATES.date1
sqlFiddle here
in MS-SQL SERVER 2002 change the word end to "end" as it complains about syntax near end
You haven't made it clear whether you're really using mySQL or SQL Server but I'm posting a solution that works for SQL 2008 and above. Might work for 2005 but I can't test that.
-- Set up a temp table with sample data
DECLARE #testData AS TABLE(
id int,
dt date,
meter int)
INSERT #testData(id, dt, meter) VALUES
(1, '2013-11-01', 5)
,(2, '2013-11-10', 8)
,(4, '2013-11-14', 10)
,(6, '2013-11-20', 18)
,(7, '2013-11-25', 25)
,(10, '2013-11-29',30)
---------------------------------------------
-- Begin SQL Server solution
;WITH cte AS (
SELECT
ROW_NUMBER() OVER (ORDER BY id) AS rownum
,id
,dt
,meter
FROM
#testData AS [date2]
)
SELECT
t1.id
,t1.dt AS [date1]
,t2.dt AS [date2]
,t1.meter AS [start]
,t2.meter AS [end]
,t2.meter - t1.meter AS [amount]
FROM
cte t1
LEFT OUTER JOIN cte t2 ON (t2.rownum = t1.rownum + 1)
WHERE
t2.dt IS NOT NULL
If you're using MySQL, then a self-join will work well here. Join the table to itself, using an ON clause to make sure you don't join the same record to itself. This will give you ((N * N) - N) permutations of your data, where N is the number of original rows.
SELECT
...
FROM
tableName first
JOIN
tableName second
ON first.id != second.id
Then, it's all about SELECTing the right stuff (including the calculation of the difference between the two meter values). To get the columns in the result set you posted, you'd probably want to SELECT:
first.date AS date1,
second.date AS date2,
first.meter AS start,
second.meter AS end,
ABS(first.meter - second.meter) AS amount
Edit
Ah, I see. I'd envisioned something like a inter-city mileage chart that you used to see on road maps (where you'd have the same cities in the rows and columns, and the cell in the intersection would indicate the number of miles between those two cities.
But it looks like you just want to compare values from one date to the next. If that's the case, you can take advantage of the way MySQL handles GROUPing and ORDERing... but be careful, because I'm not sure this is guaranteed:
mysql> SELECT
table1.date AS date1,
table2.date AS date2,
table1.meter AS start,
table2.meter AS end,
ABS(table1.meter - table2.meter) AS amount
FROM tableName table1
JOIN tableName table2
WHERE table2.date > table1.date
GROUP BY table1.date
ORDER BY table2.date - table1.date;
+---------------------+---------------------+-------+------+--------+
| date1 | date2 | start | end | amount |
+---------------------+---------------------+-------+------+--------+
| 2103-11-25 00:00:00 | 2103-11-29 00:00:00 | 25 | 30 | 5 |
| 2103-11-10 00:00:00 | 2103-11-14 00:00:00 | 8 | 10 | 2 |
| 2103-11-20 00:00:00 | 2103-11-25 00:00:00 | 18 | 25 | 7 |
| 2103-11-14 00:00:00 | 2103-11-20 00:00:00 | 10 | 18 | 8 |
| 2103-11-01 00:00:00 | 2103-11-10 00:00:00 | 5 | 8 | 3 |
+---------------------+---------------------+-------+------+--------+
5 rows in set (0.00 sec)