-- Контекст: perf-батч после prod-EXPLAIN-охоты (follow-up эпика #1953, пункты #2/#6/#5).
--
-- #2/#6 — делаем physflat-индекс ПОКРЫВАЮЩИМ, дропаем старый partial.
-- EKB-wide дефолт market_metrics._STOCK_SQL (has_district=F/has_obj_ids=F) и
-- зеркальный _SALES_WINDOW_SQL дедуплицируют objective_lots до последнего снапшота
-- на физлот через
--   SELECT DISTINCT ON (project_name, corpus_name, section, floor, lot_number) …
--   ORDER BY …, snapshot_date DESC, id DESC
-- по квартирам (premise_kind='квартира'). Partial-индекс миграции 173
-- (objective_lots_physflat_latest_idx) совпадал с ключом DISTINCT ON, НО НЕ нёс
-- проецируемых колонок → Index-Only невозможен. Планировщик брал Parallel Seq Scan
-- + external merge Sort всех 1.75M квартир-строк (прод-EXPLAIN 2026-06-28:
-- ~5177 ms, temp-spill ~140 MB на трёх воркерах) вместо чтения из индекса.
--
-- Покрывающий индекс добавляет INCLUDE с колонками, которые проецируют/агрегируют
-- ОБА запроса ПОСЛЕ DISTINCT ON:
--   _STOCK_SQL:        rooms_int, area_pd, sales_start_date + sold_now-предикат
--                      (is_sold, contract_date, status)
--   _SALES_WINDOW_SQL: rooms_int, area_pd, contract_date, class
-- Объединение INCLUDE = {rooms_int, area_pd, sales_start_date, is_sold,
-- contract_date, status, class}. Ключ (physflat + snapshot_date DESC, id DESC) и
-- partial-WHERE (premise_kind='квартира') не меняются → тот же DISTINCT ON, но
-- теперь Index-Only Scan + Unique БЕЗ Sort.
-- Прод DRY-RUN (BEGIN; CREATE covering; EXPLAIN ANALYZE EKB-wide _STOCK_SQL;
-- ROLLBACK): Index Only Scan using objective_lots_physflat_covering_idx + Unique,
-- внешней сортировки НЕТ, Execution ~1642 ms (vs ~5177 ms). Размер ~252 MB.
--
-- price_per_m2_rub в INCLUDE НЕ кладём: его проецируют только analyze-inline
-- запросы (concepts._OBJECTIVE_MEDIAN_SQL, parcels district/geo-median), а они
-- НЕ фильтруют premise_kind='квартира' → partial-индекс к ним и так неприменим
-- (как и старый 173). Их обслуживают idx_objective_lots_pname_pricing / district-
-- /geo-пути — не регрессируют (никогда не использовали partial physflat-индекс).
--
-- Старый partial objective_lots_physflat_latest_idx ДРОПАЕМ: покрывающий индекс
-- его полностью замещает (тот же ключ + WHERE) и обслуживает все DISTINCT ON-
-- потребители (#1959 inline _STOCK/_SALES, competitors._SOLD_COUNT_SQL inline,
-- analyze-inline по квартирам). Держать оба — лишний write-amp на bulk objective-
-- scrape.
--
-- Порядок: CREATE covering → DROP old (covering уже на месте, ни одного окна без
-- индекса под DISTINCT ON). Idempotent (IF NOT EXISTS / IF EXISTS), BEGIN/COMMIT.
--
-- #5 — статистика objective_lots устаревала (last_analyze был 2026-05-11, planner
-- оценивал ~880 строк при реальных ~603k → плохие планы). ANALYZE в конце tx
-- (выполняется в транзакции штатно) освежает оценки под новый индекс.
--
-- ⚠ Деплой ВНЕ недельного окна objective-scrape: миграция дропает+создаёт ~252 MB
-- индекс на 1.75M строк (кратковременный SHARE-lock на objective_lots).
BEGIN;

CREATE INDEX IF NOT EXISTS objective_lots_physflat_covering_idx
    ON objective_lots (
        project_name,
        corpus_name,
        section,
        floor,
        lot_number,
        snapshot_date DESC,
        id DESC
    )
    INCLUDE (
        rooms_int,
        area_pd,
        sales_start_date,
        is_sold,
        contract_date,
        status,
        class
    )
    WHERE premise_kind = 'квартира';

DROP INDEX IF EXISTS objective_lots_physflat_latest_idx;

ANALYZE objective_lots;

COMMIT;
