# Занятие 12: Статистика / Расширенная статистика

> Исходная страница в нативном Markdown: Статистика / Расширенная статистика

---

Индекс LLMS: [llms.txt](/ru/llms.txt)

---

<!-- Generated by scripts/generate_skeleton.py. -->

> [!NOTE]
> Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета.
> **HTML source:** [qpt_12_stats_extended.html](https://edu.postgrespro.ru/16/qpt-16/qpt_12_stats_extended.html)
> **Archive:** [QPT-handouts-16.zip](https://edu.postgrespro.ru/16/QPT-handouts-16.zip)
> **Страниц PDF:** 19

## Page 1: Статистика / Расширенная статистика {#source-page-001}

### Авторские права

© Postgres Professional, 2019–2024 Авторы: Егор Рогов, Павел Лузанов, Павел Толмачев, Илья Баштанов Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
### Использование материалов курса

Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
### Обратная связь

Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
### Отказ от ответственности

Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.

## Page 2: Темы {#source-page-002}

- Функциональная зависимость
- Наиболее частые комбинации значений
- Число уникальных комбинаций значений
- Статистика по выражениям

## Page 3: Расширенная статистика {#source-page-003}

**Включает**
  - многовариантную статистику (по нескольким столбцам)
  - статистику по выражениям
- Объект базы данных, создается вручную
  - хранится в pg\_statistic\_ext и pg\_statistic\_ext\_data
  - представления pg\_stats\_ext и pg\_stats\_ext\_exprs
- После создания статистика собирается автоматически

### Комментарии преподавателя

Базовой статистики, которая собирается автоматически, может не хватать для точных оценок кардинальности и селективности.

PostgreSQL позволяет администратору вручную определить, какая дополнительная — расширенная — статистика требуется. Можно собрать статистику, охватывающую не один столбец, а сразу несколько (многовариантная статистика), или статистику по произвольному выражению.

Заметьте, что базовая статистика собирается для таблиц и их столбцов, но не для индексов (за исключением индексов по выражениям). Поэтому и индекс, построенный по нескольким столбцам, не приводит сам по себе к появлению многовариантной статистики.

Расширенная статистика создается командой CREATE STATISTICS. После того, как объект создан, соответствующая статистика будет собираться автоматически в фоновом режиме или командой ANALYZE.

<https://postgrespro.ru/docs/postgresql/16/planner-stats#PLANNER-STATS-E> XTENDED

Собранная информация хранится в таблицах pg\_statistic\_ext и pg\_statistic\_ext\_data; доступная пользователю статистика отображается в представлениях pg\_stats\_ext и pg\_stats\_ext\_exprs.

## Page 4: Зависимые столбцы {#source-page-004}

![Source slide page 4: Зависимые столбцы](images/page-004.png){#source-figure-004 caption="Source slide page 4: Зависимые столбцы"}

**Функциональная зависимость (dependencies)**
  - значение одного столбца определяет значение другого столбца
  - статистика улучшает оценку селективности условий

### Комментарии преподавателя

Существует несколько видов многовариантной статистики (то есть статистики по нескольким столбцам таблицы), которые можно указать при создании объекта расширенной статистики.

Функциональная зависимость между столбцами показывает, насколько данные в одном столбце определяются значением другого столбца.

В примере на слайде почтовый индекс однозначно определяет город, поэтому селективность условия city = 'Самара' and index = '443000' определяется селективностью предиката index = '443000'. Такие предикаты, селективность которых нельзя рассчитывать отдельно друг от друга, называются коррелированными.

<https://postgrespro.ru/docs/postgresql/16/planner-stats#PLANNER-STATS-E> XTENDED-FUNCTIONAL-DEPS

## Page 5: Функциональные зависимости {#source-page-005}

Рассмотрим запрос с двумя условиями:
```sql
=> SELECT count(*)
FROM flights
WHERE flight_no = 'PG0007' AND departure_airport = 'VKO';
```
```text
 count
-------
   396
(1 row)
```
Оценка оказывается сильно заниженной:
```sql
=> EXPLAIN
SELECT * FROM flights
WHERE flight_no = 'PG0007' AND departure_airport = 'VKO';
```
```text
                                                QUERY PLAN
------------------------------------------------------------------------------------------
-----------------
 Bitmap Heap Scan on flights  (cost=12.18..1277.67 rows=27 width=63)
   Recheck Cond: (flight_no = 'PG0007'::bpchar)
   Filter: (departure_airport = 'VKO'::bpchar)
   ->  Bitmap Index Scan on flights_flight_no_scheduled_departure_key  (cost=0.00..12.18
rows=501 width=0)
         Index Cond: (flight_no = 'PG0007'::bpchar)
(5 rows)
```
Причина в том, что планировщик полагается на независимость предикатов и считает итоговую селективность как произведение селективностей предикатов. Это хорошо видно в приведенном плане: оценка в узле Bitmap Index Scan (условие на flight\_no) верная, а после фильтрации в узле Bitmap Heap Scan (условие на departure\_airport) — заниженная.
Однако мы понимаем, что номер рейса однозначно определяет аэропорт отправления: фактически, второе условие избыточно (конечно, считая, что аэропорт указан правильно).
Это можно объяснить планировщику с помощью статистики по функциональной зависимости:
```sql
=> CREATE STATISTICS (dependencies)
ON flight_no, departure_airport FROM flights;
```
```sql
CREATE STATISTICS
```
```text
=> ANALYZE flights;
```
```text
ANALYZE
```
Собранная статистика хранится в следующем виде:
```sql
=> SELECT dependencies
FROM pg_stats_ext
WHERE statistics_name = 'flights_flight_no_departure_airport_stat';
```
```text
               dependencies
------------------------------------------
 {"2 => 5": 1.000000, "5 => 2": 0.011233}
(1 row)
```
Сначала идут порядковые номера атрибутов, а после двоеточия — коэффициент зависимости.
```sql
=> EXPLAIN
SELECT * FROM flights
WHERE flight_no = 'PG0007' AND departure_airport = 'VKO';
```

## Page 6: Page 6 {#source-page-006}

```text
                                                QUERY PLAN
------------------------------------------------------------------------------------------
-----------------
 Bitmap Heap Scan on flights  (cost=10.55..814.31 rows=275 width=63)
   Recheck Cond: (flight_no = 'PG0007'::bpchar)
   Filter: (departure_airport = 'VKO'::bpchar)
   ->  Bitmap Index Scan on flights_flight_no_scheduled_departure_key  (cost=0.00..10.48
rows=275 width=0)
         Index Cond: (flight_no = 'PG0007'::bpchar)
(5 rows)
```
Теперь оценка улучшилась.
Команда \\d показывает объекты расширенной статистики для конкретной таблицы:
```sql
=> \d flights
```
```sql
                                              Table "bookings.flights"
       Column        |           Type           | Collation | Nullable |
Default
---------------------+--------------------------+-----------+----------+------------------
--------------------------
 flight_id           | integer                  |           | not null |
nextval('flights_flight_id_seq'::regclass)
 flight_no           | character(6)             |           | not null |
 scheduled_departure | timestamp with time zone |           | not null |
 scheduled_arrival   | timestamp with time zone |           | not null |
 departure_airport   | character(3)             |           | not null |
 arrival_airport     | character(3)             |           | not null |
 status              | character varying(20)    |           | not null |
 aircraft_code       | character(3)             |           | not null |
 actual_departure    | timestamp with time zone |           |          |
 actual_arrival      | timestamp with time zone |           |          |
Indexes:
    "flights_pkey" PRIMARY KEY, btree (flight_id)
    "flights_flight_no_scheduled_departure_key" UNIQUE CONSTRAINT, btree (flight_no,
scheduled_departure)
Check constraints:
    "flights_check" CHECK (scheduled_arrival > scheduled_departure)
    "flights_check1" CHECK (actual_arrival IS NULL OR actual_departure IS NOT NULL AND
actual_arrival IS NOT NULL AND actual_arrival > actual_departure)
    "flights_status_check" CHECK (status::text = ANY (ARRAY['On Time'::character
varying::text, 'Delayed'::character varying::text, 'Departed'::character varying::text,
'Arrived'::character varying::text, 'Scheduled'::character varying::text,
'Cancelled'::character varying::text]))
Foreign-key constraints:
    "flights_aircraft_code_fkey" FOREIGN KEY (aircraft_code) REFERENCES
aircrafts_data(aircraft_code)
    "flights_arrival_airport_fkey" FOREIGN KEY (arrival_airport) REFERENCES
airports_data(airport_code)
    "flights_departure_airport_fkey" FOREIGN KEY (departure_airport) REFERENCES
airports_data(airport_code)
Referenced by:
    TABLE "ticket_flights" CONSTRAINT "ticket_flights_flight_id_fkey" FOREIGN KEY
(flight_id) REFERENCES flights(flight_id)
Statistics objects:
    "bookings.flights_flight_no_departure_airport_stat" (dependencies) ON flight_no,
departure_airport FROM flights
```
В разделе Statistics objects показаны имена, столбцы и нестандартные целевые значения объектов статистики.

## Page 7: Частые комбинации {#source-page-007}

![Source slide page 7: Частые комбинации](images/page-007.png){#source-figure-007 caption="Source slide page 7: Частые комбинации"}

**Наиболее частые комбинации значений (mcv)**
  - как pg\_stats.most\_common\_vals/freqs, но для нескольких столбцов
  - статистика улучшает оценку селективности условий

### Комментарии преподавателя

Список наиболее частых комбинаций значений позволяет сохранить несколько комбинаций значений и их частоту.

На слайде показаны возможные пары город — река. Очевидно, что предикаты со столбцами city и river коррелированы, но, в отличие от предыдущего примера, ни один из этих столбцов не определяет другой (между городами и реками отношение «многие ко многим»). В этом случае статистика по функциональным зависимостям не поможет улучшить оценки.

<https://postgrespro.ru/docs/postgresql/16/planner-stats#PLANNER-STATS-E> XTENDED-MCV-LISTS

## Page 8: Частые комбинации {#source-page-008}

Столбцы могут быть коррелированы, но не всегда между ними есть прямая функциональная зависимость. Выполним такой запрос:
```sql
=> EXPLAIN (analyze, timing off, summary off)
SELECT * FROM flights
WHERE departure_airport = 'LED' AND aircraft_code = '321';
```
```text
                                              QUERY PLAN
------------------------------------------------------------------------------------------
-------------
 Gather  (cost=1000.00..5594.79 rows=749 width=63) (actual rows=5148 loops=1)
   Workers Planned: 1
   Workers Launched: 1
   ->  Parallel Seq Scan on flights  (cost=0.00..4519.89 rows=441 width=63) (actual
rows=2574 loops=2)
         Filter: ((departure_airport = 'LED'::bpchar) AND (aircraft_code = '321'::bpchar))
         Rows Removed by Filter: 104860
(6 rows)
```
Планировщик ошибается в несколько раз. Учет функциональной зависимости недостаточно исправит ситуацию:
```sql
=> CREATE STATISTICS (dependencies)
ON departure_airport, aircraft_code FROM flights;
```
```sql
CREATE STATISTICS
```
```text
=> ANALYZE flights;
```
```text
ANALYZE
```
```sql
=> EXPLAIN
SELECT * FROM flights
WHERE departure_airport = 'LED' AND aircraft_code = '321';
```
```text
                                        QUERY PLAN
------------------------------------------------------------------------------------------
-
 Gather  (cost=1000.00..5696.09 rows=1762 width=63)
   Workers Planned: 1
   ->  Parallel Seq Scan on flights  (cost=0.00..4519.89 rows=1036 width=63)
         Filter: ((departure_airport = 'LED'::bpchar) AND (aircraft_code = '321'::bpchar))
(4 rows)
```
В этом случае можно добавить расширенную статистику по частым комбинациям значений нескольких столбцов:
```sql
=> DROP STATISTICS flights_departure_airport_aircraft_code_stat;
```
```sql
DROP STATISTICS
```
```sql
=> CREATE STATISTICS (mcv)
ON departure_airport, aircraft_code FROM flights;
```
```sql
CREATE STATISTICS
```
```text
=> ANALYZE flights;
```
```text
ANALYZE
```
```sql
=> EXPLAIN
SELECT * FROM flights
WHERE departure_airport = 'LED' AND aircraft_code = '321';
```
```text
                                     QUERY PLAN
-------------------------------------------------------------------------------------
 Seq Scan on flights  (cost=0.00..5847.00 rows=5164 width=63)
   Filter: ((departure_airport = 'LED'::bpchar) AND (aircraft_code = '321'::bpchar))
(2 rows)
```
Теперь оценка улучшилась.
Количество собираемых наиболее частых значений определяется параметром default\_statistics\_target. Его можно задать

## Page 9: Page 9 {#source-page-009}

для конкретной статистики:
```sql
=> ALTER STATISTICS flights_departure_airport_aircraft_code_stat
SET STATISTICS 300;
```
```sql
ALTER STATISTICS
```
```text
=> ANALYZE flights;
```
```text
ANALYZE
```
```sql
=> EXPLAIN (analyze, timing off, summary off)
SELECT * FROM flights
WHERE departure_airport = 'LED' AND aircraft_code = '321';
```
```text
                                       QUERY PLAN
-----------------------------------------------------------------------------------------
 Seq Scan on flights  (cost=0.00..5847.00 rows=5169 width=63) (actual rows=5148 loops=1)
   Filter: ((departure_airport = 'LED'::bpchar) AND (aircraft_code = '321'::bpchar))
   Rows Removed by Filter: 209719
(3 rows)
```
Оценка кардинальности еще немного улучшилась.
Однако это не привело к дальнейшему улучшению плана, поэтому увеличение ориентира статистики в данном случае вряд ли оправдано.
Статистику по наиболее частым комбинациям можно посмотреть так:
```sql
=> SELECT values, frequency
FROM pg_statistic_ext
  JOIN pg_statistic_ext_data ON oid = stxoid,
  pg_mcv_list_items(stxdmcv) m
WHERE stxname = 'flights_departure_airport_aircraft_code_stat'
LIMIT 10;
```
```text
  values   |      frequency
-----------+----------------------
 {SVO,SU9} |  0.03048888888888889
 {DME,SU9} |  0.03017777777777778
 {LED,321} | 0.024055555555555556
 {DME,CR2} |               0.0238
 {VKO,CR2} |                0.022
 {BZK,SU9} |               0.0187
 {SVO,CR2} | 0.018266666666666667
 {KJA,CN1} | 0.015111111111111112
 {VKO,SU9} | 0.013333333333333334
 {DME,321} | 0.012977777777777777
(10 rows)
```

## Page 10: Уникальные комбинации {#source-page-010}

![Source slide page 10: Уникальные комбинации](images/page-010.png){#source-figure-010 caption="Source slide page 10: Уникальные комбинации"}

**Число уникальных комбинаций значений (ndistinct)**
  - как pg\_stats.ndistinct, но для нескольких столбцов
  - статистика улучшает оценку кардинальности для группировки

### Комментарии преподавателя

Число уникальных комбинаций значений позволяет улучшить оценку кардинальности при группировке по нескольким столбцам.

В примере на слайде число возможных комбинаций всех полей, очевидно, нельзя определить, просто перемножив количество уникальных значений для каждого столбца по отдельности.

<https://postgrespro.ru/docs/postgresql/16/planner-stats#PLANNER-STATS-E> XTENDED-N-DISTINCT-COUNTS

## Page 11: Уникальные комбинации {#source-page-011}

Другая ситуация, в которой планировщик ошибается с оценкой, связана с группировкой. Количество пар аэропортов, связанных прямыми рейсами, ограничено:
```sql
=> SELECT count(*) FROM (
  SELECT DISTINCT departure_airport, arrival_airport FROM flights
) t;
```
```text
 count
-------
   618
(1 row)
```
Но планировщик не знает об этом:
```sql
=> EXPLAIN
SELECT DISTINCT departure_airport, arrival_airport FROM flights;
```
```text
                             QUERY PLAN
---------------------------------------------------------------------
 HashAggregate  (cost=5847.01..5955.16 rows=10816 width=8)
   Group Key: departure_airport, arrival_airport
   ->  Seq Scan on flights  (cost=0.00..4772.67 rows=214867 width=8)
(3 rows)
```
Расширенная статистика позволяет исправить и эту оценку (если не указать вид статистики, в создаваемый объект будут включены все поддерживаемые виды):
```sql
=> CREATE STATISTICS
ON departure_airport, arrival_airport FROM flights;
```
```sql
CREATE STATISTICS
```
```text
=> ANALYZE flights;
```
```text
ANALYZE
```
```sql
=> EXPLAIN
SELECT DISTINCT departure_airport, arrival_airport FROM flights;
```
```text
                                           QUERY PLAN
------------------------------------------------------------------------------------------
------
 Unique  (cost=5616.51..5621.15 rows=618 width=8)
   ->  Sort  (cost=5616.51..5618.06 rows=618 width=8)
         Sort Key: departure_airport, arrival_airport
         ->  Gather  (cost=5519.88..5587.86 rows=618 width=8)
               Workers Planned: 1
               ->  HashAggregate  (cost=4519.88..4526.06 rows=618 width=8)
                     Group Key: departure_airport, arrival_airport
                     ->  Parallel Seq Scan on flights  (cost=0.00..3887.92 rows=126392
width=8)
(8 rows)
```
Статистику по уникальным комбинациям можно увидеть так:
```sql
=> SELECT n_distinct
FROM pg_stats_ext
WHERE statistics_name = 'flights_departure_airport_arrival_airport_stat';
```
```text
  n_distinct
---------------
 {"5, 6": 618}
(1 row)
```
Посмотреть список всех объектов расширенной статистики можно командой \\dX:
```text
=> \x \dX \x
```

## Page 12: Page 12 {#source-page-012}

```sql
Expanded display is on.
List of extended statistics
-[ RECORD 1 ]+------------------------------------------------
Schema       | bookings
Name         | flights_departure_airport_aircraft_code_stat
Definition   | departure_airport, aircraft_code FROM flights
Ndistinct    |
Dependencies |
MCV          | defined
-[ RECORD 2 ]+------------------------------------------------
Schema       | bookings
Name         | flights_departure_airport_arrival_airport_stat
Definition   | departure_airport, arrival_airport FROM flights
Ndistinct    | defined
Dependencies | defined
MCV          | defined
-[ RECORD 3 ]+------------------------------------------------
Schema       | bookings
Name         | flights_flight_no_departure_airport_stat
Definition   | flight_no, departure_airport FROM flights
Ndistinct    |
Dependencies | defined
MCV          |
```
```text
Expanded display is off.
```
Отображается наличие статистики по типам (Ndistinct, Dependencies, MCV), а значения нужно смотреть в таблице pg\_statistic\_ext\_data.

## Page 13: Статистика по выражению {#source-page-013}

![Source slide page 13: Статистика по выражению](images/page-013.png){#source-figure-013 caption="Source slide page 13: Статистика по выражению"}

**Расширенная статистика по выражению**
  - как если бы в таблице был определен генерируемый столбец
  - статистика улучшает оценку селективности условий с выражениями

### Комментарии преподавателя

Расширенная статистика по выражению позволяет собрать всю базовую статистику, которая была бы собрана, если бы в таблице был определен столбец, вычисляемый по этому выражению.

Если в предикате слева или справа от оператора стоит не имя столбца, а выражение, планировщик использует фиксированную оценку селективности. С помощью статистики по выражению это можно исправить.

Заметим, что выражения также можно использовать вместо имен столбцов в многовариантной статистике любого типа.

## Page 14: Статистика по выражению {#source-page-014}

Как мы видели в теме «Базовая статистика», если в условии используются выражение, планировщик, не имея информации о селективности, использует константу и часто ошибается:
```sql
=> SELECT count(*) FROM flights
WHERE extract(month FROM scheduled_departure AT TIME ZONE 'Europe/Moscow') = 1;
```
```text
 count
-------
 16831
(1 row)
```
```sql
=> EXPLAIN
SELECT * FROM flights
WHERE extract(month FROM scheduled_departure AT TIME ZONE 'Europe/Moscow') = 1;
```
```sql
                                                  QUERY PLAN
------------------------------------------------------------------------------------------
---------------------
 Gather  (cost=1000.00..5943.27 rows=1074 width=63)
   Workers Planned: 1
   ->  Parallel Seq Scan on flights  (cost=0.00..4835.87 rows=632 width=63)
         Filter: (EXTRACT(month FROM (scheduled_departure AT TIME ZONE
'Europe/Moscow'::text)) = '1'::numeric)
(4 rows)
```
В теме «Базовая статистика» мы исправляли ситуацию, построив индекс по выражению, но это возможно не во всех случаях. К тому же индексы требуют ресурсов для хранения и постоянной синхронизации. Можно поступить иначе — добавить расширенную статистику по выражению:
```sql
=> CREATE STATISTICS
ON extract(month FROM scheduled_departure AT TIME ZONE 'Europe/Moscow')
FROM flights;
```
```sql
CREATE STATISTICS
```
```text
=> ANALYZE flights;
```
```text
ANALYZE
```
```sql
=> EXPLAIN
SELECT * FROM flights
WHERE extract(month FROM scheduled_departure AT TIME ZONE 'Europe/Moscow') = 1;
```
```sql
                                               QUERY PLAN
------------------------------------------------------------------------------------------
---------------
 Seq Scan on flights  (cost=0.00..6384.17 rows=16860 width=63)
   Filter: (EXTRACT(month FROM (scheduled_departure AT TIME ZONE 'Europe/Moscow'::text))
= '1'::numeric)
(2 rows)
```
Оценка стала корректной.
Расширенная статистика по выражениям хранится отдельно. Вот несколько столбцов:
```sql
=> SELECT statistics_name, expr, n_distinct, most_common_vals
FROM pg_stats_ext_exprs \gx
```
```sql
-[ RECORD 1
]----+-----------------------------------------------------------------------------
statistics_name  | flights_expr_stat
expr             | EXTRACT(month FROM (scheduled_departure AT TIME ZONE
'Europe/Moscow'::text))
n_distinct       | 12
most_common_vals | {8,9,10,5,1,7,3,12,4,6,11,2}
```

## Page 15: Итоги {#source-page-015}

**Расширенная статистика помогает в сложных случаях**
- Создается вручную, поддерживается автоматически
- Может увеличить затраты на анализ и планирование

## Page 16: Практика {#source-page-016}

1. Используя команды из демонстрации, создайте статистики типа dependencies, mcv и ndistinct для таблицы flights. Измерьте время выполнения команды ANALYZE. Удалите расширенные статистики, снова измерьте время выполнения команды ANALYZE и сравните с предыдущим результатом. 2. Напишите запрос, выбирающий все перелеты в бизнес-классе стоимостью более 100 тыс. рублей. Поможет ли расширенная статистика улучшить оценку кардинальности результата? Если да, то какой тип статистики лучше использовать?

### Комментарии преподавателя

1. Выполняйте измерение времени несколько раз и усредняйте результаты, чтобы сгладить неравномерности.

## Page 17: 1. Затраты на анализ {#source-page-017}

Создадим расширенные статистики аналогично тому, как это было сделано в демонстрации.
Статистика по функциональной зависимости:
```sql
=> CREATE STATISTICS flights_dep(dependencies)
ON flight_no, departure_airport FROM flights;
```
```sql
CREATE STATISTICS
```
Списки наиболее частых комбинаций значений:
```sql
=> CREATE STATISTICS flights_mcv(mcv)
ON departure_airport, aircraft_code FROM flights;
```
```sql
CREATE STATISTICS
```
Статистика по уникальным комбинациям значений:
```sql
=> CREATE STATISTICS flights_nd(ndistinct)
ON departure_airport, arrival_airport FROM flights;
```
```sql
CREATE STATISTICS
```
Измерим время анализа.
```text
=> \timing on
```
```text
Timing is on.
```
```text
=> ANALYZE flights;
```
```text
ANALYZE
Time: 875,119 ms
```
В первый раз анализ может занимать существенно больше времени, чем обычно.
```text
=> ANALYZE flights;
```
```text
ANALYZE
Time: 680,730 ms
```
```text
=> ANALYZE flights;
```
```text
ANALYZE
Time: 620,083 ms
```
```text
=> \timing off
```
```text
Timing is off.
```
Удалим созданные расширенные статистики:
```sql
=> DROP STATISTICS flights_dep;
```
```sql
DROP STATISTICS
```
```sql
=> DROP STATISTICS flights_mcv;
```
```sql
DROP STATISTICS
```
```sql
=> DROP STATISTICS flights_nd;
```
```sql
DROP STATISTICS
```
Повторно измеряем время:
```text
=> \timing on
```
```text
Timing is on.
```
```text
=> ANALYZE flights;
```
```text
ANALYZE
Time: 385,839 ms
```
```text
=> ANALYZE flights;
```
```text
ANALYZE
Time: 415,945 ms
```
```text
=> \timing off
```

## Page 18: 2. Применение расширенной статистики {#source-page-018}

```text
Timing is off.
```
Было создано всего три расширенные статистики, но время анализа до и после удаления заметно различается. С увеличением количества собираемых расширенных статистик будет расти и время на анализ, что может привести к увеличению нагрузки на сервер.
Поэтому использовать расширенную статистику нужно осмысленно и только в тех случаях, когда это необходимо.
Перелеты в бизнес-классе стоимостью свыше 100 тысяч ₽:
```sql
=> EXPLAIN (analyze, timing off, summary off)
SELECT *
FROM ticket_flights
WHERE fare_conditions = 'Business' and amount > 100_000;
```
```text
                                                    QUERY PLAN
------------------------------------------------------------------------------------------
------------------------
 Gather  (cost=1000.00..124557.57 rows=11764 width=32) (actual rows=111203 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Parallel Seq Scan on ticket_flights  (cost=0.00..122381.18 rows=4902 width=32)
(actual rows=37068 loops=3)
         Filter: ((amount > '100000'::numeric) AND ((fare_conditions)::text =
'Business'::text))
         Rows Removed by Filter: 2760216
(6 rows)
```
Оптимизатор ошибается на порядок. Причина в том, что стоимость билета и класс обслуживания коррелируют между собой, поэтому расширенная статистика должна помочь исправить оценку. Поскольку между столбцами нет прямой функциональной зависимости, добавим статистику по наиболее частым значениям:
```sql
=> CREATE STATISTICS (mcv) ON fare_conditions, amount FROM ticket_flights;
```
```sql
CREATE STATISTICS
```
```text
=> ANALYZE ticket_flights;
```
```text
ANALYZE
```
```sql
=> EXPLAIN (timing off, summary off)
SELECT *
FROM ticket_flights
WHERE fare_conditions = 'Business' and amount > 100_000;
```
```text
                                           QUERY PLAN
------------------------------------------------------------------------------------------
-------
 Gather  (cost=1000.00..126555.45 rows=31727 width=32)
   Workers Planned: 2
   ->  Parallel Seq Scan on ticket_flights  (cost=0.00..122382.75 rows=13220 width=32)
         Filter: ((amount > '100000'::numeric) AND ((fare_conditions)::text =
'Business'::text))
(4 rows)
```
Оценка немного улучшилась, но все еще сильно отличается от точного значения. Можно заметить, что доля строк, удовлетворяющих условию, немногим более 1%:
```sql
=> SELECT count(*) FILTER (WHERE fare_conditions = 'Business' and amount > 100_000)
  / count(*)::float
FROM ticket_flights;
```
```text
      ?column?
---------------------
 0.01325130614791586
(1 row)
```
Поскольку по умолчанию хранится 100 наиболее частых пар, среди них с большой вероятностью встретится не более 1–2 удовлетворяющих условию, и из-за этого оценка оптимизатора не будет адекватной. Попробуем повысить точность, увеличив объем статистики:
```sql
=> ALTER STATISTICS ticket_flights_fare_conditions_amount_stat SET STATISTICS 500;
```
```sql
ALTER STATISTICS
```

## Page 19: Page 19 {#source-page-019}

```text
=> ANALYZE ticket_flights;
```
```text
ANALYZE
```
```sql
=> EXPLAIN (timing off, summary off)
SELECT *
FROM ticket_flights
WHERE fare_conditions = 'Business' and amount > 100_000;
```
```text
                                           QUERY PLAN
------------------------------------------------------------------------------------------
-------
 Gather  (cost=1000.00..133923.68 rows=105416 width=32)
   Workers Planned: 2
   ->  Parallel Seq Scan on ticket_flights  (cost=0.00..122382.08 rows=43923 width=32)
         Filter: ((amount > '100000'::numeric) AND ((fare_conditions)::text =
'Business'::text))
(4 rows)
```
Оценка стала практически точной.
