MySQL schedule conflicts - mysql

Hey, I stumbled upon this site looking for solutions for event overlaps in mySQL tables. I was SO impressed with the solution (which is helping already) I thought I'd see if I could get some more help...
Okay, so Joe want's to swap shifts with someone at work. He has a court date. He goes to the shift swap form and it pull up this week's schedule (or what's left of it). This is done with a DB query. No sweat. He picks a shift. From this point, it gets prickly.
So, first, the form passes the shift start and shift end to the script. It runs a query for anyone who has a shift that overlaps this shift. They can't work two shifts at once, so all user IDs from this query are put on a black list. This query looks like:
SELECT DISTINCT user_id FROM shifts
WHERE
FROM_UNIXTIME('$swap_shift_start') < shiftend
AND FROM_UNIXTIME('$swap_shift_end') > shiftstart
Next, we run a query for all shifts that are a) the same length (company policy), and b) don't overlap with any other shifts Joe is working.
What I currently have is something like this:
SELECT *
FROM shifts
AND shiftstart BETWEEN FROM_UNIXTIME('$startday') AND FROM_UNIXTIME('$endday')
AND user_id NOT IN ($busy_users)
AND (TIME_TO_SEC(TIMEDIFF(shiftend,shiftstart)) = '$swap_shift_length')
$conflict_dates
ORDER BY shiftstart, lastname
Now, you are probably wondering "what is $conflict_dates???"
Well, when Joe submits the swap shift, it reloads his shifts for the week in case he decides to check out another shift's potential. So when it does that first query, while the script is looping through and outputting his choices, it is also building a string that looks kind of like:
AND NOT(
'joe_shift1_start' < shiftend
AND 'joe_shift1_end' > shiftstart)
AND NOT(
'joe_shift2_start' < shiftend
AND 'joe_shift2_end' > shiftstart)
...etc
So that the database is getting a pretty long query along the lines of:
SELECT *
FROM shifts
AND shiftstart BETWEEN FROM_UNIXTIME('$startday') AND FROM_UNIXTIME('$endday')
AND user_id NOT IN ('blacklisteduser1', 'blacklisteduser2',...etc)
AND (TIME_TO_SEC(TIMEDIFF(shiftend,shiftstart)) = '$swap_shift_length')
AND NOT(
'joe_shift1_start' < shiftend
AND 'joe_shift1_end' > shiftstart)
AND NOT(
'joe_shift2_start' < shiftend
AND 'joe_shift2_end' > shiftstart)
AND NOT(
'joe_shift3_start' < shiftend
AND 'joe_shift3_end' > shiftstart)
AND NOT(
'joe_shift4_start' < shiftend
AND 'joe_shift4_end' > shiftstart)
...etc
ORDER BY shiftstart, lastname
So, my hope is that either SQL has some genius way of dealing with this in a simpler way, or that someone can point out a fantastic logical principal that accounts for the potential conflicts in a much smarter way. (Notice the use of the 'start > end, end < start', before I found that I was using betweens and had to subtract a minute off both ends.)
Thanks!
A

I think you should be able to exclude Joe's other shifts using an inner select instead of the generated string, something like:
SELECT *
FROM shifts s1
AND shiftstart BETWEEN FROM_UNIXTIME('$startday') AND FROM_UNIXTIME('$endday')
AND user_id NOT IN ($busy_users)
AND (TIME_TO_SEC(TIMEDIFF(shiftend,shiftstart)) = '$swap_shift_length')
AND (SELECT COUNT(1) FROM shifts s2
WHERE s2.user_id = $joes_user_id
AND s1.shiftstart < s2.shiftend
AND s2.shiftstart < s1.shiftend) = 0
ORDER BY shiftstart, lastname
Basically, each row has an inner query for the count of Joe's shifts which overlap, and makes sure that it's zero. Thus, only rows which don't overlap with any of Joe's existing shifts will be returned.

You could load the joe_shift{1,2,3} values into a TEMPORARY table and then do a query to join against it, using an outer join to find only shift that don't match any:
CREATE TEMPORARY TABLE joes_shifts (
shiftstart DATETIME
shiftend DATETIME
);
INSERT INTO joes_shifts (shiftstart, shiftend) VALUES
('$joe_shift1_start', '$joe_shift1_end'),
('$joe_shift2_start', '$joe_shift2_end'),
('$joe_shift3_start', '$joe_shift3_end'),
('$joe_shift4_start', '$joe_shift4_end');
-- make sure you have validated these variables to prevent SQL injection
SELECT s.*
FROM shifts s
LEFT OUTER JOIN joes_shifts j
ON (j.shiftstart < s.shiftend OR j.shiftend > s.shiftstart)
WHERE j.shiftstart IS NULL
AND s.shiftstart BETWEEN FROM_UNIXTIME('$startday') AND FROM_UNIXTIME('$endday')
AND s.user_id NOT IN ('blacklisteduser1', 'blacklisteduser2',...etc)
AND (TIME_TO_SEC(TIMEDIFF(s.shiftend,s.shiftstart)) = '$swap_shift_length');
Because of the LEFT OUTER JOIN, when there is no matching row in joes_shifts, the columns are NULL.

Related

How to Find First Valid Row in SQL Based on Difference of Column Values

I am trying to find a reliable query which returns the first instance of an acceptable insert range.
Research:
some of the below links adress similar questions, but I could get none of them to work for me.
Find first available date, given a date range in SQL
Find closest date in SQL Server
MySQL difference between two rows of a SELECT Statement
How to find a gap in range in SQL
and more...
Objective Query Function:
InsertRange(1) = (StartRange(i) - EndRange(i-1)) > NewValue
Where InsertRange(1) is the value the query should return. In other words, this would be the first instance where the above condition is satisfied.
Table Structure:
Primary Key: StartRange
StartRange(i-1) < StartRange(i)
StartRange(i-1) + EndRange(i-1) < StartRange(i)
Example Dataset
Below is an example User table (3 columns), with a set range distribution. StartRanges are always ordered in a strictly ascending way, UserID are arbitrary strings, only the sequences of StartRange and EndRange matters:
StartRange EndRange UserID
312 6896 user0
7134 16268 user1
16877 22451 user2
23137 25142 user3
25955 28272 user4
28313 35172 user5
35593 38007 user6
38319 38495 user7
38565 45200 user8
46136 48007 user9
My current Query
I am trying to use this query at the moment:
SELECT t2.StartRange, t2.EndRange
FROM user AS t1, user AS t2
WHERE (t1.StartRange - t2.StartRange+1) > NewValue
ORDER BY t1.EndRange
LIMIT 1
Example Case
Given the table, if NewValue = 800, then the returned answer should be 23137. This means, the first available slot would be between user3 and user4 (with an actual slot size = 813):
InsertRange(1) = (StartRange(i) - EndRange(i-1)) > NewValue
InsertRange = (StartRange(6) - EndRange(5)) > NewValue
23137 = 25955 - 25142 > 800
More Comments
My query above seemed to be working for the special case where StartRanges where tightly packed (i.e. StartRange(i) = StartRange(i-1) + EndRange(i-1) + 1). This no longer works with a less tightly packed set of StartRanges
Keep in mind that SQL tables have no implicit row order. It seems fair to order your table by StartRange value, though.
We can start to solve this by writing a query to obtain each row paired with the row preceding it. In MySQL, it's hard to do this beautifully because it lacks the row numbering function.
This works (http://sqlfiddle.com/#!9/4437c0/7/0). It may have nasty performance because it generates O(n^2) intermediate rows. There's no row for user0; it can't be paired with any preceding row because there is none.
select MAX(a.StartRange) SA, MAX(a.EndRange) EA,
b.StartRange SB, b.EndRange EB , b.UserID
from user a
join user b ON a.EndRange <= b.StartRange
group by b.StartRange, b.EndRange, b.UserID
Then, you can use that as a subquery, and apply your conditions, which are
gap >= 800
first matching row (lowest StartRange value) ORDER BY SB
just one LIMIT 1
Here's the query (http://sqlfiddle.com/#!9/4437c0/11/0)
SELECT SB-EA Gap,
EA+1 Beginning_of_gap, SB-1 Ending_of_gap,
UserId UserID_after_gap
FROM (
select MAX(a.StartRange) SA, MAX(a.EndRange) EA,
b.StartRange SB, b.EndRange EB , b.UserID
from user a
join user b ON a.EndRange <= b.StartRange
group by b.StartRange, b.EndRange, b.UserID
) pairs
WHERE SB-EA >= 800
ORDER BY SB
LIMIT 1
Notice that you may actually want the smallest matching gap instead of the first matching gap. That's called best fit, rather than first fit. To get that you use ORDER BY SB-EA instead.
Edit: There is another way to use MySQL to join adjacent rows, that doesn't have the O(n^2) performance issue. It involves employing user variables to simulate a row_number() function. The query involved is a hairball (that's a technical term). It's described in the third alternative of the answer to this question. How do I pair rows together in MYSQL?

MDX Help: - Comparing Values in Two Max Time Periods within a larger Set of Time Periods to Populate an Indicator

I'm brand new to MDX and need some help. In SSRS I have a dataset that pulls from an SSAS cube. The dataset always contains six months of data. What I need to be able to do is to compare a value for the max(timeID) with a value for the second max(timeID) and if the value for the max(timeID) > value for the second max(timeID) than the arrow goes up in the indicator, etc...
So for the dataset below I would subtract 20130201's Value which is 8 from
20130301's Value which is 10. The result would be a positive number and the indicator would be an upward pointing green arrow. If it was 0 it would be straight and if negative the arrow would be red and point down. I understand how to deal with the indicator - that's not an issue. It's the MDX I need help with.
20130201 8
20130301 10
20121201 4
I can write it in SQL and it would look like this.
Select Item, case when sum(Time1ContentCount) > sum(Time2ContentCount) then 3 when sum(Time1ContentCount) = sum(Time2ContentCount) then 2 when sum(Time1ContentCount) sum(Time2ContentCount) then 1 end as Indicator, sum(Time1ContentCount) as Time1Count, sum(Time2ContentCount) as Time2Count from (Select timeID, dc.Item, Case when timeID = (Select max(timeID) from FactUsage) then count(fu.Contentid) else 0 END as Time1ContentCount, Case when timeID = (Select max(timeID) from FactUsage where timeID <>(Select max(timeID) from FactUsage)) then count(fu.Contentid) else 0 END as Time2ContentCount from factUsage fu INNER JOIN dimContent dC on dc.ContentID = fu.ContentID WHERE TimeID in (Select distinct top 6 timeid from factUsage order by timeID desc) Group by timeID, Item) a group by Item
Thanks so much for your help!
Edit:
I changed the statement to read as follows for the indicator.
WITH Member MEASURES.Indicator AS (
IIF(( [Measures].[Activity], [Time].[Time ID].LastChild ) >
( [Measures].[Activity], [Time].[Time ID].LastChild.PrevMember),3,
(IIF(([Measures].[Activity], [Time].[Time ID].LastChild ) =
([Measures].[Activity], [Time].[Time ID].LastChild.PrevMember), 2,1))))
SELECT {Measures.Indicator} on 0
FROM [DW]
It works when I run it as a query against the cube in SSMS but I tried to put it in the indicator and that doesn't work. Just adding the IIF statement doesn't work either. When I tried to add it into the query or the cube itself so I could just pull from there it errors out with an out of memory error.
I don't know how much you can edit in the MDX expression - or in your report builder, but to get the difference between two values in a series, you can create a measure (in your report) that is the difference between the CurrentMember and PrevMember. Since the time series (timeid) is sorted by the key, it will always be in the right order (or your schema and architecture needs a rework)
So basically, you can do :
WITH
MEMBER MEASURES.GrowthTime AS (
( [Measures].[Value], [TimeID].CurrentMember ) -
( [Measures].[Value], [TimeID].PrevMember )
)
MEMBER MEASURES.GrowthRatio AS (
( [Measures].[Value], [TimeID].CurrentMember ) /
( [Measures].[Value], [TimeID].PrevMember )
)
SELECT { Measures.Value, Measures.GrowthTime, Measures.GrowthRatio } on 0,
[TimeID].CHILDREN on 1
FROM Cube
This is pseudo as i don't know your cube structure. For TimeID you would want it like [DimensionName].[AttributeName].CurrentMember and PrevMember

Need help to make one mysql query to get expected result for my requirement

I am facing few issue to write mysql query in my scope to get result. Actually I am getting appropriate result using this existing query but it is not written appropriate way. Here is my query:
SELECT c.ID, c.chn_name,c.chn_logo,
(SELECT ID FROM tv_showtime WHERE showtime<='2013-02-18 10:28:35' AND status='Enable' AND chn_id=c.ID ORDER BY ID DESC Limit 0,1) as currentshowid,
(SELECT tv_showtime FROM tv_showtime WHERE showtime<='2013-02-18 10:28:35' AND status='Enable' AND chn_id=c.ID ORDER BY ID DESC Limit 0,1) as currentshowtime ,
(SELECT tv_showtime FROM tv_showtime WHERE showtime >'2013-02-18 10:28:35' AND status='Enable' AND chn_id=c.ID ORDER BY ID ASC Limit 0,1) as nextshowtime
FROM tv_channels AS c
WHERE c.status="Enable"
ORDER BY c.chn_name
LIMIT 0,10
Here, there are only two tables named as "tv_channels" and "tv_showtime". I need one record for each channel at a time ( for current time). So here suppose 12 channels and approx 30 (may vary foe each channel) records for each channel and I only need to display channels with current show (More clarification: only channels will be displayed which has current show time and/or next show time.)
Problem: I need more field values from "tv_showtime" to display other required values. And if I will use this way then I have to write more inner select query and it will slow down my website to load. So can you suggest or advise any other way to write this query please?
Database table detail:
tv_channels [ID, chn_name, [other required fields]],
tv_showtime [ID, chn_id, showtime, show_name, hits, last_ip [and few more fields]]
Please let me know if you will need further detail to get this question.
Any help or suggestion will be appreciated. thanks.
As another asked, but you didnt respond to an "end time" for each show, I had to go on the premise that the show time was when it started. That said, how do you determine which is the current show running for a given channel based on CURTIME() (instead of fixed time value).
Get each channel and the MAXIMUM SHOW Time that exists PRIOR TO the current time...
Likewise, how to get the NEXT Show? Get each channel with the MINIMUM SHOW time that STARTS AFTER the current time.
So, if I had the following records for 1 channels and the current time is 2:15pm
Channel ShowTime Show_Name
1 12:30pm Show "X"
1 01:00pm Show "B"
1 01:30pm Show "C"
1 02:00pm Show "D" <- Current Show
1 02:30pm Show "Y" <- Next Show
1 03:00pm Show "Z"
The current show running is the latest one PRIOR to 2:15 (Show "D" starting at 2pm)
and the NEXT Show is first AFTER current time (Show "Y" starting at 2:30pm). The above will work even if the rows are not in sequential order as I am using MIN() and MAX() respectively to get the time.
So, I start with the channel table and do a left-join to each separate pre-aggregate query for detecting the current show and next show times respectively and join on the channel ID which each COULD return at most one record --- provided there IS a record within qualified WHERE CURTIME() consideration.
From THAT, I am re-joining THOSE result sets back to the actual tv schedule table AGAIN, but this time, on the channel AND the time that matched the corresponding current or next time.
So now, I have everything lined up ready to go with respective aliases for content. Now, I just grab the columns I want to present.
Since the joins are all LEFT-JOINs, each side COULD have NULL values, so you might want to adjust the query to prevent nulls using COALESCE(), such as I've sampled...
SELECT
TC.ID,
TC.Chn_Name,
TC.Chn_Logo,
COALESCE( CurShowTimeDetail.ShowTime, 'no time' ) CurShowTime,
COALESCE( CurShowTimeDetail.Show_Name, '' ) CurShowName,
COALESCE( CurShowTimeDetail.Hits, 0 ) CurHits,
COALESCE( NextShowTimeDetail.ShowTime, 'no time' ) NextShowTime,
COALESCE( NextShowTimeDetail.Show_Name, '' ) NextShowName,
COALESCE( NextShowTimeDetail.Hits, 0 ) NextHits
from
TV_Channels TC
LEFT JOIN ( SELECT
ST.chn_id,
MAX( ST.showtime ) CurShowTime
from
tv_showtime ST
where
ST.ShowTime < CURTIME()
group by
ST.chn_id ) CurrentShow
ON TC.ID = CurrentShow.Chn_ID
LEFT JOIN tv_showtime CurShowTimeDetail
ON CurrentShow.Chn_ID = CurShowTimeDetail.Chn_ID
AND CurrentShow.CurShowTime = CurShowTimeDetail.ShowTime
LEFT JOIN ( SELECT
ST.chn_id,
MIN( ST.showtime ) NextShowTime
from
tv_showtime ST
where
ST.ShowTime > CURTIME()
group by
ST.chn_id ) NextShow
ON TC.ID = NextShow.Chn_ID
LEFT JOIN tv_showtime NextShowTimeDetail
ON NextShow.Chn_ID = NextShowTimeDetail.Chn_ID
AND NextShow.NextShowTime = NextShowTimeDetail.ShowTime
To select last (first) records from a table by some order, you may LEFT JOIN the table with itself as any next (previous) element, and add a condition that there is no such element.
SELECT c.ID, c.chn_name, c.chn_logo
, curr_sh.ID AS currentshowid, curr_sh.showtime AS currentshowtime -- Continue with desired columns
, next_sh.showtime AS nextshowtime -- Continue with desired columns
FROM tv_channels AS c
LEFT JOIN tv_showtime AS curr_sh
ON curr_sh.chn_id = c.ID
AND curr_sh.showtime <= '2013-02-18 10:28:35'
AND curr_sh.status='Enable'
LEFT JOIN tv_showtime AS curr_next_sh
ON curr_next_sh.chn_id = curr_sh.chn_id
AND curr_next_sh.showtime > curr_sh.showtime
AND curr_next_sh.showtime <= '2013-02-18 10:28:35'
AND curr_next_sh.status = 'Enable'
LEFT JOIN tv_showtime AS next_sh
ON next_sh.chn_id = c.ID
AND next_sh.showtime > '2013-02-18 10:28:35'
AND next_sh.status='Enable'
LEFT JOIN tv_showtime AS next_prev_sh
ON next_prev_sh.chn_id = next_sh.chn_id
AND next_prev_sh.showtime < next_sh.showtime
AND next_prev_sh.showtime > '2013-02-18 10:28:35'
AND next_prev_sh.status = 'Enable'
WHERE c.status = 'Enable'
AND curr_next_sh.ID IS NULL -- This gives us only the latest current show
AND next_prev_sh.ID IS NULL -- This gives us only the earliest next show
AND (curr_sh.ID IS NOT NULL OR next_sh.ID IS NOT NULL) -- This gives us 'which has current show time and/or next show time'
ORDER BY c.chn_name
LIMIT 0,10
But I'm not sure about performance, and whether this solution is optimal.

MySQL: self join to produce pairs of dates

I have a table with entries for Items as being 'lost' and 'found'. Each row has a date for the event. Im hoping to build a query with matching pairs of 'itemid', 'lost date', 'found date' by joining the table to itself.
This works to a point: unfortunately if there are multiple lost and found pairs for a given item each 'lost date' will be joined with all the 'found dates' that follow it.
Still with me?
The query goes something like:
select c0.ItemId, c0.ChangeDate, c1.ChangeDate from Changes c0
join Changes c1 on
c0.ItemId = c1.ItemId and c1.ChangeDate >= c0.ChangeDate
where c0.ChangeType = 9 (lost) and c1.ChangeType = 10 (found);
What Im hoping to achieve is some form of a given 'lost date' paired with only the next 'found date' in sequence (or NULL if no 'found date' exists). Im (pretty) sure this is possible but Im not seeing the path.
I was wondering about putting a sub-select in the first join and using a LIMIT 1 to get only one record but I don't see how to join this to the appropriate row in the main part of the select. MySQL tells me it doesn't exist. Fair enough.
The trick here is to stipulate 'and there is no other lost or found date between the lost and found dates', or, in SQL:
SELECT c0.ItemId, c0.ChangeDate, c1.ChangeDate
FROM Changes AS c0
JOIN Changes AS c1 ON c0.ItemId = c1.ItemId AND c1.ChangeDate >= c0.ChangeDate
WHERE c0.ChangeType = 9 -- Lost
AND c1.ChangeType = 10 -- Found
AND NOT EXISTS(SELECT *
FROM Changes AS c2
WHERE c2.ItemId = c1.ItemID
AND c2.ChangeType IN (9, 10) -- Lost or Found
AND c2.ChangeDate BETWEEN c0.ChangeDate AND c1.ChangeDate
AND (c2.ChangeDate != c0.ChangeDate AND c2.ChangeDate != c1.ChangeDate)
);
Because that is a correlated sub-query, it tends to slow down the query, but it should produce the correct rows.
There is an important caveat about the way I've eliminated the c0 and c1 rows by stipulating that the ChangeDate for the row in c2 should be different from either the lost date or the found date. However, the main query seems to allow for an item to be found on the same day that it is lost. There might be some other column - such as a ChangeId column - that is not mentioned in the query yet that could be used instead:
AND c2.ChangeID NOT IN (c0.ChangeID, c1.ChangeID)
You'll need to think about what happens if an item is lost on, say, 2011-06-07, and lost again on 2011-06-14, and only found on 2011-06-21. And what about if it is also found on 2011-06-28? Such problems should be prevented by the data entry processing, so the query above assumes there won't be such issues.
Generally when dealing with pairs of dates (e.g. start/end for scheduling) the advice is don't put them on separate rows. Put them in two columns of the same row. See Joe Celko's SQL Programming Style.
But that said, you can solve it with your current schema by searching doing another self-join to search for a ChangeDate between the two. If none is found (that is, if c2.* is null because of the outer join), then c0 and c1 are "adjacent."
select c0.ItemId, c0.ChangeDate, c1.ChangeDate
from Changes c0
inner join Changes c1 on
c0.ItemId = c1.ItemId and c1.ChangeDate > c0.ChangeDate
left outer join Changes c2 on
c0.ItemId = c2.ItemId and c2.ChangeDate > c0.ChangeDate
and c2.ChangeDate < c1.ChangeDate
and c2.ChangeType IN (9,10) -- edit
where c0.ChangeType = 9 (lost) and c1.ChangeType = 10 (found)
and c2.ItemId IS NULL;
In the above example, I've assumed that ChangeDate is unique, and I changed the >= to >. If ChangeDate is not unique, you'll have to come up with some other expression to test for c2 "between" c0 and c1.

Use MySQL to calculate difference between two entries in the same table

I have a series of meter reading stored in a table, each is identified by a building ID, a meter ID and the time at which it was recorded.
For each entry I would like to search for the entry that has the same ID numbers and the closest previous time, I would then like to use the previous time and the previous reading to calculate the length of the time step and the differential between the two readings.
so, I currently have:
BuildingID | MeterID | Date_and_Time | Reading
and I would like to produce:
BuildingID | MeterID | Date_and_Time | Time_Since_Previous_Read | Accumulation_Since previous_Read
two typical entries might look like this:
1 | 1 | 2010-10-09 17:56:20 | 119.6
1 | 1 | 2010-10-09 18:01:08 | 157.4
and I would like to produce:
1 | 1 | 2010-10-09 18:01:08 | 00:04:48 | 37.8
If no previous entry exists (i.e. for the first reading) i woudl like to rerun zeros for the time elapsed and the accumulation.
I would appreciate very much any help that could be offered on this, I made a concerted effort to find the answer in previous posts but to no avail, feel free to direct me to a good source if this has already been solved elsewhere.
thank you
Maybe like this?
SELECT a.BuildingID, a.MeterID, a.Date_and_Time,
a.Date_and_Time-b.Date_and_Time AS `Time_Since_Previous_Read`,
a.Reading-b.Reading AS `Accumulation`,
MAX(b.Date_and_Time) AS `otherdateandtime`
FROM `TABLENAME` AS `a`, `TABLENAME` AS `b`
WHERE a.BuildingID = b.BuildingID AND a.MeterID = b.MeterID
AND a.Date_and_Time>b.Date_and_Time
GROUP BY `a.Date_and_Time`
Try this:
/* 1*/SELECT
/* 2*/ r_B.BuildingID,
/* 3*/ r_B.MeterID,
/* 4*/ r_B.Date_and_Time,
/* 5*/ COALESCE(DATEDIFF(hh, r_A.Date_and_Time, r_B.Date_and_Time), 0) AS Time_Since_Previous_Read,
/* 6*/ COALESCE(r_B.Reading-r_A.Reading, 0) AS Accumulation_Since_Previous_Read
/* 7*/ FROM meterdata r_B
/* 8*/ LEFT OUTER JOIN meterdata r_A
/* 9*/ ON r_B.BuildingID = r_A.BuildingID AND r_B.MeterID = r_A.MeterID AND r_B.Date_and_Time > r_A.Date_and_Time
/*10*/ WHERE NOT EXISTS (SELECT nonelater.Date_and_Time FROM meterdata nonelater WHERE nonelater.BuildingID = r_B.BuildingID AND nonelater.MeterID = r_B.MeterID AND nonelater.Date_and_Time > r_A.Date_and_Time AND nonelater.Date_and_Time < r_B.Date_and_Time)
/*11*/ORDER BY r_B.BuildingID, r_B.MeterID, r_B.Date_and_Time
Here's how the design works:
Lines 7 and 8: the core of the query is a self-JOIN on the meterdata table. You need that to be able to find the difference between values in one row and values in another row. r_B is the later one, r_B the earlier one.
Line 8: Making it a LEFT OUTER JOIN means that it works even if there isn't an earlier r_A row; that part of the join will return NULLs.
Line 9: this constrains the JOIN to only join rows for the same building and meter, and makes sure the two rows are the right way around.
Line 10: If you didn't change the join any more, you'd have each r_B row joining to every single past row. To make sure that r_B matches only to the most recent past row, this line checks that there isn't another row more recent than r_A.
Line 6: this calculates the difference between the readings; if there isn't an earlier r_A row, this calculation will return NULL, so you need the COALESCE function to change that to zero.
At line 5, it does the same thing to find the time interval. For this demo I've used the SQL Server DATEDIFF function which won't give you exactly what you want, because on MySQL it only has one option, to calculate the difference in days; you may be able to use the INTERVAL function instead. Again, if there isn't a row r_A then COALESCE will change the NULL to zero.
Everything's there except for getting a time interval out in days, hours and minutes and formatting it nicely. Good luck with that.