\set ON_ERROR_STOP on BEGIN; CREATE TABLE IF NOT EXISTS weather ( dateTime TIMESTAMP NOT NULL, city VARCHAR(255) NOT NULL, tempMax DOUBLE PRECISION, tempMin DOUBLE PRECISION, tempAvg DOUBLE PRECISION, precipitation DOUBLE PRECISION, windAvg DOUBLE PRECISION, windMax DOUBLE PRECISION, visibilityMin DOUBLE PRECISION, visibilityAvg DOUBLE PRECISION, snowAvg DOUBLE PRECISION, atmPressure DOUBLE PRECISION, dewPoint DOUBLE PRECISION, humidity DOUBLE PRECISION, sunDuration DOUBLE PRECISION, phenomena TEXT[], UNIQUE(city, dateTime) ); WITH settings AS ( SELECT date_trunc('hour', CURRENT_TIMESTAMP)::timestamp AS end_at, (date_trunc('hour', CURRENT_TIMESTAMP) - INTERVAL '18 months')::timestamp AS start_at ), cities(city, climate_offset) AS ( VALUES ('Ainaži', -0.8), ('Alūksne', -2.3), ('Bauska', 0.7), ('Dagda', -1.7), ('Daugavgrīva', 0.6), ('Daugavpils', -0.9), ('Dobele', 0.8), ('Gulbene', -2.0), ('Jelgava', 0.9), ('Kalnciems', 0.7), ('Kolka', 0.3), ('Kuldīga', 0.8), ('Lielpēči', 0.2), ('Liepāja', 1.1), ('Madona', -1.8), ('Mērsrags', 0.2), ('Pāvilosta', 0.9), ('Piedruja', -1.4), ('Priekuļi', -1.6), ('Rēzekne', -1.2), ('Rīga', 1.3), ('Rucava', 1.0), ('Rūjiena', -1.0), ('Saldus', 0.3), ('Sigulda', -0.8), ('Sīļi', -1.3), ('Skrīveri', -0.5), ('Skulte', 0.0), ('Stende', -0.3), ('Valmiera', -1.2), ('Ventspils', 0.9), ('Vičaki', 0.2), ('Zīlāni', -1.1), ('Zosēni', -2.1) ), hours AS ( SELECT generate_series(settings.start_at, settings.end_at, INTERVAL '1 hour') AS observed_at FROM settings ), base AS ( SELECT hours.observed_at, cities.city, cities.climate_offset, abs(hashtext(cities.city || hours.observed_at::text)) AS sample_hash, 7.5 + 12.5 * sin(2 * pi() * (extract(doy FROM hours.observed_at) - 172) / 365.25) + 2.8 * sin(2 * pi() * (extract(hour FROM hours.observed_at) - 9) / 24) + cities.climate_offset + 1.8 * sin(extract(epoch FROM hours.observed_at) / 173000 + cities.climate_offset) AS temperature FROM hours CROSS JOIN cities ), weather_values AS ( SELECT *, CASE WHEN sample_hash % 100 < 13 THEN round(((sample_hash % 190) / 10.0 + 0.2)::numeric, 1)::double precision ELSE 0.0 END AS rain, round((1.2 + (sample_hash % 65) / 10.0)::numeric, 1)::double precision AS wind, round((58 + (sample_hash % 34) + 8 * cos(2 * pi() * extract(doy FROM observed_at) / 365.25))::numeric, 1)::double precision AS relative_humidity FROM base ), final_values AS ( SELECT *, GREATEST(0.0, LEAST(100.0, relative_humidity)) AS bounded_humidity, CASE WHEN temperature < 1.0 AND rain > 0 THEN round((rain * 0.8 + (sample_hash % 25) / 10.0)::numeric, 1)::double precision WHEN temperature < -2.0 THEN round(((sample_hash % 80) / 10.0)::numeric, 1)::double precision ELSE 0.0 END AS snow, CASE WHEN extract(hour FROM observed_at) BETWEEN 7 AND 18 AND rain = 0 THEN round((35 + sample_hash % 26)::numeric, 1)::double precision ELSE 0.0 END AS sunshine_minutes FROM weather_values ) INSERT INTO weather ( dateTime, city, tempMax, tempMin, tempAvg, precipitation, windAvg, windMax, visibilityMin, visibilityAvg, snowAvg, atmPressure, dewPoint, humidity, sunDuration, phenomena ) SELECT observed_at, city, CASE WHEN sample_hash % 997 = 0 THEN NULL ELSE round((temperature + 1.8 + (sample_hash % 12) / 10.0)::numeric, 1)::double precision END, CASE WHEN sample_hash % 991 = 0 THEN NULL ELSE round((temperature - 1.6 - (sample_hash % 10) / 10.0)::numeric, 1)::double precision END, round(temperature::numeric, 1)::double precision, rain, wind, round((wind + 1.5 + (sample_hash % 70) / 10.0)::numeric, 1)::double precision, CASE WHEN rain > 12 THEN round((0.8 + sample_hash % 20 / 10.0)::numeric, 1)::double precision WHEN bounded_humidity > 88 THEN round((1.5 + sample_hash % 40 / 10.0)::numeric, 1)::double precision ELSE round((8 + sample_hash % 80 / 10.0)::numeric, 1)::double precision END, CASE WHEN rain > 12 THEN round((3 + sample_hash % 40 / 10.0)::numeric, 1)::double precision ELSE round((12 + sample_hash % 90 / 10.0)::numeric, 1)::double precision END, snow, round((1000 + sample_hash % 310 / 10.0 + 5 * sin(extract(epoch FROM observed_at) / 250000))::numeric, 1)::double precision, round((temperature - (100 - bounded_humidity) / 5.0)::numeric, 1)::double precision, CASE WHEN sample_hash % 983 = 0 THEN NULL ELSE round(bounded_humidity::numeric, 1)::double precision END, sunshine_minutes, CASE WHEN snow > 0.5 THEN ARRAY['snow']::text[] WHEN rain > 12 THEN ARRAY['heavy rain', 'overcast']::text[] WHEN rain > 0 THEN ARRAY['rain']::text[] WHEN bounded_humidity > 88 THEN ARRAY['fog']::text[] WHEN wind > 6.5 THEN ARRAY['windy']::text[] WHEN sunshine_minutes > 0 THEN ARRAY['clear']::text[] ELSE ARRAY['cloudy']::text[] END FROM final_values ON CONFLICT (city, dateTime) DO UPDATE SET tempMax = EXCLUDED.tempMax, tempMin = EXCLUDED.tempMin, tempAvg = EXCLUDED.tempAvg, precipitation = EXCLUDED.precipitation, windAvg = EXCLUDED.windAvg, windMax = EXCLUDED.windMax, visibilityMin = EXCLUDED.visibilityMin, visibilityAvg = EXCLUDED.visibilityAvg, snowAvg = EXCLUDED.snowAvg, atmPressure = EXCLUDED.atmPressure, dewPoint = EXCLUDED.dewPoint, humidity = EXCLUDED.humidity, sunDuration = EXCLUDED.sunDuration, phenomena = EXCLUDED.phenomena; ANALYZE weather; COMMIT; SELECT count(*) AS synthetic_rows, count(DISTINCT city) AS stations, min(dateTime) AS first_observation, max(dateTime) AS last_observation FROM weather;