All checks were successful
Deploy / changes (push) Successful in 12s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 2m17s
Deploy / build-worker (push) Successful in 3m34s
Deploy / deploy (push) Successful in 2m16s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 10s
58 lines
3.8 KiB
PL/PgSQL
58 lines
3.8 KiB
PL/PgSQL
-- 189_land_reservation_nulls_not_distinct.sql
|
||
-- #2464 — дедуп land_reservation и UNIQUE NULLS NOT DISTINCT на живой таблице.
|
||
--
|
||
-- БАГ. `_UPSERT_NO_ACT_SQL` (workers/tasks/izyatie_ocr_ingest.py) заканчивается
|
||
-- `ON CONFLICT DO NOTHING`, а единственный подходящий констрейнт —
|
||
-- `uq_land_reservation_cad_act UNIQUE (cad_num, act_number)` с обычной NULL-семантикой.
|
||
-- В Postgres NULL != NULL, поэтому у записей БЕЗ номера акта конфликт не наступает
|
||
-- никогда: `ON CONFLICT DO NOTHING` не срабатывает, и каждый недельный прогон
|
||
-- вставляет копию. Docstring таски при этом обещает per-batch дедуп и двухшаговый
|
||
-- upsert по (cad_num, doc_url) — ни того, ни другого в коде нет.
|
||
--
|
||
-- ЗАМЕР ПРОДА 2026-08-20 (до правки):
|
||
-- строк всего 297
|
||
-- из них с act_number IS NULL 297 (то есть все)
|
||
-- групп (cad_num, doc_url) с дублями 27
|
||
-- максимум копий в группе 11
|
||
-- лишних строк 270 (91% таблицы)
|
||
--
|
||
-- Проверено, что ключ подходит: ни у одного cad_num нет более одного doc_url
|
||
-- (max = 1), то есть NULLS NOT DISTINCT по (cad_num, act_number) НЕ схлопнет
|
||
-- разные документы одного участка. Дубли внутри групп — точные копии: по одному
|
||
-- различному значению reservation_kind и act_date на группу.
|
||
--
|
||
-- ЧТО УДАЛЯЕТСЯ. Строки-копии сверх первой (по возрастанию id) в каждой группе
|
||
-- (cad_num, act_number) среди act_number IS NULL. Это порождение бага, а не
|
||
-- пользовательские данные; таблица — кэш OCR-разбора PDF с сайта, пересобираемый
|
||
-- прогоном таски. Первая строка группы (минимальный id) сохраняется целиком.
|
||
--
|
||
-- ПОЧЕМУ ОТДЕЛЬНОЙ МИГРАЦИЕЙ, а не правкой CREATE TABLE: та же причина, что в
|
||
-- м.158 — исходный файл уже в _schema_migrations и на деплое пропускается.
|
||
-- Прецеденты NULLS NOT DISTINCT в репо: м.110, м.125, м.140, м.158. Prod = PG16.4.
|
||
-- Apply after: 188_regrant_quarter_price_index_fdw.sql
|
||
|
||
BEGIN;
|
||
|
||
-- #2752: блокирующий DDL обязан иметь lock_timeout — иначе ALTER TABLE встанет в
|
||
-- очередь за чужой сессией и уведёт за собой запросы приложения. Таблица крошечная
|
||
-- (297 строк), сам DDL мгновенный; пять секунд — про ОЖИДАНИЕ блокировки, не про
|
||
-- работу. Не дождались — миграция падает, а не подвешивает прод.
|
||
SET LOCAL lock_timeout = '5s';
|
||
|
||
-- 1) Дедуп: оставляем строку с минимальным id в каждой группе.
|
||
DELETE FROM land_reservation a
|
||
USING land_reservation b
|
||
WHERE a.act_number IS NULL
|
||
AND b.act_number IS NULL
|
||
AND a.cad_num = b.cad_num
|
||
AND a.id > b.id;
|
||
|
||
-- 2) Пересоздаём констрейнт с NULL-семантикой, при которой ON CONFLICT матчит.
|
||
ALTER TABLE land_reservation
|
||
DROP CONSTRAINT IF EXISTS uq_land_reservation_cad_act;
|
||
|
||
ALTER TABLE land_reservation
|
||
ADD CONSTRAINT uq_land_reservation_cad_act
|
||
UNIQUE NULLS NOT DISTINCT (cad_num, act_number);
|
||
|
||
COMMIT;
|