--- /dev/null
+<?php
+
+use yii\db\Migration;
+
+/**
+ * ERP-237: Добавление отсутствующих индексов для устранения узких мест из pg_stat_statements.
+ *
+ * Анализ показал ~2108s суммарного времени на запросы без индексов.
+ * Индекс api_cron.request_id уже создан в m260210_120000.
+ *
+ * Оставшиеся 6 индексов:
+ * - export_import_table (entity, export_id, export_val): 2.3M запросов, 511s
+ * - marketplace_orders (guid): 247K запросов, 412s
+ * - marketplace_flowwow_emails (subject, from, date): 253K запросов, 409s
+ * - marketplace_order_items (order_id): 376K запросов, 307s
+ * - timetable_fact (admin_id, date, is_opening): 52K запросов, 298s
+ * - admin_payroll_days (admin_id, date): 27K запросов, 278s
+ */
+class m260319_150000_add_missing_indexes_for_slow_queries extends Migration
+{
+ public function safeUp(): void
+ {
+ // 1. export_import_table: 2.3M запросов, 511s
+ // WHERE entity=$1 AND export_id=$2 AND export_val=$3
+ if (!$this->indexExists('export_import_table', 'idx_export_import_table_entity_export_id_val')) {
+ $this->createIndex(
+ 'idx_export_import_table_entity_export_id_val',
+ 'export_import_table',
+ ['entity', 'export_id', 'export_val']
+ );
+ }
+
+ // 2. marketplace_orders (guid): 247K запросов, 412s
+ // WHERE guid=$1
+ if (!$this->indexExists('marketplace_orders', 'idx_marketplace_orders_guid')) {
+ $this->createIndex(
+ 'idx_marketplace_orders_guid',
+ 'marketplace_orders',
+ 'guid'
+ );
+ }
+
+ // 3. marketplace_flowwow_emails (subject, from, date): 253K, 409s
+ // SELECT EXISTS WHERE subject=$1 AND from=$2 AND date=$3
+ if (!$this->indexExists('marketplace_flowwow_emails', 'idx_marketplace_flowwow_emails_subject_from_date')) {
+ $this->createIndex(
+ 'idx_marketplace_flowwow_emails_subject_from_date',
+ 'marketplace_flowwow_emails',
+ ['subject', '"from"', 'date']
+ );
+ }
+
+ // 4. marketplace_order_items (order_id): 376K запросов, 307s
+ // SELECT * WHERE order_id=$1
+ if (!$this->indexExists('marketplace_order_items', 'idx_marketplace_order_items_order_id')) {
+ $this->createIndex(
+ 'idx_marketplace_order_items_order_id',
+ 'marketplace_order_items',
+ 'order_id'
+ );
+ }
+
+ // 5. timetable_fact (admin_id, date, is_opening): 52K запросов, 298s
+ // WHERE admin_id=$1 AND (date=$2 OR date=$3) AND is_opening=$4
+ if (!$this->indexExists('timetable_fact', 'idx_timetable_fact_admin_date_opening')) {
+ $this->createIndex(
+ 'idx_timetable_fact_admin_date_opening',
+ 'timetable_fact',
+ ['admin_id', 'date', 'is_opening']
+ );
+ }
+
+ // 6. admin_payroll_days (admin_id, date): 27K запросов, 278s
+ if (!$this->indexExists('admin_payroll_days', 'idx_admin_payroll_days_admin_id')) {
+ $this->createIndex(
+ 'idx_admin_payroll_days_admin_id',
+ 'admin_payroll_days',
+ ['admin_id', 'date']
+ );
+ }
+ }
+
+ public function safeDown(): void
+ {
+ $indexes = [
+ ['idx_export_import_table_entity_export_id_val', 'export_import_table'],
+ ['idx_marketplace_orders_guid', 'marketplace_orders'],
+ ['idx_marketplace_flowwow_emails_subject_from_date', 'marketplace_flowwow_emails'],
+ ['idx_marketplace_order_items_order_id', 'marketplace_order_items'],
+ ['idx_timetable_fact_admin_date_opening', 'timetable_fact'],
+ ['idx_admin_payroll_days_admin_id', 'admin_payroll_days'],
+ ];
+
+ foreach ($indexes as [$indexName, $table]) {
+ if ($this->indexExists($table, $indexName)) {
+ $this->dropIndex($indexName, $table);
+ }
+ }
+ }
+
+ private function indexExists(string $table, string $indexName): bool
+ {
+ return (bool) $this->db->createCommand(
+ "SELECT 1 FROM pg_indexes WHERE tablename = :table AND indexname = :index LIMIT 1",
+ [':table' => $table, ':index' => $indexName]
+ )->queryScalar();
+ }
+}