SQL, чтобы найти разницу между двумя строками
У меня есть база данных Informix, содержащая измеренные значения температуры для нескольких разных мест. Измерения проводятся каждые 15 минут для всех местоположений, а затем загружаются с меткой времени в ту же таблицу. Таблица выглядит так:
locId dtg temp ааа 25 февраля 2009 г. 10:00 15 bbb 25.02.2009, 10:00 20 ccc 2009-02-25 10:00 24 ааа 25-02-2009, 09:45 13 ccc 2009-02-25 09:45 16 bbb 25.02.2009 09:45 18 ддд 25.02.2009, 09:45 12 ааа 25 февраля 2009 г., 09:30 11 ccc 2009-02-25 09:30 14 BBB 25 февраля 2009 г., 09:30 15 ддд 25.02.2009, 09:30 10
Теперь мне нужен запрос, который представит мне изменение температуры между двумя последними измерениями для всех станций. А также только те, у которых есть обновленное измерение. Например, в приведенной выше таблице местоположение ddd не будет включено. В результате получается:
locId изменить ааа 2 BBB 2 ccc 8
Я много пробовал, но не могу найти хорошего решения. На самом деле с веб-страницы запрашивается около 700 местоположений, поэтому я думаю, что запрос должен быть достаточно эффективным.
Был бы очень признателен за помощь!
// Джеспер
Ответов (6)6
Я не верю, что в Informix есть аналитические функции, такие как Oracle, но если бы они были, это было бы отличным местом для их использования. Ниже приводится пример Oracle с использованием аналитических функций lag и max.
Сценарий установки:
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 запрос с использованием аналитических функций:
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'
;
Результат:
LOCID CHANGE
aaa 2
bbb 2
ccc 8
Хороший ресурс по аналитике Oracle: http://www.psoug.org/reference/analytic_functions.html
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
Изменить: то же самое, что и BCS, опередите меня!
В псевдо-SQL вы можете выполнить запрос:
@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
Не уверен, что вы определяете это как «хорошее» решение, но оно должно работать при наличии двух точек данных для каждого местоположения.
Попробуйте что-нибудь подобное. Это может быть не очень эффективно, но, в отличие от некоторых других ответов, он вернет разницу для каждого 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
Предостережение: я написал это, используя tSQL, но старался максимально придерживаться стандартного ANSI SQL для переносимости на Informix.
Спасибо uglysmurf за предоставление данных в формате SQL.
При использовании IDS (IBM Informix Dynamic Server) версии 11.50 следующий запрос работает.
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);
Результаты (больше столбцов, чем запрошено, но вы можете легко обрезать список выбора):
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
Обратите внимание, что это решение не зависит от ТЕКУЩЕГО (или СЕЙЧАС); он работает с последними записанными данными. Единственная часть оператора SELECT, относящаяся к IDS, - это " + 15 UNITS MINUTE
"; это также может быть записано как ' + INTERVAL(15) MINUTE TO MINUTE
' в Informix и как ' + INTERVAL '15' MINUTE
' в стандартном SQL (если СУБД поддерживает типы INTERVAL). Использование DATETIME YEAR TO MINUTE в таблице зависит от Informix; в таком контексте полезно не хранить информацию, которая вам не интересна (например, секунды).