Содержание
    18.09.2020

    Одной из наиболее полезных функций (помимо типа данных JSON) в MySQL 8.0 оказались генерируемые поля. В генерируемом поле значение формируется автоматически, что позволяет в некоторых случаях упростить SQL-запросы, а в некоторых — избавиться от триггеров. Например, у вас есть таблица с перечнем товаров в счете, в которой есть поля с ценой (price) и количеством товара (quantity), а также поле с суммой (total), значение которого вычисляется в триггере. Поле с суммой занимает место на диске и требует использования триггера. С появлением MySQL 8.0 поле total можно сделать генерируемым. Оно не будет занимать место и всегда будет актуальным.

    Пример создания таблицы с генерируемым полем:

    CREATE TABLE invoice_item ( 
        `price` decimal(10,2) unsigned NOT NULL DEFAULT 0, 
        `quantity` decimal(10,4) unsigned NOT NULL DEFAULT 0, 
        `total` decimal(10,2) unsigned GENERATED ALWAYS AS (ROUND(quantity * price, 2)) VIRTUAL 
    )
    

    Формула, которая указывается для генерируемых полей, может содержать математические операторы и функции MySQL. С учетом появления типа данных JSON, в генерируемые поля можно выносить значения параметров из данных JSON, по которым впоследствии можно проводить поиск. Например, у вас есть таблица, в которой хранятся транзакции от различных платежных систем. Каждая система при получении платежа возвращает свой набор данных, который редко используется, но нужен для поиска по номеру банковской карты:

    CREATE TABLE transactions (
        `response` JSON,
        `card` VARCHAR(20) GENERATED ALWAYS AS (response->>"$.card_mask") STORED,
        INDEX card_idx (card)
    )

    Для генерируемого поля указывать NOT NULL нужно после определения GENERATED ALWAYS, а не перед ним.

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

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

    Пример добавления генерируемого поля:

    ALTER TABLE table_name
    ADD COLUMN `card` DECIMAL(10,2) GENERATED ALWAYS AS (ROUND(price * quantity, 2)) VIRTUAL;

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

    SELECT column_name, generation_expression, extra
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE
        table_name='$table_name' and
        table_schema='$db_name'
    ORDER BY ordinal_position

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

    CREATE TABLE `transactions_backup` LIKE `transactions`;
    INSERT INTO `transactions_backup`
    SELECT * FROM `transactions`;

    Вернет ошибку «The value specified for generated column 'response' in table 'transactions' is not allowed».

    Для таких таблиц приходится прописывать список полей, которые не являются генерируемыми. Помогает это сделать запрос к базе INFORMATION_SCHEMA:

    SELECT GROUP_CONCAT('`', column_name, '`' ORDER BY ordinal_position SEPARATOR ', ')
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE
        table_name='$table_name' and
        table_schema='$db_name' and
        generation_expression=''