perf(objective): request-path consumers dedup inline, not via whole-table view (#1964)
deep-review #1964: v_objective_lots_latest has NO premise/district filter inside, so a consumer's outer WHERE cannot push below DISTINCT ON → the view materializes the WHOLE table (Parallel Seq Scan + external Sort 1.76M rows, ~55MB spill) on every query. For REQUEST-PATH consumers inside analyze_parcel this is a ~19x latency regression vs the pre-#1964 raw-table plan. DISPROVEN remedy (NOT applied): a full index on the physflat-key does NOT help — DISTINCT ON selects ol.* (51 cols, width≈945) so index-only-unique is impossible; the planner ignores the index (seq-scan+sort still cheaper) and even forced it is ~3.9 s. A 142MB index for zero request-path benefit + slower bulk-INSERT during objective-scrape is wrong. Honors original #1964 decision "no new index". Prod EXPLAIN (Академический / 3km radius, 2026-06-28): consumer via view inline (this commit) concepts median 5854 ms 1640 ms (bitmap district + sort) parcels district 5854 ms 1640 ms parcels geo-median 6443 ms 122 ms (NestedLoop geo->complex bitmap) parcels obj_pricing 5721 ms 441 ms (project bitmap per nearby ЖК) FIX: keep v_objective_lots_latest ONLY for batch/background/cached consumers (supply_layers L1, competitors._SOLD_COUNT_SQL, special_indices [/forecast bg task 30-180s], admin, landing). Revert the 4 request-path consumers inside analyze_parcel to inline DISTINCT ON (physflat-key, latest snapshot) with the filter pushed INTO the CTE so the district/spatial/project index applies: - concepts._OBJECTIVE_MEDIAN_SQL - parcels.py district price block - parcels.py geo-radius median (complex_id-scoped) - parcels.py obj_pricing CTE (project_name-scoped; aggregates over deduped set) Migration 175 header CORRECTED: accurately states the partial mig-173 index does NOT serve the view (qual can't push below DISTINCT ON), the full index is disproven/not added, and which consumers use the view vs inline. No DDL change (still view-only). Tests: +guards (concepts/obj_pricing dedup inline, not view; obj_pricing physflat DISTINCT ON; perf-pushdown scope preserved). 965 passed.
This commit is contained in:
parent
e67cb721bf
commit
74f1ffb500
5 changed files with 207 additions and 72 deletions
|
|
@ -72,16 +72,33 @@ _DISTRICT_FOR_POINT_SQL = text(
|
|||
|
||||
# Медиана цены продажи жилья из объявлений Objective по району + размер выборки.
|
||||
# price_per_m2_rub — РУБЛИ. Санитарный диапазон отсекает мусор. CAST psycopg v3.
|
||||
# #1964: читаем v_objective_lots_latest (physflat-дедуп view) — сырой objective_lots
|
||||
# раздут ~2.91× (мульти lot_id на физлот) → sample_size и гейт n≥:_MIN_OBJECTIVE_SAMPLE
|
||||
# были по пере-листингам, а не по физическим квартирам; вес медианы тоже смещался.
|
||||
#
|
||||
# #1964 physflat-дедуп: objective_lots раздут ~2.91× (мульти objective_lot_id на
|
||||
# один физлот через пере-листинги) → sample_size и гейт n≥:_MIN_OBJECTIVE_SAMPLE
|
||||
# были по пере-листингам, вес медианы смещался. Дедупим INLINE через DISTINCT ON
|
||||
# (physflat-ключ, последний снапшот snapshot_date DESC, id DESC) — НЕ через общий
|
||||
# physflat-VIEW. Причина (deep-review #1964, прод-EXPLAIN): view не несёт
|
||||
# district-фильтр внутри, qual не проталкивается ниже DISTINCT ON → view сортирует
|
||||
# ВСЮ таблицу 1.76M (~5.8 s, request-path внутри analyze_parcel). Inline с district
|
||||
# В CTE → bitmap по району ~240k строк + sort (~1.7 s), индекс по district работает.
|
||||
# Сегмент-фильтры (price NOT NULL / диапазон) объективны по физлоту → применяем
|
||||
# ПОСЛЕ дедупа. Зеркало паттерна market_metrics._STOCK_SQL.
|
||||
_OBJECTIVE_MEDIAN_SQL = text(
|
||||
"""
|
||||
WITH latest AS (
|
||||
SELECT DISTINCT ON (
|
||||
project_name, corpus_name, section, floor, lot_number
|
||||
)
|
||||
price_per_m2_rub
|
||||
FROM objective_lots
|
||||
WHERE district = CAST(:dn AS text)
|
||||
ORDER BY project_name, corpus_name, section, floor, lot_number,
|
||||
snapshot_date DESC, id DESC
|
||||
)
|
||||
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price_per_m2_rub) AS median_ppm2,
|
||||
COUNT(*) AS sample_size
|
||||
FROM v_objective_lots_latest
|
||||
WHERE district = CAST(:dn AS text)
|
||||
AND price_per_m2_rub IS NOT NULL
|
||||
FROM latest
|
||||
WHERE price_per_m2_rub IS NOT NULL
|
||||
AND price_per_m2_rub BETWEEN CAST(:lo AS numeric) AND CAST(:hi AS numeric)
|
||||
"""
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1869,30 +1869,52 @@ def analyze_parcel(
|
|||
3000
|
||||
)
|
||||
),
|
||||
obj_pricing AS (
|
||||
-- Pushdown: агрегируем objective_lots ТОЛЬКО для ближних obj_id.
|
||||
-- AVG/COUNT per domrf_obj_id независимы между группами, поэтому
|
||||
-- ограничение scope даёт байт-идентичные значения для тех obj_id,
|
||||
-- что реально джойнятся ниже (далёкие медианы раньше считались зря).
|
||||
-- #1964: читаем из v_objective_lots_latest (physflat-дедуп view) —
|
||||
-- сырой objective_lots раздут ~2.91× (мульти lot_id на физлот) →
|
||||
-- units_sold/units_available карточки конкурента были завышены.
|
||||
-- View = 1 строка на физлот; маппинг прод 1:1 по domrf_obj_id
|
||||
-- (303 строки = 303 distinct) → COUNT(*) корректен.
|
||||
SELECT
|
||||
cm.domrf_obj_id,
|
||||
ROUND(AVG(ol.price_per_m2_rub)::numeric, 0) AS avg_price_per_m2_rub,
|
||||
ROUND(AVG(ol.area_pd)::numeric, 1) AS avg_area_pd,
|
||||
COUNT(*) FILTER (WHERE ol.is_sold) AS units_sold,
|
||||
COUNT(*) FILTER (WHERE NOT ol.is_sold) AS units_available,
|
||||
COUNT(*) FILTER (
|
||||
WHERE ol.price_per_m2_rub IS NOT NULL
|
||||
) AS lots_with_price
|
||||
nearby_projects AS (
|
||||
-- Имена Objective-проектов ближних obj_id — scope для physflat-дедупа.
|
||||
SELECT DISTINCT cm.domrf_obj_id, cm.objective_complex_name
|
||||
FROM objective_complex_mapping cm
|
||||
JOIN v_objective_lots_latest ol
|
||||
ON ol.project_name = cm.objective_complex_name
|
||||
WHERE cm.domrf_obj_id IN (SELECT obj_id FROM nearby_obj)
|
||||
GROUP BY cm.domrf_obj_id
|
||||
),
|
||||
obj_lots_latest AS (
|
||||
-- #1964 physflat-дедуп: objective_lots раздут ~2.91× (мульти lot_id
|
||||
-- на физлот) → units_sold/units_available карточки конкурента были
|
||||
-- завышены. Дедупим INLINE (DISTINCT ON physflat-ключ, последний
|
||||
-- снапшот), scope протолкнут через project_name ближних ЖК. НЕ через
|
||||
-- общий physflat-VIEW: он материализует ВСЮ таблицу (join не проходит
|
||||
-- ниже DISTINCT ON) → seq-scan+sort 1.76M (~5.7 s на request-path
|
||||
-- analyze_parcel); inline scoped → ~доли секунды (прод-EXPLAIN #1964).
|
||||
SELECT DISTINCT ON (
|
||||
ol.project_name, ol.corpus_name, ol.section, ol.floor, ol.lot_number
|
||||
)
|
||||
ol.project_name,
|
||||
ol.price_per_m2_rub,
|
||||
ol.area_pd,
|
||||
ol.is_sold
|
||||
FROM objective_lots ol
|
||||
WHERE ol.project_name IN (
|
||||
SELECT objective_complex_name FROM nearby_projects
|
||||
)
|
||||
ORDER BY ol.project_name, ol.corpus_name, ol.section, ol.floor,
|
||||
ol.lot_number, ol.snapshot_date DESC, ol.id DESC
|
||||
),
|
||||
obj_pricing AS (
|
||||
-- Pushdown: агрегируем ТОЛЬКО для ближних obj_id. AVG/COUNT per
|
||||
-- domrf_obj_id независимы между группами → scope даёт идентичные
|
||||
-- значения для джойнящихся obj_id. Маппинг прод 1:1 по domrf_obj_id
|
||||
-- (303 строки = 303 distinct) → COUNT(*) по дедуп-физлотам корректен.
|
||||
SELECT
|
||||
np.domrf_obj_id,
|
||||
ROUND(AVG(oll.price_per_m2_rub)::numeric, 0) AS avg_price_per_m2_rub,
|
||||
ROUND(AVG(oll.area_pd)::numeric, 1) AS avg_area_pd,
|
||||
COUNT(*) FILTER (WHERE oll.is_sold) AS units_sold,
|
||||
COUNT(*) FILTER (WHERE NOT oll.is_sold) AS units_available,
|
||||
COUNT(*) FILTER (
|
||||
WHERE oll.price_per_m2_rub IS NOT NULL
|
||||
) AS lots_with_price
|
||||
FROM nearby_projects np
|
||||
JOIN obj_lots_latest oll
|
||||
ON oll.project_name = np.objective_complex_name
|
||||
GROUP BY np.domrf_obj_id
|
||||
)
|
||||
SELECT o.obj_id,
|
||||
o.comm_name,
|
||||
|
|
@ -2541,10 +2563,24 @@ def analyze_parcel(
|
|||
dp_row = (
|
||||
db.execute(
|
||||
text("""
|
||||
-- #1964: physflat-дедуп view вместо сырого objective_lots
|
||||
-- (раздут ~2.91× мульти lot_id на физлот) → sample_size и
|
||||
-- вес медианы теперь по физическим квартирам, не по
|
||||
-- пере-листингам.
|
||||
-- #1964 physflat-дедуп: objective_lots раздут ~2.91× (мульти
|
||||
-- lot_id на физлот) → sample_size/вес медианы были по
|
||||
-- пере-листингам. Дедупим INLINE через DISTINCT ON
|
||||
-- (physflat-ключ, последний снапшот), district протолкнут В
|
||||
-- CTE. НЕ через v_objective_lots_latest: view не несёт
|
||||
-- district внутри → qual не проходит ниже DISTINCT ON →
|
||||
-- сортировка ВСЕЙ таблицы 1.76M (~5.8 s на request-path
|
||||
-- analyze_parcel). Inline → bitmap по району + sort (~1.7 s).
|
||||
WITH latest AS (
|
||||
SELECT DISTINCT ON (
|
||||
project_name, corpus_name, section, floor, lot_number
|
||||
)
|
||||
price_per_m2_rub
|
||||
FROM objective_lots
|
||||
WHERE district = CAST(:dn AS text)
|
||||
ORDER BY project_name, corpus_name, section, floor,
|
||||
lot_number, snapshot_date DESC, id DESC
|
||||
)
|
||||
SELECT
|
||||
MIN(price_per_m2_rub) AS price_min,
|
||||
MAX(price_per_m2_rub) AS price_max,
|
||||
|
|
@ -2552,9 +2588,8 @@ def analyze_parcel(
|
|||
ORDER BY price_per_m2_rub
|
||||
) AS price_median,
|
||||
COUNT(*) AS sample_size
|
||||
FROM v_objective_lots_latest
|
||||
WHERE district = CAST(:dn AS text)
|
||||
AND price_per_m2_rub IS NOT NULL
|
||||
FROM latest
|
||||
WHERE price_per_m2_rub IS NOT NULL
|
||||
AND price_per_m2_rub BETWEEN 30000 AND 600000
|
||||
"""),
|
||||
{"dn": district_row["district_name"]},
|
||||
|
|
@ -2913,31 +2948,58 @@ def analyze_parcel(
|
|||
grp_row = (
|
||||
db.execute(
|
||||
text("""
|
||||
-- #1964: physflat-дедуп view (сырой objective_lots
|
||||
-- раздут ~2.91× мульти lot_id на физлот) → n и вес
|
||||
-- медианы по физическим квартирам, гейт n≥10 честный.
|
||||
-- #1964 physflat-дедуп: objective_lots раздут ~2.91×
|
||||
-- (мульти lot_id на физлот) → n/вес медианы/гейт n≥10
|
||||
-- были по пере-листингам. Дедупим INLINE через DISTINCT ON
|
||||
-- (physflat-ключ, последний снапшот), scope протолкнут В
|
||||
-- CTE через complex_id ближних ЖК. НЕ через
|
||||
-- v_objective_lots_latest: view материализует ВСЮ таблицу
|
||||
-- (qual/join не проходят ниже DISTINCT ON) → seq-scan+sort
|
||||
-- 1.76M (~6.4 s на request-path analyze_parcel). Inline:
|
||||
-- geo-index по complexes → Nested Loop bitmap
|
||||
-- objective_lots_complex_idx по ~186 ЖК → ~120 ms
|
||||
-- (прод-EXPLAIN deep-review #1964). Дедуп до price-фильтра:
|
||||
-- цена объективна по физлоту (последний снапшот).
|
||||
WITH nearby_cx AS (
|
||||
SELECT c.id
|
||||
FROM complexes c
|
||||
WHERE c.latitude IS NOT NULL
|
||||
AND c.longitude IS NOT NULL
|
||||
AND ST_DWithin(
|
||||
ST_SetSRID(
|
||||
ST_MakePoint(c.longitude, c.latitude),
|
||||
4326
|
||||
)::geography,
|
||||
ST_SetSRID(
|
||||
ST_MakePoint(
|
||||
CAST(:lon AS float),
|
||||
CAST(:lat AS float)
|
||||
), 4326
|
||||
)::geography,
|
||||
CAST(:radius_m AS float)
|
||||
)
|
||||
),
|
||||
latest AS (
|
||||
SELECT DISTINCT ON (
|
||||
ol.project_name, ol.corpus_name, ol.section,
|
||||
ol.floor, ol.lot_number
|
||||
)
|
||||
ol.price_per_m2_rub,
|
||||
ol.complex_id
|
||||
FROM objective_lots ol
|
||||
WHERE ol.complex_id IN (SELECT id FROM nearby_cx)
|
||||
ORDER BY ol.project_name, ol.corpus_name, ol.section,
|
||||
ol.floor, ol.lot_number,
|
||||
ol.snapshot_date DESC, ol.id DESC
|
||||
)
|
||||
SELECT
|
||||
percentile_cont(0.5) WITHIN GROUP (ORDER BY ol.price_per_m2_rub)
|
||||
AS median,
|
||||
count(*) AS n,
|
||||
count(DISTINCT c.id) AS n_complexes
|
||||
FROM complexes c
|
||||
JOIN v_objective_lots_latest ol
|
||||
ON ol.complex_id = c.id
|
||||
AND ol.price_per_m2_rub IS NOT NULL
|
||||
WHERE c.latitude IS NOT NULL
|
||||
AND c.longitude IS NOT NULL
|
||||
AND ST_DWithin(
|
||||
ST_SetSRID(
|
||||
ST_MakePoint(c.longitude, c.latitude), 4326
|
||||
)::geography,
|
||||
ST_SetSRID(
|
||||
ST_MakePoint(
|
||||
CAST(:lon AS float), CAST(:lat AS float)
|
||||
), 4326
|
||||
)::geography,
|
||||
CAST(:radius_m AS float)
|
||||
)
|
||||
percentile_cont(0.5) WITHIN GROUP (
|
||||
ORDER BY price_per_m2_rub
|
||||
) AS median,
|
||||
count(*) AS n,
|
||||
count(DISTINCT complex_id) AS n_complexes
|
||||
FROM latest
|
||||
WHERE price_per_m2_rub IS NOT NULL
|
||||
"""),
|
||||
{
|
||||
"lon": centroid_lon,
|
||||
|
|
|
|||
|
|
@ -174,12 +174,36 @@ class TestObjPricingPushdown:
|
|||
"совпадал с выводимыми конкурентами"
|
||||
)
|
||||
|
||||
def test_aggregation_unchanged(self) -> None:
|
||||
"""Агрегатные выражения (AVG/COUNT FILTER) не тронуты — менялся только scope."""
|
||||
def test_aggregation_per_domrf_obj_id(self) -> None:
|
||||
"""Агрегаты (AVG/COUNT FILTER) считаются per domrf_obj_id (логика цен #1332).
|
||||
|
||||
#1964: источник агрегатов сменился с сырого objective_lots (alias ol) на
|
||||
physflat-дедуп CTE obj_lots_latest (alias oll) — см. test_obj_pricing_*_physflat
|
||||
ниже. Сами агрегатные выражения и группировка per-obj_id неизменны.
|
||||
"""
|
||||
sql = self._competitor_sql()
|
||||
# Логика цен/fallback (#1332) неизменна: те же агрегаты per domrf_obj_id.
|
||||
assert "ROUND(AVG(ol.price_per_m2_rub)::numeric, 0) AS avg_price_per_m2_rub" in sql
|
||||
assert "COUNT(*) FILTER (WHERE ol.price_per_m2_rub IS NOT NULL )" in sql.replace(
|
||||
assert "ROUND(AVG(oll.price_per_m2_rub)::numeric, 0) AS avg_price_per_m2_rub" in sql
|
||||
assert "lots_with_price" in sql
|
||||
assert "COUNT(*) FILTER (WHERE oll.is_sold) AS units_sold" in sql
|
||||
assert "COUNT(*) FILTER (WHERE NOT oll.is_sold) AS units_available" in sql
|
||||
assert "GROUP BY np.domrf_obj_id" in sql
|
||||
|
||||
def test_obj_pricing_dedups_physflat_inline(self) -> None:
|
||||
"""#1964: obj_pricing агрегирует physflat-дедуп набор (DISTINCT ON), НЕ сырой.
|
||||
|
||||
objective_lots раздут ~2.91× (мульти objective_lot_id на физлот через
|
||||
пере-листинги) → units_sold/units_available карточки конкурента были завышены.
|
||||
Дедупим INLINE (DISTINCT ON physflat-ключ, последний снапшот) со scope по
|
||||
project_name ближних ЖК. НЕ через v_objective_lots_latest: view материализует
|
||||
ВСЮ таблицу 1.76M (join не проходит ниже DISTINCT ON → seq-scan+sort ~5.7 s на
|
||||
request-path analyze_parcel; inline scoped → ~0.4 s, прод-EXPLAIN #1964).
|
||||
"""
|
||||
sql = self._competitor_sql()
|
||||
assert "obj_lots_latest AS (" in sql, "должен быть physflat-дедуп CTE (#1964)"
|
||||
assert "DISTINCT ON ( ol.project_name, ol.corpus_name, ol.section," in sql.replace(
|
||||
"\n", " "
|
||||
) or "lots_with_price" in sql
|
||||
assert "GROUP BY cm.domrf_obj_id" in sql
|
||||
), "obj_lots_latest должен дедупить по physflat-ключу"
|
||||
assert "snapshot_date DESC, ol.id DESC" in sql, "берём последний снапшот физлота"
|
||||
assert (
|
||||
"v_objective_lots_latest" not in sql
|
||||
), "request-path: view материализует всю таблицу — нужен inline DISTINCT ON (#1964)"
|
||||
|
|
|
|||
|
|
@ -48,6 +48,24 @@ class _FakeSession:
|
|||
_WKT = "POINT (60.6 56.83)"
|
||||
|
||||
|
||||
def test_objective_median_sql_dedups_inline_not_via_view() -> None:
|
||||
"""#1964: _OBJECTIVE_MEDIAN_SQL дедупит INLINE (DISTINCT ON physflat-ключ), НЕ
|
||||
через v_objective_lots_latest.
|
||||
|
||||
Это REQUEST-PATH запрос (внутри analyze/концепт-генерации). View не несёт
|
||||
district-фильтр внутри → qual не проталкивается ниже DISTINCT ON → view сортирует
|
||||
ВСЮ таблицу 1.76M (~5.8 s, прод-EXPLAIN deep-review #1964). Inline DISTINCT ON с
|
||||
district В CTE → bitmap по району + sort (~1.7 s). Регресс к
|
||||
`FROM v_objective_lots_latest` (whole-table materialize) → fail.
|
||||
"""
|
||||
sql = str(concepts._OBJECTIVE_MEDIAN_SQL)
|
||||
assert "DISTINCT ON (" in sql, "должен дедупить физлоты inline"
|
||||
assert "snapshot_date DESC" in sql, "берём последний снапшот физлота"
|
||||
assert (
|
||||
"v_objective_lots_latest" not in sql
|
||||
), "request-path: view материализует всю таблицу — нужен inline DISTINCT ON (#1964)"
|
||||
|
||||
|
||||
def test_objective_median_selected_when_sample_large_enough() -> None:
|
||||
# Район найден + Objective n>=10 → выбирается медиана Objective.
|
||||
db = _FakeSession(
|
||||
|
|
|
|||
|
|
@ -16,16 +16,30 @@
|
|||
-- которым нужна fan-out-защита по маппингу (competitors._SOLD_COUNT_SQL), могут
|
||||
-- использовать COUNT(DISTINCT objective_lot_id) поверх view.
|
||||
--
|
||||
-- Ключ/направления DISTINCT ON совпадают с partial-индексом
|
||||
-- ⚠ ПЛАН/ИНДЕКС (deep-review #1964, прод-EXPLAIN 2026-06-28): partial-индекс
|
||||
-- objective_lots_physflat_latest_idx (миграция 173, WHERE premise_kind='квартира')
|
||||
-- → для квартир-scoped потребителей планировщик берёт Index Scan + Unique без
|
||||
-- полной сортировки. Все current-state потребители #1964 — premise_kind='квартира'.
|
||||
-- Новый индекс НЕ добавляем (173 покрывает).
|
||||
-- НЕ обслуживает этот view. Внутри view НЕТ premise-фильтра, а внешний
|
||||
-- WHERE premise_kind/district потребителя НЕ проталкивается НИЖЕ DISTINCT ON →
|
||||
-- индекс-173 не применим. View материализует ВСЮ таблицу: Parallel Seq Scan +
|
||||
-- external Sort 1.76M строк → ~5.8 s (count по одному району).
|
||||
-- ПОЛНЫЙ индекс (project,corpus,section,floor,lot_number,snapshot_date DESC,id DESC)
|
||||
-- НЕ помогает и НЕ добавлен: DISTINCT ON отдаёт ol.* (51 кол., width≈945) →
|
||||
-- index-only-unique невозможен; 605k heap-проб через 142 MB индекс ДОРОЖЕ
|
||||
-- seq-scan+sort (планировщик его игнорирует: forced index-path всё равно ~3.9 s).
|
||||
-- 142 MB write-amp ради нуля на request-path + замедление bulk-INSERT objective-
|
||||
-- scrape — не берём. Подтверждает исходное решение #1964 «новый индекс НЕ добавляем».
|
||||
--
|
||||
-- Следствие: view применяем ТОЛЬКО к BATCH/широким консьюмерам, где полная
|
||||
-- материализация амортизирована или кэширована (supply_layers L1 группирует ВСЕ
|
||||
-- районы; competitors._SOLD_COUNT_SQL — по mapping; special_indices; admin; landing).
|
||||
-- REQUEST-PATH консьюмеры внутри analyze_parcel с фильтром по ОДНОМУ району
|
||||
-- (concepts._OBJECTIVE_MEDIAN_SQL, parcels district-price/geo-median) view НЕ
|
||||
-- используют — у них inline DISTINCT ON с district, протолкнутым В CTE (bitmap по
|
||||
-- району ~240k строк + sort ~19 MB → ~1.7 s vs 5.8 s через view). Дедуп платится в
|
||||
-- любом случае (старый 303 ms был быстрым потому что считал РАЗДУТЫЕ сырые строки).
|
||||
--
|
||||
-- #1959 уже устранил инфляцию ВНУТРИ compute_market_metrics (inline DISTINCT ON в
|
||||
-- _STOCK_SQL/_SALES_WINDOW_SQL) и его 9 forecast/scoring-вызовов — НЕ дублируем.
|
||||
-- Этот view — для ОСТАЛЬНЫХ current-state консьюмеров (supply_layers L1, конкурент-
|
||||
-- карточки, district-цены, sample_size-гейты).
|
||||
--
|
||||
-- НЕ трогаем (time-series, корректны): sales_series._SOURCE_B_SQL,
|
||||
-- objective_lots_history, mv_sales_tracker_*.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue