tags:

views:

2853

answers:

6

Hi

I have an Informix database containing measured temperature values for quite a few different locations. The measurements are taken every 15 min for all locations and then loaded with a timestamp into the same table. Table looks like this:

locId    dtg               temp
aaa      2009-02-25 10:00  15
bbb      2009-02-25 10:00  20
ccc      2009-02-25 10:00  24
aaa      2009-02-25 09:45  13
ccc      2009-02-25 09:45  16
bbb      2009-02-25 09:45  18
ddd      2009-02-25 09:45  12
aaa      2009-02-25 09:30  11
ccc      2009-02-25 09:30  14
bbb      2009-02-25 09:30  15
ddd      2009-02-25 09:30  10

Now I would like a query that present me with the change in temperature between the two last measurements for all stations. And also, only the ones that has an updated measurement. For example in the table above, location ddd would not be included. So the result becomes:

locId    change
aaa      2
bbb      2
ccc      8

I have tried alot but I can´t find any good solution. In reality it is about 700 locations that is asked from a web page so I think the query needs to be fairly efficient.

Would really appreciate some help!
//Jesper

+2  A: 

In pseudo-SQL you could do the query:

@now = Time Now

Select Oldest.LocId, Oldest.timestamp, Oldest.temp - Newest.temp as Change
(Select LocId, temp from Foo where timestamp < @now - 15 mins AND timestamp >= @now - 30 mins) Oldest
   left join
(Select LocId, temp from Foo where timestamp >= TimeNow - 15 mins) Newest
   on Oldest.LocId = Newest.LocId

Not sure if you define this as a 'good' solution, but it should work providing there are two data points for each location.

RM
+4  A: 
set now = select max(dtg) from table;
set then = select max(dtg) from table where dtg < now;

select locID, old.temp-new.temp from 
      table as old join
      table as new 
      on old.locId = new.locID
where
      old.dtg = then and
      new.dtg = now;


assumes that all times will be exact

BCS
By "all times will be exact" I think this code assumes that the latest two readings for all locations will be at the exact same time as each other. Seems unlikely. Also might want to edit the "Ware" to "Where"
JohnFx
that depends on how things are set up. it could easily happen in a pull model.
BCS
True. I just wanted to make sure the OP understood that it would only work in that scenario.
JohnFx
This code is using notation that is not supported by Informix.
Jonathan Leffler
+1  A: 
declare @dt_latest datetime, @dt_prev datetime  

select @dt_latest = max(dtg) from Measures
select @dt_prev = max(dtg) from Measures where dtg < @dt_latest  

select Latest.Locid, Latest.temp - Prev.temp
from Measures as "Latest"
inner join Measures as "Prev" on Latest.Locid = Prev.Locid
where Latest.dtg = @dt_latest
and Prev.dtg = @dt_prev

Edit: same as BCS basically, beat me to it!

MikeW
A: 

Give something like this a try. It may not be super efficient, but unlike some of the other answers it will return the dif for each LocID

SELECT DISTINCT LocID,
            (SELECT max(t3.temp)-min(t3.temp) from 
                   (SELECT TOP 2 T2.temp 
                    From Table2 T2 
                    Where (t2.Locid=t1.locid) 
                    order by DTG DESC) as t3
             ) as Diff
     FROM Table1 T1

Caveat: I wrote this using tSQL, but tried to stick to standard ANSI SQL as much as possible for portability to Informix.

JohnFx
A: 

I don't believe Informix has analytic functions like Oracle, but if it did this would be a excellent place to use them. What follows is an Oracle example using the analytic functions lag and max.

Setup script:

drop table temps;
create table temps (
locId varchar2(3),
dtg date,
temp number(3)
);

insert into temps values ('aaa', to_date('2009-02-25 10:00','yyyy-mm-dd hh:mi'), 15);
insert into temps values ('bbb', to_date('2009-02-25 10:00','yyyy-mm-dd hh:mi'), 20);
insert into temps values ('ccc', to_date('2009-02-25 10:00','yyyy-mm-dd hh:mi'), 24);
insert into temps values ('aaa', to_date('2009-02-25 09:45','yyyy-mm-dd hh:mi'), 13);
insert into temps values ('ccc', to_date('2009-02-25 09:45','yyyy-mm-dd hh:mi'), 16);
insert into temps values ('bbb', to_date('2009-02-25 09:45','yyyy-mm-dd hh:mi'), 18);
insert into temps values ('ddd', to_date('2009-02-25 09:45','yyyy-mm-dd hh:mi'), 12);
insert into temps values ('aaa', to_date('2009-02-25 09:30','yyyy-mm-dd hh:mi'), 11);
insert into temps values ('ccc', to_date('2009-02-25 09:30','yyyy-mm-dd hh:mi'), 14);
insert into temps values ('bbb', to_date('2009-02-25 09:30','yyyy-mm-dd hh:mi'), 15);
insert into temps values ('ddd', to_date('2009-02-25 09:30','yyyy-mm-dd hh:mi'), 10);
commit;

Oracle-specific query using analytic functions:

select locId, change
  from (
select t.locId,
       t.dtg,
       t.temp,
       -- difference between this records temperature and the record before it 
       t.temp - lag(t.temp) over (partition by t.locId order by t.dtg) change,
       -- max date for this location
       max(t.dtg) over (partition by t.locId) maxDtg,
       max(t.dtg) over (partition by 1) overallMaxDtg
  from temps t
 order by t.locId, t.dtg
 ) where maxDtg = dtg -- only most recent measurement
     and overallMaxDtg = maxDtg -- only stations with an 'updated measurement'
     ;

Result:

LOCID CHANGE

aaa   2
bbb   2
ccc   8

Good resource on Oracle analytics: http://www.psoug.org/reference/analytic_functions.html

jimmyorr
+4  A: 

Thanks to uglysmurf for providing the data in an SQL format.

Using IDS (IBM Informix Dynamic Server) version 11.50, the following query works.

CREATE TEMP TABLE temps
(
    locId   CHAR(3),
    dtg     DATETIME YEAR TO MINUTE,
    temp    SMALLINT
);
INSERT INTO temps VALUES ('aaa', '2009-02-25 10:00', 15);
INSERT INTO temps VALUES ('bbb', '2009-02-25 10:00', 20);
INSERT INTO temps VALUES ('ccc', '2009-02-25 10:00', 24);
INSERT INTO temps VALUES ('aaa', '2009-02-25 09:45', 13);
INSERT INTO temps VALUES ('ccc', '2009-02-25 09:45', 16);
INSERT INTO temps VALUES ('bbb', '2009-02-25 09:45', 18);
INSERT INTO temps VALUES ('ddd', '2009-02-25 09:45', 12);
INSERT INTO temps VALUES ('aaa', '2009-02-25 09:30', 11);
INSERT INTO temps VALUES ('ccc', '2009-02-25 09:30', 14);
INSERT INTO temps VALUES ('bbb', '2009-02-25 09:30', 15);
INSERT INTO temps VALUES ('ddd', '2009-02-25 09:30', 10);

SELECT latest.locID, latest.temp, prior.temp,
       latest.temp - prior.temp as delta_temp,
       latest.dtg, prior.dtg
    FROM temps latest, temps prior
    WHERE latest.locId = prior.locId
      AND latest.dtg = prior.dtg + 15 UNITS MINUTE
      AND latest.dtg = (SELECT MAX(dtg) FROM temps);

Results (more columns than requested, but you can easily trim the select list):

aaa 15 13 2 2009-02-25 10:00 2009-02-25 09:45
ccc 24 16 8 2009-02-25 10:00 2009-02-25 09:45
bbb 20 18 2 2009-02-25 10:00 2009-02-25 09:45

Note that this solution does not depend on CURRENT (or NOW); it works on the latest recorded data. The only part of the SELECT statement that is IDS-specific is the '+ 15 UNITS MINUTE'; that could also be written as '+ INTERVAL(15) MINUTE TO MINUTE' in Informix, and as '+ INTERVAL '15' MINUTE' in standard SQL (if the DBMS supports INTERVAL types). The use of DATETIME YEAR TO MINUTE in the table is Informix-specific; in a context like this, it is useful not to store information you are not interested in (such as the seconds).

Jonathan Leffler
Without analytics, I prefer this SQL-only solution, as opposed to BCS's which is using three separate queries and temporary variables...totally unnecessary.
jimmyorr
One tip for Jonathan Leffler: use more descriptive table aliases. "new" and "old" give more info than "a" and "b". For readability, I'd also rearrange the predicates a bit, so that "old.dtg = new.dtg - 15...", and "new.dtg = (SELECT MAX...)". That way it's clear what old and new's dtg are.
jimmyorr
@uglysmurf: Fair comment; I'll edit accordingly. For trial work, I use short aliases - I just didn't convert to 'non-trial' mode.
Jonathan Leffler