При анализе результат не передается клиенту, время IO и передачи по сети не учитывается (а у Вас там 1.5 ляма строк, как сами сказали, к тому же относительно длинные строки могут быть).
И что Вы хотите добиться, сортируя огромный массив? Для дальнейшего анализа сортировка обычно не нужна, а для визуального анализа - нужна выборка поменьше. Уберете сортировку - сэкономите еще кучу времени. Это очень дорогая операция. (Хотя, если поставите ORDER BY device_id, time то сортировка может уйти из-за того, что сам по себе индекс хранит значения в отсортированном виде)
И вот это
CASE
WHEN (duration > (extract(epoch from (time - '2015-08-29 12:36:50'))) )
THEN extract(epoch from (time - '2015-08-29 12:36:50'))
ELSE duration
END AS trim_duration
Заменить на
LEAST(duration, extract(epoch from (time - '2015-08-29 12:36:50')))
Так чуть понятнее и короче, хотя сути и стоимости особо не меняет.