Агент проектирует схемы, оптимизирует запросы и чинит медленные SQL — база данных перестаёт быть узким местом
path VARCHAR: "electronics/phones/iphone"WHERE path LIKE 'electronics/%' — один запрос, один индексpath ltree + GiST-индекс — нативная поддержка в PostgreSQL-- Основной индекс для каталога (покрывает 90% запросов):
CREATE INDEX idx_listings_catalog
ON listings(city_id, category_path text_pattern_ops, created_at DESC)
WHERE status = 'active';
-- Для фильтра по цене — отдельный индекс:
CREATE INDEX idx_listings_price
ON listings(city_id, price)
WHERE status = 'active';
WHERE status = 'active' — partial index, ~70% строк (архивные не индексируются)city_id первый — равенство, высокая селективностьtext_pattern_ops — для LIKE 'prefix%' по category_pathcreated_at DESC — сортировка без дополнительного SortUNSIGNED INT → INTEGER (PG не имеет unsigned)TINYINT(1) → BOOLEANDATETIME → TIMESTAMPTZ (с часовым поясом!)ENUM → VARCHAR + CHECK или CREATE TYPEAUTO_INCREMENT → GENERATED ALWAYS AS IDENTITYGROUP BY — PG строгий: все неагрегатные колонки в GROUP BYconfig/database.php → pgsqlpgloader — автоматическая миграция данных из MySQL в PGSHOW CREATE TABLE для основных таблиц? Начну с конвертации.-- 1. Добавить tsvector-колонку (денормализация для скорости):
ALTER TABLE products ADD COLUMN search_vector tsvector;
-- 2. Заполнить (русский + английский):
UPDATE products SET search_vector =
setweight(to_tsvector('russian', coalesce(name, '')), 'A') ||
setweight(to_tsvector('russian', coalesce(description, '')), 'B');
-- 3. GIN-индекс:
CREATE INDEX idx_products_search ON products USING GIN(search_vector);
-- 4. Триггер для автообновления:
CREATE FUNCTION products_search_trigger() RETURNS trigger AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('russian', coalesce(NEW.name, '')), 'A') ||
setweight(to_tsvector('russian', coalesce(NEW.description, '')), 'B');
RETURN NEW;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER tsvector_update BEFORE INSERT OR UPDATE ON products
FOR EACH ROW EXECUTE FUNCTION products_search_trigger();
-- 5. Запрос:
SELECT *, ts_rank(search_vector, q) AS rank
FROM products, plainto_tsquery('russian', 'кроссовки nike') q
WHERE search_vector @@ q
ORDER BY rank DESC
LIMIT 20;
setweight(A/B/C/D) — name важнее descriptionts_rank — релевантность, не бинарный matchWITH cohorts AS (
SELECT
id AS user_id,
date_trunc('month', created_at)::date AS cohort_month
FROM users
),
activity AS (
SELECT DISTINCT
e.user_id,
date_trunc('month', e.created_at)::date AS activity_month
FROM events e
WHERE e.event_type = 'page_view' -- или любое целевое действие
),
cohort_size AS (
SELECT cohort_month, count(*) AS users_count
FROM cohorts
GROUP BY cohort_month
),
retention AS (
SELECT
c.cohort_month,
EXTRACT(YEAR FROM age(a.activity_month, c.cohort_month)) * 12 +
EXTRACT(MONTH FROM age(a.activity_month, c.cohort_month)) AS month_number,
count(DISTINCT c.user_id) AS active_users
FROM cohorts c
JOIN activity a ON a.user_id = c.user_id
AND a.activity_month >= c.cohort_month
GROUP BY c.cohort_month, month_number
)
SELECT
r.cohort_month,
cs.users_count AS cohort_size,
r.month_number,
r.active_users,
round(100.0 * r.active_users / cs.users_count, 1) AS retention_pct
FROM retention r
JOIN cohort_size cs ON cs.cohort_month = r.cohort_month
WHERE r.month_number <= 12 -- первые 12 месяцев
ORDER BY r.cohort_month, r.month_number;
cohort_month — строки, month_number — колонки, retention_pct — значения. Получится классическая retention-таблица.7 дней без ограничений. Настройка за 3 минуты — бот уже ждёт в Telegram.
Попробовать за 490 ₽ — 7 дней ← ИИ агент для программиста