-- 161_supply_layers_view_devgroup_key.sql -- #1650 — v_supply_layers_latest collapses L3 dev_groups: fix DISTINCT ON key. -- -- БАГ: v_supply_layers_latest (м.125) использует DISTINCT ON по 6-колоночному ключу -- (layer, district_name, complex_id, obj_class, source) — без dev_group_name. -- После м.128 логический UNIQUE-ключ supply_layers расширен до 7 колонок: -- (layer, district_name, complex_id, obj_class, dev_group_name, source, snapshot_date). -- Для L3 (layer=3, domrf_multiphase): complex_id=NULL у ВСЕХ строк, разные dev_group'ы -- района имеют одинаковый 6-column DISTINCT-ключ → VIEW схлопывает их в 1 строку. -- Измерено на prod: 8 L3 строк в view (по 1 на район) vs 102 фактических строк в таблице. -- Например, Академический: view→1 строка (607 юнитов), таблица→48 строк (41 887 юнитов). -- -- ЧИНИМ: добавляем dev_group_name в DISTINCT ON / ORDER BY view'а. -- Новый DISTINCT ON ключ: -- (layer, district_name, complex_id, obj_class, dev_group_name, source) -- зеркалит логический ключ uq_supply_layers_logical (м.128, без snapshot_date — view -- берёт max-снапшот, snapshot_date остаётся в ORDER BY как tiebreak). -- -- Зависимости: -- • supply_layers (м.125 + м.128) — таблица с колонкой dev_group_name (м.128 обязателен). -- • v_supply_layers_latest (м.125) — пересоздаётся здесь через CREATE OR REPLACE VIEW. -- • Проверено: у v_supply_layers_latest нет downstream-зависимых view/matview. -- CREATE OR REPLACE VIEW безопасен — колонки совместимы (добавляем dev_group_name в конец, -- существующие колонки 1-13 не переупорядочиваем). -- -- Idempotent: CREATE OR REPLACE VIEW — повторный прогон перезаписывает view тем же DDL, -- без ошибок. -- Deploy: auto-applied через _schema_migrations (ровно один раз по NN). -- Migration order: м.125 и м.128 уже применены на prod — этот файл идёт ПОСЛЕ них. -- После применения v_supply_layers_latest сразу вернёт корректные L3 per-dev_group строки -- без перезапуска воркера (view читает актуальные данные supply_layers напрямую). BEGIN; -- ── Пересоздать v_supply_layers_latest с dev_group_name в DISTINCT ON ───────────── -- Ключ: (layer, district_name, complex_id, obj_class, dev_group_name, source) -- зеркалит uq_supply_layers_logical (м.128). DISTINCT ON требует совпадения ведущих -- ORDER BY-выражений с DISTINCT-ключом — они перечислены первыми, далее snapshot_date -- DESC, computed_at DESC, id DESC как детерминированный tiebreak на max-снапшот. -- dev_group_name добавлена в SELECT как последняя колонка (позиция 14) — CREATE OR REPLACE -- VIEW в PG допускает добавление колонок в конец списка без пересоздания зависимостей. -- NULLS NOT DISTINCT логика: view просто делает DISTINCT ON, не UNIQUE-constraint — -- NULL в dev_group_name у L1/L2 работает как обычный NULL в DISTINCT ON (все L1/L2 строки -- с dev_group_name=NULL дедуплируются по остальным ключевым полям, что корректно). CREATE OR REPLACE VIEW v_supply_layers_latest AS SELECT DISTINCT ON (layer, district_name, complex_id, obj_class, dev_group_name, source) id, layer, district_name, complex_id, obj_class, units_estimate, area_estimate, expected_online_date, source, confidence, method, snapshot_date, computed_at, dev_group_name FROM supply_layers ORDER BY layer, district_name, complex_id, obj_class, dev_group_name, source, snapshot_date DESC, computed_at DESC, id DESC; COMMENT ON VIEW v_supply_layers_latest IS 'Свежий снапшот supply_layers на логический ключ (layer, district_name, complex_id, ' 'obj_class, dev_group_name, source) — max(snapshot_date), детерминир. tiebreak ' 'computed_at→id DESC. dev_group_name добавлен в DISTINCT ON в м.161 (#1650): ' 'без него L3 dev_group''ы района схлопывались в 1 строку (~95.6% потерь). ' 'Canonical read-path для future-supply (#950 Step6) и UI/forecast.'; COMMIT;