# Максимальная сумма бронирования

    => EXPLAIN SELECT total_amount FROM bookings ORDER BY total_amount DESC LIMIT 1;

                                      QUERY PLAN
    ------------------------------------------------------------------------------
     Limit  (cost=45113.65..45113.65 rows=1 width=6)
       ->  Sort  (cost=45113.65..50391.43 rows=2111110 width=6)
             Sort Key: total_amount DESC
             ->  Seq Scan on bookings  (cost=0.00..34558.10 rows=2111110 width=6)
    (4 rows)

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

# Индекс

Создаем индекс.

    => CREATE INDEX ON bookings(total_amount);

    CREATE INDEX

Повторим запрос:

    => EXPLAIN SELECT total_amount FROM bookings ORDER BY total_amount DESC LIMIT 1;

                                                           QUERY PLAN
    -------------------------------------------------------------------------------------------------------------------------
     Limit  (cost=0.43..0.48 rows=1 width=6)
       ->  Index Only Scan Backward using bookings_total_amount_idx on bookings  (cost=0.43..108623.04 rows=2111110 width=6)
    (2 rows)

Теперь планировщик выбрал только индексное сканирование.

Сформулируем запрос по-другому:

    => EXPLAIN SELECT max(total_amount) FROM bookings;

                                                               QUERY PLAN
    ---------------------------------------------------------------------------------------------------------------------------------
     Result  (cost=0.48..0.49 rows=1 width=32)
       InitPlan 1 (returns $0)
         ->  Limit  (cost=0.43..0.48 rows=1 width=6)
               ->  Index Only Scan Backward using bookings_total_amount_idx on bookings  (cost=0.43..113900.82 rows=2111110 width=6)
                     Index Cond: (total_amount IS NOT NULL)
    (5 rows)

Видно, что и в этом случае планировщик выбрал индексное сканирование (добавив условие NOT NULL, поскольку функция max не должна учитывать неопределенные значения).

Новый узел плана, который здесь появляется - InitPlan. Он соответствует подзапросу, который выполняется один раз. То есть, фактически, планировщик переформулировал запрос следующим образом:

    => EXPLAIN SELECT (
      SELECT total_amount FROM bookings
      WHERE total_amount IS NOT NULL
      ORDER BY total_amount DESC LIMIT 1
    );

                                                               QUERY PLAN
    ---------------------------------------------------------------------------------------------------------------------------------
     Result  (cost=0.48..0.49 rows=1 width=16)
       InitPlan 1 (returns $0)
         ->  Limit  (cost=0.43..0.48 rows=1 width=6)
               ->  Index Only Scan Backward using bookings_total_amount_idx on bookings  (cost=0.43..113900.82 rows=2111110 width=6)
                     Index Cond: (total_amount IS NOT NULL)
    (5 rows)

Если бы подзапрос выполнялся несколько раз, вместо InitPlan в плане появился бы узел SubPlan.

# Порядок сортировки при создании индекса

Порядок важен для многоколоночных индексов. Создадим индекс на таблице рейсов по аэропортам вылета и прилета:

    => CREATE INDEX dep_arr on flights(departure_airport, arrival_airport);

    CREATE INDEX

Индекс может использоваться для такого запроса:

    => EXPLAIN SELECT * FROM flights ORDER BY departure_airport, arrival_airport;

                                       QUERY PLAN
    ---------------------------------------------------------------------------------
     Index Scan using dep_arr on flights  (cost=0.42..15199.59 rows=214867 width=63)
    (1 row)

И для такого:

    => EXPLAIN SELECT * FROM flights ORDER BY departure_airport DESC, arrival_airport DESC;

                                            QUERY PLAN
    ------------------------------------------------------------------------------------------
     Index Scan Backward using dep_arr on flights  (cost=0.42..15199.59 rows=214867 width=63)
    (1 row)

Но не для такого (сортировка в разных направлениях):

    => EXPLAIN SELECT * FROM flights ORDER BY departure_airport, arrival_airport DESC;

                                  QUERY PLAN
    ----------------------------------------------------------------------
     Sort  (cost=31675.96..32213.12 rows=214867 width=63)
       Sort Key: departure_airport, arrival_airport DESC
       ->  Seq Scan on flights  (cost=0.00..4564.67 rows=214867 width=63)
    (3 rows)

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

    => CREATE INDEX dep_asc_arr_desc ON flights(departure_airport, arrival_airport DESC);

    CREATE INDEX

    => EXPLAIN SELECT * FROM flights ORDER BY departure_airport, arrival_airport DESC;

                                            QUERY PLAN
    ------------------------------------------------------------------------------------------
     Index Scan using dep_asc_arr_desc on flights  (cost=0.42..15199.59 rows=214867 width=63)
    (1 row)

А также для запроса с обратным порядком сортировки:

    => EXPLAIN SELECT * FROM flights ORDER BY departure_airport DESC, arrival_airport;

                                                QUERY PLAN
    ---------------------------------------------------------------------------------------------------
     Index Scan Backward using dep_asc_arr_desc on flights  (cost=0.42..15199.59 rows=214867 width=63)
    (1 row)