2009-11-11 6 views
0

У меня есть таблица температурных образцов с течением времени из нескольких источников, и я хочу найти минимальную, максимальную и среднюю температуру по всем источникам с установленными интервалами времени. На первый взгляд это легко сделать так:Self-Joins, cross-Joins and Grouping

SELECT MIN(temp), MAX(temp), AVG(temp) FROM samples GROUP BY time; 

Однако, все становится гораздо сложнее (до точки, где я тупик!), Если источники понижаться в и и а не игнорировать недостающие источники во время промежутки, о которых идет речь, я хочу использовать последние знающие температуры источников для отсутствующих образцов. Использование datetimes и построение интервалов (скажем, каждую минуту) между образцами, неравномерно распределенными со временем, еще больше усложняет ситуацию.

Я думаю, что должно быть возможно создать результаты, которые я хочу, сделав самосоединение в таблице образцов, где время из первой таблицы больше или равно времени второй таблицы, а затем вычисляет совокупные значения для строк, сгруппированных по источнику. Однако я не понимаю, как это сделать.

Вот мой тест стол:

+------+------+------+ 
| time | source | temp | 
+------+------+------+ 
| 1 | a | 20 | 
| 1 | b | 18 | 
| 1 | c | 23 | 
| 2 | b | 21 | 
| 2 | c | 20 | 
| 2 | a | 18 | 
| 3 | a | 16 | 
| 3 | c | 13 | 
| 4 | c | 15 | 
| 4 | a | 4 | 
| 4 | b | 31 | 
| 5 | b | 10 | 
| 5 | c | 16 | 
| 5 | a | 22 | 
| 6 | a | 18 | 
| 6 | b | 17 | 
| 7 | a | 20 | 
| 7 | b | 19 | 
+------+------+------+ 
INSERT INTO samples (time, source, temp) VALUES (1, 'a', 20), (1, 'b', 18), (1, 'c', 23), (2, 'b', 21), (2, 'c', 20), (2, 'a', 18), (3, 'a', 16), (3, 'c', 13), (4, 'c', 15), (4, 'a', 4), (4, 'b', 31), (5, 'b', 10), (5, 'c', 16), (5, 'a', 22), (6, 'a', 18), (6, 'b', 17), (7, 'a', 20), (7, 'b', 19); 

Для моей мин, макс и Avg вычисления, я хочу промежуточную таблицу, которая выглядит следующим образом:

+------+------+------+ 
| time | source | temp | 
+------+------+------+ 
| 1 | a | 20 | 
| 1 | b | 18 | 
| 1 | c | 23 | 
| 2 | b | 21 | 
| 2 | c | 20 | 
| 2 | a | 18 | 
| 3 | a | 16 | 
| 3 | b | 21 | 
| 3 | c | 13 | 
| 4 | c | 15 | 
| 4 | a | 4 | 
| 4 | b | 31 | 
| 5 | b | 10 | 
| 5 | c | 16 | 
| 5 | a | 22 | 
| 6 | a | 18 | 
| 6 | b | 17 | 
| 6 | c | 16 | 
| 7 | a | 20 | 
| 7 | b | 19 | 
| 7 | c | 16 | 
+------+------+------+ 

Следующий запрос получает меня близко к тому, что я хочу, но он принимает значение температуры первого результата источника, а не самое последнее за данный интервал времени:

SELECT s.dt as sdt, s.mac, ss.temp, MAX(ss.dt) as maxdt FROM (SELECT DISTINCT dt FROM samples) AS s CROSS JOIN samples AS ss WHERE s.dt >= ss.dt GROUP BY sdt, mac HAVING maxdt <= s.dt ORDER BY sdt ASC, maxdt ASC; 

+------+------+------+-------+ 
| sdt | mac | temp | maxdt | 
+------+------+------+-------+ 
| 1 | a | 20 |  1 | 
| 1 | c | 23 |  1 | 
| 1 | b | 18 |  1 | 
| 2 | a | 20 |  2 | 
| 2 | c | 23 |  2 | 
| 2 | b | 18 |  2 | 
| 3 | b | 18 |  2 | 
| 3 | a | 20 |  3 | 
| 3 | c | 23 |  3 | 
| 4 | a | 20 |  4 | 
| 4 | c | 23 |  4 | 
| 4 | b | 18 |  4 | 
| 5 | a | 20 |  5 | 
| 5 | c | 23 |  5 | 
| 5 | b | 18 |  5 | 
| 6 | c | 23 |  5 | 
| 6 | a | 20 |  6 | 
| 6 | b | 18 |  6 | 
| 7 | c | 23 |  5 | 
| 7 | b | 18 |  7 | 
| 7 | a | 20 |  7 | 
+------+------+------+-------+ 

Обновление: chadhoc (отличное имя, кстати!) Дает хорошее решение, к сожалению, не работает в MySQL, так как оно не поддерживает FULL JOIN, который он использует. К счастью, я считаю простой UNION является эффективной заменой:

-- Unify the original samples with the missing values that we've calculated 
(
    SELECT time, source, temp 
    FROM samples 
) 
UNION 
(-- Pull all the time/source combinations that we are missing from the sample set, along with the temp 
    -- from the last sampled interval for the same time/source combination if we do not have one 
    SELECT a.time, a.source, (SELECT t2.temp FROM samples AS t2 WHERE t2.time < a.time AND t2.source = a.source ORDER BY t2.time DESC LIMIT 1) AS temp 
    FROM  
    (-- All values we want to get should be a cross of time/temp 
    SELECT t1.time, s1.source 
    FROM 
    (SELECT DISTINCT time FROM samples) AS t1 
    CROSS JOIN 
    (SELECT DISTINCT source FROM samples) AS s1 
) AS a 
    LEFT JOIN samples s 
    ON a.time = s.time 
    AND a.source = s.source 
    WHERE s.source IS NULL 
) 
ORDER BY time, source; 

Update 2: MySQL дает следующий EXPLAIN выход для кода chadhoc в:

+----+--------------------+------------+------+---------------+------+---------+------+------+-----------------------------+ 
| id | select_type  | table  | type | possible_keys | key | key_len | ref | rows | Extra      | 
+----+--------------------+------------+------+---------------+------+---------+------+------+-----------------------------+ 
| 1 | PRIMARY   | temp  | ALL | NULL   | NULL | NULL | NULL | 18 |        | 
| 2 | UNION    | <derived4> | ALL | NULL   | NULL | NULL | NULL | 21 |        | 
| 2 | UNION    | s   | ALL | NULL   | NULL | NULL | NULL | 18 | Using where     | 
| 4 | DERIVED   | <derived6> | ALL | NULL   | NULL | NULL | NULL | 3 |        | 
| 4 | DERIVED   | <derived5> | ALL | NULL   | NULL | NULL | NULL | 7 |        | 
| 6 | DERIVED   | temp  | ALL | NULL   | NULL | NULL | NULL | 18 | Using temporary    | 
| 5 | DERIVED   | temp  | ALL | NULL   | NULL | NULL | NULL | 18 | Using temporary    | 
| 3 | DEPENDENT SUBQUERY | t2   | ALL | NULL   | NULL | NULL | NULL | 18 | Using where; Using filesort | 
| NULL | UNION RESULT  | <union1,2> | ALL | NULL   | NULL | NULL | NULL | NULL | Using filesort    | 
+----+--------------------+------------+------+---------------+------+---------+------+------+-----------------------------+ 

Я был в состоянии получить код Чарльза работает как так:

SELECT T.time, S.source, 
    COALESCE(
    D.temp, 
    (
     SELECT temp FROM samples 
     WHERE source = S.source AND time = (
     SELECT MAX(time) 
     FROM samples 
     WHERE 
      source = S.source 
      AND time < T.time 
    ) 
    ) 
) AS temp 
FROM (SELECT DISTINCT time FROM samples) AS T 
CROSS JOIN (SELECT DISTINCT source FROM samples) AS S 
    LEFT JOIN samples AS D 
ON D.source = S.source AND D.time = T.time 

Его объяснение:

+----+--------------------+------------+------+---------------+------+---------+------+------+-----------------+ 
| id | select_type  | table  | type | possible_keys | key | key_len | ref | rows | Extra   | 
+----+--------------------+------------+------+---------------+------+---------+------+------+-----------------+ 
| 1 | PRIMARY   | <derived5> | ALL | NULL   | NULL | NULL | NULL | 3 |     | 
| 1 | PRIMARY   | <derived4> | ALL | NULL   | NULL | NULL | NULL | 7 |     | 
| 1 | PRIMARY   | D   | ALL | NULL   | NULL | NULL | NULL | 18 |     | 
| 5 | DERIVED   | temp  | ALL | NULL   | NULL | NULL | NULL | 18 | Using temporary | 
| 4 | DERIVED   | temp  | ALL | NULL   | NULL | NULL | NULL | 18 | Using temporary | 
| 2 | DEPENDENT SUBQUERY | temp  | ALL | NULL   | NULL | NULL | NULL | 18 | Using where  | 
| 3 | DEPENDENT SUBQUERY | temp  | ALL | NULL   | NULL | NULL | NULL | 18 | Using where  | 
+----+--------------------+------------+------+---------------+------+---------+------+------+-----------------+ 

ответ

1

Я думаю, что вы получите лучшую производительность, используя функции ранжирования и окон в mySql, но, к сожалению, я не знаю таких, как реализация TSQL. Вот совместимый решение ANSI, который будет работать, хотя:

-- Full join across the sample set and anything missing from the sample set, pulling the missing temp first if we do not have one 
select coalesce(c1.[time], c2.[time]) as dt, coalesce(c1.source, c2.source) as source, coalesce(c2.temp, c1.temp) as temp 
from samples c1 
full join (-- Pull all the time/source combinations that we are missing from the sample set, along with the temp 
      -- from the last sampled interval for the same time/source combination if we do not have one 
      select a.time, a.source, 
        (select top 1 t2.temp from samples t2 where t2.time < a.time and t2.source = a.source order by t2.time desc) as temp 
      from  
       ( -- All values we want to get should be a cross of time/samples 
        select t1.[time], s1.source 
        from 
        (select distinct [time] from samples) as t1 
        cross join 
        (select distinct source from samples) as s1 
       ) a 
      left join samples s 
      on a.[time] = s.time 
      and a.source = s.source 
      where s.source is null 
     ) c2 
on c1.time = c2.time 
and c1.source = c2.source 
order by dt, source 
0

Я знаю, это выглядит сложным, но он отформатирован, чтобы объяснить себя ... Он должен работать ... Надеюсь, у вас есть только три источника ... Если у вас есть произвольное количество источников, чем это не будет работать ... В этом случае см. второй запрос ... EDIT: Удалена первая попытка

EDIT: Если вы не знаете источников раньше времени, вам нужно будет сделать что-то, где вы создадите промежуточный набор результатов, который «Заполняет» недостающие значения. :

2-й РЕДАКТОР: Убрана необходимость в Coalesce путем перемещения логики для извлечения последнего последнего отсчета температуры для каждого источника из предложения Select в условие Join.

Select T.Time, Max(Temp) MaxTemp, 
    Min(Temp) MinTemp, Avg(Temp) AvgTemp 
From 
    (Select T.TIme, S.Source, D.Temp 
    From (Select Distinct Time From Samples) T 
    Cross Join 
     (Select Distinct Source From Samples) S 
    Left Join Samples D 
     On D.Source = S.Source 
      And D.Time = 
       (Select Max(Time) 
       From Samples 
       Where Source = S.Source 
        And Time <= T.Time)) Z 
Group By T.Time 
+0

Спасибо, Чарльз, но ваше решение предполагает, что источники известны заранее. Есть ли у вас какие-либо предложения, когда они не известны? – pr1001

+0

Добавлен еще один sql-запрос, если вы не знаете источники ... –

+0

После изменения IsNull в COALESCE я смог получить запрос для работы с моей базой данных MySQL. Благодарю. – pr1001