The dear part is where the correlated subqueries must calculate the time difference for each individual row of each temperature_* table to find only one closest row for one column of one row in the main query.
This would be much faster if you could just select one row after and one row before the current time according to the index and only calculate the time difference for these two candidates. All you need for fast is the index in the time column in your tables.
I ignore the zone column, as its role remains unclear in the question, and it just adds more noise to the main problem. It should be easy to add to the request.
Without further submission, this query does it all at once:
SELECT time ,COALESCE(temp1 ,CASE WHEN timediff(time, time1a) > timediff(time1b, time) THEN (SELECT t.temperature FROM temperature_1 t WHERE t.time = y.time1b) ELSE (SELECT t.temperature FROM temperature_1 t WHERE t.time = y.time1a) END) AS temp1 ,COALESCE(temp2 ,CASE WHEN timediff(time, time2a) > timediff(time2b, time) THEN (SELECT t.temperature FROM temperature_2 t WHERE t.time = y.time2b) ELSE (SELECT t.temperature FROM temperature_2 t WHERE t.time = y.time2a) END) AS temp2 ,COALESCE(temp3 ,CASE WHEN timediff(time, time3a) > timediff(time3b, time) THEN (SELECT t.temperature FROM temperature_3 t WHERE t.time = y.time3b) ELSE (SELECT t.temperature FROM temperature_3 t WHERE t.time = y.time3a) END) AS temp3 FROM ( SELECT time ,max(t1) AS temp1 ,max(t2) AS temp2 ,max(t3) AS temp3 ,CASE WHEN max(t1) IS NULL THEN (SELECT t.time FROM temperature_1 t WHERE t.time < x.time ORDER BY t.time DESC LIMIT 1) ELSE NULL END AS time1a ,CASE WHEN max(t1) IS NULL THEN (SELECT t.time FROM temperature_1 t WHERE t.time > x.time ORDER BY t.time LIMIT 1) ELSE NULL END AS time1b ,CASE WHEN max(t2) IS NULL THEN (SELECT t.time FROM temperature_2 t WHERE t.time < x.time ORDER BY t.time DESC LIMIT 1) ELSE NULL END AS time2a ,CASE WHEN max(t2) IS NULL THEN (SELECT t.time FROM temperature_2 t WHERE t.time > x.time ORDER BY t.time LIMIT 1) ELSE NULL END AS time2b ,CASE WHEN max(t3) IS NULL THEN (SELECT t.time FROM temperature_3 t WHERE t.time < x.time ORDER BY t.time DESC LIMIT 1) ELSE NULL END AS time3a ,CASE WHEN max(t3) IS NULL THEN (SELECT t.time FROM temperature_3 t WHERE t.time > x.time ORDER BY t.time LIMIT 1) ELSE NULL END AS time3b FROM ( SELECT time, temperature AS t1, NULL AS t2, NULL AS t3 FROM temperature_1 UNION ALL SELECT time, NULL AS t1, temperature AS t2, NULL AS t3 FROM temperature_2 UNION ALL SELECT time, NULL AS t1, NULL AS t2, temperature AS t3 FROM temperature_3 ) AS x GROUP BY time ) y ORDER BY time;
β sqlfiddle
To explain
suqquery x replaces your temptimes and brings temperature to the result. If all three tables are synchronized and have a temperature at all identical points in time, the rest is not even necessary and very fast.
For each point in time when one of the three tables does not have a row, the temperature is selected in accordance with the instructions: take the "closest" from each table.
suqquery y concatenates the rows from x and selects the previous time ( time1a ) and the next time ( time1b ) according to the current time from each table where there is no temperature. These searches should be fast using an index.
the final query selects the temperature from the row with the closest time for each actually absent temperature.
This query might be easier if MySQL allows you to reference columns from more than one level above the current subquery. Bit can't. Works fine with PostgreSQL : -> sqlfiddle
It would also be simpler if more than one column could be returned from a correlated subquery, but I don't know how to do it in MySQL.
And it would be much simpler with CTE and window functions, but MySQL does not know these modern SQL functions (unlike other corresponding DBMSs).