tags:

views:

35

answers:

2

I have 2 tables, one containing meter IDs, and another containing measurements for some of the meters in the first table. This is the table structure:

MeterConfig:

  • MeterID (int)
  • MeterNumber (char[16])
  • Type (char[25])

Readings:

  • MeterID (int)
  • Date (datetime)
  • Value (numeric(18,6))

I need to get the last reading (and its date) from a given period for each meter, as well as the meter number. I managed to do this in T-SQL, although I'm not particularly pleased with the way I did it using this query:

select distinct
 cfg.MeterNumber,
 (select top 1 r.Date from Readings as r where r.Date between @startdate and @endDate and r.MeterID = cfg.MeterID order by r.Date desc) as Date,
 (select top 1 r.Value from Readings as r where r.Date between @startdate and @endDate and r.MeterID = cfg.MeterID order by r.Date desc) as Value
from
 MeterConfig cfg, Readings r1
where cfg.MeterID = r1.MeterID and r1.Date between @startdate and @endDate;

How can I do this more efficiently?

+1  A: 

Assuming the dates in Readings are unique (ic include a timestamp), following should be equivalent to your query.

SELECT  DISTINCT cfg.MeterNumber
        , r1.Date
        , r1.Value         
FROM    MeterConfig cfg
        INNER JOIN Readings r1 ON cfg.MeterID = r1.MeterID 
        INNER JOIN (        
          SELECT  date = MAX(r.Date)
          FROM    Readings r
          WHERE   r.Date BETWEEN @StartDate AND @EndDate
        ) r2 On r2.date = r1.date          
Lieven
Thank you, this seems much better. One change, though; the first part should be `SELECT DISTINCT cfg.MeterNumber, r1.Date, r1.Value`. **r** is out of scope.
alex
@alex, thx, I've updated the answer. If you believe this fully answers your question, you can accept this as the answer.
Lieven
@Lieven I'll leave the question open for a few more hours after which, if it's the best answer available, I'll mark your answer as accepted. Thank you!
alex
After anivas posted his answer, I just noticed something. Your query only gives me back 20 results vs. his 40. I looked into it and noticed that your query uses `date = MAX(r.Date)`. I might have meters that were read Oct. 30, while others were read on the 27th; the ones read prior to the last date are ignored.
alex
I thought that was the requirement. IF anivas' answer is correct, let us know and accept his answer.
Lieven
Sorry if I wasn't clear enough. I've upvoted both of you, since both your answers were helpful.
alex
+2  A: 
WITH CTE AS (
SELECT mc.MeterID, Date, Value, ROW_NUMBER() OVER (PARTITION BY mc.MeterID ORDER BY Date DESC) as Rank
FROM  MeterConfig mc
INNER JOIN Readings rd
ON mc.MeterID = rd.MeterID
WHERE rd.Date BETWEEN @startdate AND @endDate)
SELECT * FROM CTE WHERE Rank = 1
anivas
Nice solution; way over my head, but nice. Thanks!
alex