Работа с SQL и Apache Calcite#

Описание SQL-движка на основе Apache Calcite#

Внимание

Новый SQL-движок находится в статусе beta.

Начиная с версии 4.2130 DataGrid поставляется с новым SQL-движком, который основан на фреймворке Apache Calcite.

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

У текущего SQL-движка, основанного на H2, есть набор фундаментальных ограничений, которые связаны с исполнением SQL-запросов в распределенной среде. Новый движок позволяет обойти эти ограничения. Он использует инструменты Apache Calcite для планирования и обработки запросов и новый процесс для их исполнения.

Фазы выполнения запроса через Calcite:

  1. Обработка:

    • Вход — строка самого запроса.

    • Выход — синтаксическое дерево (AST — Abstract Syntax Tree).

  2. Валидация (семантический анализ):

    • Вход — синтаксическое дерево (AST) и метаданные. На данном этапе синтаксическое дерево проверяется на соответствие метаданным.

    • Выход — AST с привязкой к конкретным метаданным.

  3. Построение логического плана запроса на основе AST:

    • Вход — AST.

    • Выход — логический план запроса (дерево реляционных операторов).

  4. Оптимизация:

    • Вход — логический план запроса и статистика.

    • Выход — физический план запроса (дерево реляционных операторов с привязкой к конкретному способу выполнения запроса).

  5. Выполнение:

    • Вход — физический план запроса.

    • Выход — результат выполнения (курсор).

Способы настройки SQL-движка на основе Apache Calcite#

Режим Standalone#

При запуске кластера в режиме Standalone перед запуском скрипта ignite.sh или ignite.bat переместите подкаталоги optional/ignite-calcite и optional/ignite-slf4j в каталог libs. В этом случае контент папки, в которой находится модуль, добавится в classpath.

Конфигурация Maven#

Если в проекте используются библиотеки DataGrid, добавьте в зависимости модуль ignite-calcite. В случае использования Maven добавьте следующую зависимость (замените параметр ${ignite.version} на необходимую версию DataGrid):

XML#
<dependency>
    <groupId>org.apache.ignite</groupId>
    <artifactId>ignite-calcite</artifactId>
    <version>${ignite.version}</version>
</dependency>

Конфигурация SQL-движков#

Для включения SQL-движка на основе Apache Calcite укажите CalciteQueryEngineConfiguration в разделе SqlConfiguration.QueryEnginesConfiguration.

Пример конфигурации двух SQL-движков (H2 и Calcite), где движок по умолчанию — Calcite:

<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    <property name="sqlConfiguration">
        <bean class="org.apache.ignite.configuration.SqlConfiguration">
            <property name="queryEnginesConfiguration">
                <list>
                    <bean class="org.apache.ignite.indexing.IndexingQueryEngineConfiguration">
                        <property name="default" value="false"/>
                    </bean>
                    <bean class="org.apache.ignite.calcite.CalciteQueryEngineConfiguration">
                        <property name="default" value="true"/>
                    </bean>
                </list>
            </property>
        </bean>
    </property>
    ...
</bean>
IgniteConfiguration cfg = new IgniteConfiguration().setSqlConfiguration(
    new SqlConfiguration().setQueryEnginesConfiguration(
        new IndexingQueryEngineConfiguration(),
        new CalciteQueryEngineConfiguration().setDefault(true)
    )
);

Исполнение запросов с указанием SQL-движка#

Обычно все запросы направляются в SQL-движок, который настроен по умолчанию. Если в queryEnginesConfiguration настроено более одного движка, можно указать один из них для исполнения отдельных запросов или всего подключения к базе данных.

JDBC#

Чтобы выбрать SQL-движок для JDBC-соединения, используйте параметр queryEngine.

Пример

jdbc:ignite:thin://xxx.x.x.x:10800?queryEngine=calcite

где queryEngine=calcite — используемый движок.

ODBC#

SQL-движок для ODBC-соединения можно настроить с помощью свойства QUERY_ENGINE.

Пример

[IGNITE_CALCITE]
DRIVER={Apache Ignite};
SERVER=xxx.x.x.x;
PORT=10800;
SCHEMA=PUBLIC;
QUERY_ENGINE=CALCITE

Подсказка QUERY_ENGINE#

Используйте подсказку (hint) QUERY_ENGINE, чтобы выбрать определенный движок для отдельных запросов:

SQL#
SELECT /*+ QUERY_ENGINE('calcite') */ fld FROM table;

Плагин calcite-oracle-dialect-plugin#

Плагин calcite-oracle-dialect-plugin добавляет в SQL-движок, который основан на Apache Calcite, некоторые операторы из диалекта Oracle.

Конфигурация#

Способы конфигурации:

  1. Добавьте новый плагин в конфигурацию серверного узла <bean class="com.sbt.ignite.calcite.CalciteOracleDialectPluginProvider"/>:

            <property name="pluginProviders">
                <list>
                    <bean class="com.sbt.ignite.calcite.CalciteOracleDialectPluginProvider"/>
                </list>
            </property>
    
  2. Переместите библиотеку ise-calcite-oracle-dialect-plugin из поддиректории libs/optionals в директорию libs.

Дополните конфигурацию IgniteConfiguration:

IgniteConfiguration cfg = ...

cfg.setPluginProviders(new CalciteOracleDialectPluginProvider());

Пример успешного запуска плагина в лог-файле серверного узла

YYYY-MM-DD 10:43:12.223 [INFO ][main][org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor]   ^-- CALCITE_ORACLE_DIALECT_PROVIDER 1.0
YYYY-MM-DD 10:43:12.223 [INFO ][main][org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor]   ^-- null
YYYY-MM-DD 10:43:12.223 [INFO ][main][org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor]
YYYY-MM-DD 10:43:12.224 [INFO ][main][org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor]   ^-- Check Parameters 1.0.0-SNAPSHOT
YYYY-MM-DD 10:43:12.224 [INFO ][main][org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor]   ^-- SberTech
YYYY-MM-DD 10:43:12.224 [INFO ][main][org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor]

Операторы#

Плагин добавляет в SQL-движок операторы:

  • Арифметика между датами и числами (DATE + NUMBER | NUMBER + DATE | DATE - NUMBER | TIMESTAMP + NUMBER | NUMBER + TIMESTAMP | TIMESTAMP - NUMBER). Для данного оператора числовой аргумент означает количество дней (в том числе с плавающей точкой).

  • SUBSTR(STRING, NUMERIC, NUMERIC) / SUBSTR(STRING, NUMERIC) — аналог SUBSTRING с теми же параметрами.

  • TRUNC(d TIMESTAMP) — аналог FLOOR(d TO DAY).

  • LTRIM(s STRING, c STRING) — аналог TRIM(LEADING c FROM s).

  • RTRIM(s STRING, c STRING) — аналог TRIM(TRAILING c FROM s).

  • NVL2(s STRING, a1 ANY, a2 ANY) — аналог CASE s IS NOT NULL THEN a1 ELSE a2 END.

  • TO_NUMBER(STRING) — перевод строки в число.

  • TO_CHAR(NUMBER, STRING) — перевод числа в строку с пользовательским форматом.

  • INSTR(string STRING, seek STRING[, from INTEGER[, occurrence INTEGER]]) — аналог POSITION(seek, string, from, occurrence).

  • REPLACE(a STRING, b STRING) — аналог REPLACE(a, b, ‘’).

  • REGEXP_INSTR(STRING, STRING[, INTEGER[, INTEGER]]) — поиск шаблона в строке по регулярному выражению.

  • LPAD(STRING, NUMERIC[, STRING])/RPAD(STRING, NUMERIC[, STRING]) — дополнение строки слева или справа до заданного размера.

Пользовательские SQL-функции#

Пользовательские SQL-функции — публичные статические методы, которые помечены аннотацией @QuerySqlFunction. SQL-движок позволяет добавлять пользовательские SQL-функции на Java в перечень SQL-функций, которые определены спецификацией ANSI-99.

Класс, в котором находится пользовательская SQL-функция, нужно зарегистрировать в конфигурации кеша (CacheConfiguration) с помощью метода setSqlFunctionClasses(...). При запуске кеша с указанной конфигурацией появится возможность вызова пользовательской функции внутри SQL-запросов.

Пользовательская SQL-функция может быть реализована в виде табличной функции (User-Defined Table Functions, UDTF). Результат табличной функции представлен в виде набора строк (таблицы), который доступен другим SQL-операторам. UDTF позволяет пользователям определять собственные табличные функции, которые могут возвращать наборы строк и использоваться в SQL-запросах как обычные таблицы.

Пример использования

SQL#
SELECT * FROM TABLE(my_custom_function(param1, param2))

где my_custom_function — пользовательская табличная функция, которая принимает параметры param1 и param2 и возвращает результат в виде таблицы.

Пользовательская SQL-функция в виде табличной функции представлена как публичный метод с аннотацией @QuerySqlTableFunction. Метод должен возвращать объект типа Iterable, который состоит из набора строк. Каждая строка может быть представлена массивом объектов Object[] или коллекцией. Количество элементов в массиве или коллекции должно совпадать с количеством колонок, которые описаны в аннотации. Типы значений в строках должны совпадать с типами столбцов или допускать преобразование в соответствующие типы.

Аннотация должна описывать возвращаемые колонки и их типы данных.

Пример аннотации

Java#
@QuerySqlTableFunction(
    alias = "persons",
    columnNames = {"ID", "NAME", "SALARY"},
    columnTypes = {Integer.class, String.class, Double.class}
)
public static Iterable<Object[]> persons() {
    return List.of(
        new Object[] {1, "Name 1", 100d},
        new Object[] {2, "Name 2", 200d}
    );
}

Примечание

В настоящее время табличные функции доступны только совместно с Apache Calcite.

Примечание

Классы, которые зарегистрированы с помощью метода CacheConfiguration.setSqlFunctionClasses(...), необходимо включить в classpath всех узлов, где могут быть запущены эти пользовательские функции. В противном случае при попытке их использования сгенерируется исключение ClassNotFoundException.

Пользовательские метки SQL-запросов#

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

Пользовательские метки (Id) указываются с помощью свойства setQueryInitiatorId в объекте SqlFieldsQuery. Список активных и выполненных запросов можно посмотреть с помощью системных представлений SQL_QUERIES_HISTORY и SQL_QUERIES.

Планировщик задач для блокировки запросов в Apache Calcite#

Архитектура SQL-движка Apache Calcite в DataGrid требует, чтобы задачи по одному и тому же запросу не могли выполняться конкурентно. Выполнение SQL-запроса и относящихся к нему задач привязывается к конкретному потоку из пула. Для привязки используется хеш-код идентификатора запроса. Если пользователь добавил собственную SQL-функцию (User-Defined Function, UDF), которая также выполняет некоторый SQL-запрос, это может привести к коллизиям при выборе потока и к взаимоблокировке (deadlock). Поток блокируется задачей, которая синхронно ждет выполнения нового SQL-запроса, а он не может быть выполнен, так как новый SQL-запрос по идентификатору запроса назначен на тот же поток.

Для решения этой проблемы в DataGrid добавлен новый исполнитель задач (task executor) — QueryBlockingTaskExecutor. Он основан на общей очереди задач для всех потоков, но за счет дополнительной синхронизации позволяет обеспечить гарантии, которые требуются SQL-движку при отсутствии конкуренции между задачами одного запроса. Это позволяет избежать взаимоблокировок при выполнении SQL-запросов внутри пользовательской функции.

Важно

Свойство IGNITE_CALCITE_USE_QUERY_BLOCKING_TASK_EXECUTOR позволяет переключить стандартный способ выполнения задач запросов StripedQueryTaskExecutor в Apache Calcite на общую очередь — QueryBlockingTaskExecutor.

Свойство IGNITE_CALCITE_USE_QUERY_BLOCKING_TASK_EXECUTOR позволяет вызывать запросы внутри пользовательских SQL-функций, но уменьшает производительность, поэтому включается опционально (значение по умолчанию — false, свойство выключено).

Пример создания пользовательской SQL-функции и SQL-запроса внутри нее
Java#
import java.io.File;
import java.util.Collections;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.QueryEntity;
import org.apache.ignite.cache.query.SqlFieldsQuery;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.processors.query.QuerySqlFunction;

import java.util.Collections;
import java.util.List;

public class IgniteUdfExample {
    public static void main(String[] args) {
        // Включите механизм выполнения задач `QueryBlockingTaskExecutor`.
        System.setProperty("IGNITE_CALCITE_USE_QUERY_BLOCKING_TASK_EXECUTOR", "true");

        Ignite ignite = Ignition.start();

        IgniteUdfExample example = new IgniteUdfExample();
        example.testInnerSql();
        example.testUdfInSql();
    }

    // Создайте конфигурацию кеша.
    public CacheConfiguration<Integer, Employer> cacheConfiguration() {
        return new CacheConfiguration<Integer, Employer>()
            .setName("emp4")
            .setSqlFunctionClasses(UserDefinedSqlFunction.class)
            .setSqlSchema("PUBLIC")
            .setQueryEntities(Collections.singletonList(
                new QueryEntity(Integer.class, Employer.class).setTableName("emp4")
            ));
    }

    // Создайте кеш и заполните его данными.
    public void testInnerSql() {
        IgniteCache<Integer, Employer> emp4 = ignite.getOrCreateCache(this.cacheConfiguration());

        for (int i = 0; i < 100; i++) {
            emp4.put(i, new Employer("Name" + i, (double) i));
        }
    }

    public void testUdfInSql() {
        // Создайте кеш.
        IgniteCache<Integer, Employer> emp4 = ignite.getOrCreateCache(this.cacheConfiguration());

        // Вызовите SQL-запрос внутри пользовательской SQL-функции.
        SqlFieldsQuery sql = new SqlFieldsQuery("SELECT name, salary(?, key) FROM emp4 WHERE key = ?")
            .setArgs("igniteInstanceName", 1);

        List<List<?>> result = emp4.query(sql).getAll();

        for (List<?> row : result) {
            System.out.println("Name: " + row.get(0) + ", Salary: " + row.get(1));
        }
    }

    // Создайте пользовательскую SQL-функцию.
    public static class UserDefinedSqlFunction {
        @QuerySqlFunction
        public static double salary(String igniteInstanceName, int key) {
            return (double) Ignition.ignite(igniteInstanceName)
                .cache("emp4")
                .query(new SqlFieldsQuery("SELECT salary FROM emp4 WHERE _key = ?").setArgs(key))
                .getAll().get(0).get(0);
        }
    }

    // Создайте класс `Employer` для хранения информации о сотрудниках.
    public static class Employer {
        private String name;
        private double salary;

        public Employer(String name, double salary) {
            this.name = name;
            this.salary = salary;
        }

        public String getName() {
            return name;
        }

        public double getSalary() {
            return salary;
        }
    }
}

SQL с поддержкой транзакций и scan-запросы#

SQL с поддержкой транзакций и scan-запросы в настоящее время поддерживаются только для SQL-запросов на основе Apache Calcite и уровня изоляции READ_COMMITTED. Для обеспечения обратной совместимости поддержка транзакций по умолчанию отключена. Чтобы включить поддержку транзакций для SQL- и scan-запросов, установите свойство txAwareQueriesEnabled в конфигурации транзакций (TransactionConfiguration):

Пример конфигурации txAwareQueriesEnabled:

<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    <property name="transactionConfiguration">
        <bean class="org.apache.ignite.configuration.TransactionConfiguration">
            <property name="txAwareQueriesEnabled" value="true" />
            ...
        </bean>
    </property>
    ...
</bean>
IgniteConfiguration cfg = new IgniteConfiguration();
cfg.getTransactionConfiguration().setTxAwareQueriesEnabled(true);

API, которые поддерживают работу с транзакциями:

  • Key-Value API;

  • SQL-запросы, поступившие из SQL-движка на основе Apache Calcite;

  • scan-запросы (scan queries);

  • индексированные scan-запросы (index scan query).

Поддержка работы с транзакциями означает, что:

  1. ACID-свойства будут соблюдаться на уровне Key-Value API.

  2. Измененные или удаленные в рамках транзакции данные не видны другим параллельным транзакциям до момента коммита данной транзакции.

  3. Данные, которые изменили/вставили на предыдущих шагах в рамках одной транзакции, будут видны на последующих шагах в этой транзакции.

Поэтому при включении txAwareQueriesEnabled = true можно:

  • использовать операторы INSERT, UPDATE и DELETE с обычными транзакционными гарантиями;

  • комбинировать Key-Value-, SQL- и scan-запросы для последующей транзакционной обработки данных.

Примечание

В настоящее время операторы UPDATE и SELECT не содержат блокировок. Оператор SELECT ... FOR UPDATE не поддерживается. Это означает, что при одновременном изменении одного и того же ключа в нескольких транзакциях возможно появление lost-update anomaly (аномалии потерянного обновления).

Например, при выполнении запроса вида SET salary = salary + 50 WHERE id = 1 одновременно в нескольких потоках может возникнуть lost-update anomaly. При корректном выполнении запроса значение salary должно быть равно сумме исходной зарплаты, увеличенной на 50 единиц для каждого задействованного потока. Но из-за lost-update anomaly итоговый результат может быть неверным: сумма salary не увеличится на 50 единиц для каждого потока.

Пример использования#
try (Transaction tx = srv.transactions().txStart(PESSIMISTIC, READ_COMMITTED)) {
    cache.put(1, 2);

    List<List<?>> sqlData = cache.query(new SqlFieldsQuery("SELECT COUNT(*) FROM TBL"));

    assertEquals("Must see transaction related data", 1L, sqlData.get(0).get(0));
    assertEquals("Must see transaction related data", 1, scanData.size());

    tx.commit();
}

Проверка соответствия данных схеме#

DataGrid позволяет проверить, соответствуют ли вставленные в таблицу значения типам столбцов и ограничениям, которые заявлены в ее SQL-схеме. По умолчанию DataGrid выполняет такие проверки только для индексированных столбцов. Чтобы проверить все столбцы, включите проверку на стороне сервера на каждом узле перед его запуском:

SqlConfiguration sqlCfg = new SqlConfiguration()
    .setValidationEnabled(true);
IgniteConfiguration cfg = new IgniteConfiguration()
    .setSqlConfiguration(sqlCfg);
<property name="sqlConfiguration">
    <bean class="org.apache.ignite.configuration.SqlConfiguration">
        <property name="validationEnabled" value="true"/>
    </bean>
</property>

Когда опция SqlConfiguration#validationEnabled включена, DataGrid:

  1. Выполняет дополнительные проверки для каждого SQL-запроса типа INSERT, MERGE и UPDATE и для API-вызовов, которые изменяют управляемые с помощью SQL-запросов таблицы.

  2. Отклоняет данные, которые нарушают SQL-схему.

Для SQL-клиентов (JDBC, ODBC, REST) нарушения схемы приводят к генерации исключения IgniteSQLException, а для вызовов Key-Value API — CacheException (корнем этого исключения также является IgniteSQLException). В обоих случаях приложение может обработать ошибку, и некорректные данные не будут сохранены.

Операции SQL DML пытаются привести типы значений к указанным в таблице, поэтому несоответствия типов обычно появляются при записи данных с помощью кеш-API или бинарных объектов:

Java#
// CREATE TABLE Person (id INT PRIMARY KEY, age INT);
IgniteCache<Integer, BinaryObject> cache = ignite.cache("SQL_PUBLIC_PERSON").withKeepBinary();
BinaryObject invalidPerson = ignite.binary().builder("Person")
    .setField("id", 2)
    .setField("age", "forty-two") // Строка (String) вместо столбца типа int.
    .build();
cache.put(2, invalidPerson); // Исключение `CacheException`, внутри которого при включенной проверке находится исключение `IgniteSQLException`.

С настройками по умолчанию (при отключенной для неиндексированных столбцов проверке соответствия) команда put из примера выше будет успешно выполнена, хотя хранимый BinaryObject не соответствует SQL-схеме. При включенной проверке DataGrid отклонит обновление и защитит таблицу от появления искаженных данных.

Используйте опцию проверки соответствия данных схеме SqlConfiguration#validationEnabled, когда требуются более надежные гарантии, что динамические или предоставленные пользователем данные не нарушат определение таблицы. Если затраты на дополнительные проверки перевешивают риск некорректных данных, отключите проверку.

Справочник по SQL#

DDL#

DDL-команды (Data Definition Language, язык описания данных) совместимы со старым движком на основе H2. Подробнее об этом написано в официальной документации Apache Ignite.

DML#

Новый SQL-движок в основном наследует синтаксис DML (Data Manipulation Language, язык манипулирования данными) от фреймворка Apache Calcite.

В большинстве случаев синтаксис команд совместим со старым SQL-движком, но есть некоторые отличия между DML-диалектами в движках на основе H2 и Calcite. Например, изменился синтаксис команды MERGE. Подробнее об этом написано в SQL-справочнике Apache Calcite.

Поддерживаемые функции#

SQL-движок на основе Calcite на данный момент поддерживает пользовательские функции и операторы, которые описаны ниже.

Агрегатные функции:

Имя

Синтаксис

Описание

COUNT

COUNT([ALL | DISTINCT] value) или COUNT(*)

Возвращает количество входных строк или ненулевых (non-null) значений

SUM

SUM([ALL | DISTINCT] numeric)

Возвращает сумму числовых входных значений

AVG

AVG([ALL | DISTINCT] numeric)

Возвращает среднее значение числовых входных значений

MIN

MIN([ALL | DISTINCT] value)

Возвращает минимальное входное значение

MAX

MAX([ALL | DISTINCT] value)

Возвращает максимальное входное значение

ANY_VALUE

ANY_VALUE([ALL | DISTINCT] value)

Возвращает одно произвольное входное значение

LISTAGG

LISTAGG(value[, separator]) WITHIN GROUP (ORDER BY sortExpr)

Объединяет значения из группы в заданном порядке

GROUP_CONCAT

GROUP_CONCAT(value[, separator] [ORDER BY sortExpr])

Объединяет значения из группы, опционально используя разделитель и сортировку

STRING_AGG

STRING_AGG(value, separator [ORDER BY sortExpr])

Объединяет строковые значения из группы с помощью разделителя

ARRAY_AGG

ARRAY_AGG(value [ORDER BY sortExpr])

Собирает входные значения в массив

ARRAY_CONCAT_AGG

ARRAY_CONCAT_AGG(arrayValue [ORDER BY sortExpr])

Объединяет массивы из входных строк в один массив

EVERY

EVERY(condition)

Возвращает значение TRUE, если каждое входное условие возвращает значение TRUE

SOME

SOME(condition)

Возвращает значение TRUE, если хотя бы одно входное условие возвращает значение TRUE

BIT_AND

BIT_AND(integer)

Агрегирует целые числа с помощью побитового AND

BIT_OR

BIT_OR(integer)

Агрегирует целые числа с помощью побитового OR

BIT_XOR

BIT_XOR(integer)

Агрегирует целые числа с помощью побитового XOR

Конструкция FILTER в агрегатных функциях

aggregateFunction(…) FILTER (WHERE condition)

Применяет агрегатную функцию только к тем входным строкам, которые удовлетворяют условию

Строковые функции и предикаты:

Имя

Синтаксис

Описание

UPPER

UPPER(string)

Преобразует строку в верхний регистр

LOWER

LOWER(string)

Преобразует строку в нижний регистр

INITCAP

INITCAP(string)

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

TO_BASE64

TO_BASE64(string)

Кодирует строку в формате Base64

FROM_BASE64

FROM_BASE64(string)

Декодирует строку из формата Base64

MD5

MD5(string)

Возвращает MD5-хеш строки в виде шестнадцатеричной строки

SHA1

SHA1(string)

Возвращает SHA-1-хеш строки в виде шестнадцатеричной строки

SUBSTRING

SUBSTRING(string FROM start [FOR length])

Возвращает подстроку, начиная с указанной позиции

LEFT

LEFT(string, length)

Возвращает указанное количество символов с левого края строки

RIGHT

RIGHT(string, length)

Возвращает указанное количество символов с правого края строки

REPLACE

REPLACE(string, search[, replacement])

Заменяет все вхождения искомой строки на указанную строку

TRANSLATE

TRANSLATE(string, fromString, toString)

Заменяет символы из одного набора соответствующими символами из другого набора

CHR

CHR(integer)

Возвращает символ, который соответствует указанному коду (code point)

CHAR_LENGTH

CHAR_LENGTH(string)

Возвращает количество символов в строке

CHARACTER_LENGTH

CHARACTER_LENGTH(string)

Эквивалентно CHAR_LENGTH

LENGTH

LENGTH(string)

Псевдоним для функции CHAR_LENGTH(string)

||

string || string

Объединяет две строки

CONCAT

CONCAT(string, string) или CONCAT(string[, string]…)

Объединяет строки

OVERLAY

OVERLAY(string1 PLACING string2 FROM start [FOR length])

Заменяет часть строки другой строкой

POSITION

POSITION(substring IN string [FROM start])

Возвращает позицию первого вхождения подстроки

ASCII

ASCII(string)

Возвращает код первого символа строки

REPEAT

REPEAT(string, count)

Возвращает строку, которая повторяется указанное количество раз

SPACE

SPACE(count)

Возвращает строку, которая состоит из указанного количества пробелов

STRCMP

STRCMP(string1, string2)

Сравнивает две строки и возвращает результат сравнения в виде целого числа

SOUNDEX

SOUNDEX(string)

Возвращает фонетическое представление строки

DIFFERENCE

DIFFERENCE(string1, string2)

Возвращает оценку сходства значений SOUNDEX двух строк

REVERSE

REVERSE(string)

Возвращает строку с символами в обратном порядке

TRIM

TRIM([{BOTH | LEADING | TRAILING} chars FROM] string)

Удаляет символы из начала, конца или с обеих сторон строки

LTRIM

LTRIM(string)

Удаляет пробелы в начале строки

RTRIM

RTRIM(string)

Удаляет пробелы в конце строки

LIKE

string LIKE pattern [ESCAPE escapeChar]

Проверяет, соответствует ли строка SQL-шаблону LIKE

SIMILAR TO

string SIMILAR TO pattern [ESCAPE escapeChar]

Проверяет, соответствует ли строка шаблону регулярного выражения SQL

Функции и операторы регулярных выражений:

Имя

Синтаксис

Описание

~

string ~ pattern

Проверяет, соответствует ли строка регулярному выражению POSIX с учетом регистра

~*

string ~* pattern

Проверяет, соответствует ли строка регулярному выражению POSIX без учета регистра

!~

string !~ pattern

Проверяет, что строка не соответствует регулярному выражению POSIX с учетом регистра

!~*

string !~* pattern

Проверяет, что строка не соответствует регулярному выражению POSIX без учета регистра

REGEXP_REPLACE

REGEXP_REPLACE(string, regexp, replacement[, position[, occurrence[, matchType]]])

Заменяет подстроки, которые соответствуют регулярному выражению

REGEXP_SUBSTR

REGEXP_SUBSTR(string, regexp[, position[, occurrence]])

Возвращает подстроку, которая соответствует регулярному выражению

Числовые и математические функции:

Имя

Синтаксис

Описание

MOD

MOD(numeric1, numeric2) or numeric1 % numeric2

Возвращает остаток от деления

BITAND

BITAND(integer1, integer2)

Возвращает результат побитовой операции AND двух целых чисел

BITOR

BITOR(integer1, integer2)

Возвращает результат побитовой операции OR двух целых чисел

BITXOR

BITXOR(integer1, integer2)

Возвращает результат побитовой операции XOR двух целых чисел

EXP

EXP(numeric)

Возвращает число Эйлера, возведенное в указанную степень

POWER

POWER(numeric1, numeric2)

Возвращает число, возведенное в степень другого числа

LN

LN(numeric)

Возвращает натуральный логарифм

LOG10

LOG10(numeric)

Возвращает десятичный логарифм

ABS

ABS(numeric)

Возвращает абсолютное значение (модуль числа)

RAND

RAND([seed])

Возвращает случайное значение типа double

RAND_INTEGER

RAND_INTEGER([seed,] bound)

Возвращает случайное целое число от 0 (включительно) до bound (исключая)

ACOS

ACOS(numeric)

Возвращает арккосинус

ACOSH

ACOSH(numeric)

Возвращает обратный гиперболический косинус

ASIN

ASIN(numeric)

Возвращает арксинус

ASINH

ASINH(numeric)

Возвращает обратный гиперболический синус

ATAN

ATAN(numeric)

Возвращает арктангенс

ATANH

ATANH(numeric)

Возвращает обратный гиперболический тангенс

ATAN2

ATAN2(numeric1, numeric2)

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

SQRT

SQRT(numeric)

Возвращает квадратный корень

CBRT

CBRT(numeric)

Возвращает кубический корень

COS

COS(numeric)

Возвращает косинус

COSH

COSH(numeric)

Возвращает гиперболический косинус

COT

COT(numeric)

Возвращает котангенс

COTH

COTH(numeric)

Возвращает гиперболический котангенс

DEGREES

DEGREES(numeric)

Преобразует радианы в градусы

RADIANS

RADIANS(numeric)

Преобразует градусы в радианы

ROUND

ROUND(numeric[, scale])

Округляет число до указанной точности (scale)

SIGN

SIGN(numeric)

Возвращает знак числового значения

SIN

SIN(numeric)

Возвращает синус

SINH

SINH(numeric)

Возвращает гиперболический синус

TAN

TAN(numeric)

Возвращает тангенс

TANH

TANH(numeric)

Возвращает гиперболический тангенс

SEC

SEC(numeric)

Возвращает секанс

SECH

SECH(numeric)

Возвращает гиперболический секанс

CSC

CSC(numeric)

Возвращает косеканс

CSCH

CSCH(numeric)

Возвращает гиперболический косеканс

TRUNCATE

TRUNCATE(numeric[, scale])

Отсекает дробную часть числа до указанной точности (scale)

PI

PI

Возвращает приближенное значение числа пи

Функции даты и времени:

Имя

Синтаксис

Описание

EXTRACT

EXTRACT(timeUnit FROM datetime)

Возвращает указанное поле из значения даты, времени или временной метки (timestamp)

FLOOR

FLOOR(datetime TO timeUnit)

Округляет значение даты, времени или временной метки в меньшую сторону до указанной единицы

CEIL

CEIL(datetime TO timeUnit)

Округляет значение даты, времени или временной метки в большую сторону до указанной единицы

TIMESTAMPADD

TIMESTAMPADD(timeUnit, interval, datetime)

Добавляет интервал к временной метке

TIMESTAMPDIFF

TIMESTAMPDIFF(timeUnit, datetime1, datetime2)

Возвращает количество границ указанных единиц времени между двумя значениями datetime

LAST_DAY

LAST_DAY(date)

Возвращает дату последнего дня месяца

DAYNAME

DAYNAME(datetime)

Возвращает название дня недели

MONTHNAME

MONTHNAME(datetime)

Возвращает название месяца

DAYOFMONTH

DAYOFMONTH(datetime)

Возвращает день месяца

DAYOFWEEK

DAYOFWEEK(datetime)

Возвращает день недели

DAYOFYEAR

DAYOFYEAR(datetime)

Возвращает день года

YEAR

YEAR(datetime)

Возвращает год

QUARTER

QUARTER(datetime)

Возвращает квартал года

MONTH

MONTH(datetime)

Возвращает номер месяца

WEEK

WEEK(datetime)

Возвращает номер недели

HOUR

HOUR(datetime)

Возвращает час

MINUTE

MINUTE(datetime)

Возвращает минуту

SECOND

SECOND(datetime)

Возвращает секунду

TIMESTAMP_SECONDS

TIMESTAMP_SECONDS(integer)

Преобразует количество секунд с 00:00:00 01.01.1970 во временную метку

TIMESTAMP_MILLIS

TIMESTAMP_MILLIS(integer)

Преобразует количество миллисекунд с 00:00:00 01.01.1970 во временную метку

TIMESTAMP_MICROS

TIMESTAMP_MICROS(integer)

Преобразует количество микросекунд с 00:00:00 01.01.1970 во временную метку

UNIX_SECONDS

UNIX_SECONDS(timestamp)

Возвращает количество секунд с 00:00:00 01.01.1970

UNIX_MILLIS

UNIX_MILLIS(timestamp)

Возвращает количество миллисекунд с 00:00:00 01.01.1970

UNIX_MICROS

UNIX_MICROS(timestamp)

Возвращает количество микросекунд с 00:00:00 01.01.1970

UNIX_DATE

UNIX_DATE(date)

Возвращает количество дней с 01.01.1970

DATE_FROM_UNIX_DATE

DATE_FROM_UNIX_DATE(integer)

Преобразует количество дней с 01.01.1970 в дату

DATE

DATE(value) или DATE(year, month, day)

Преобразует значение в дату или создает дату из отдельных полей

TIME

TIME(value) или TIME(hour, minute, second)

Преобразует значение в время или создает время из отдельных полей

DATETIME

DATETIME(value) или DATETIME(year, month, day, hour, minute, second)

Преобразует значение во временную метку или создает ее из полей даты и времени

CURRENT_TIME

CURRENT_TIME

Возвращает текущее время

CURRENT_TIMESTAMP

CURRENT_TIMESTAMP

Возвращает текущую временную метку

CURRENT_DATE

CURRENT_DATE

Возвращает текущую дату

LOCALTIME

LOCALTIME

Возвращает текущее местное время

LOCALTIMESTAMP

LOCALTIMESTAMP

Возвращает текущую местную временную метку

JDBC-функции для экранирования текущего времени

{fn CURDATE()}, {fn CURTIME()}, {fn NOW()}

JDBC-псевдонимы экранирования для текущих даты, времени и временной метки

TO_CHAR

TO_CHAR(datetime, format)

Форматирует значение даты, времени или временной метки

TO_DATE

TO_DATE(string, format)

Преобразует строку в дату

TO_TIMESTAMP

TO_TIMESTAMP(string, format)

Преобразует строку во временную метку

XML-функции:

Имя

Синтаксис

Описание

EXTRACTVALUE

EXTRACTVALUE(xml, xpath)

Возвращает текст, который был выбран с помощью выражения XPath

XMLTRANSFORM

XMLTRANSFORM(xml, xslt)

Преобразует XML с помощью таблицы стилей XSLT

EXTRACT

"EXTRACT"(xml, xpath)

Возвращает фрагмент XML, который был выбран с помощью выражения XPath. Используйте кавычки в имени, если нужно отличить эту функцию от EXTRACT для даты/времени

EXISTSNODE

EXISTSNODE(xml, xpath)

Возвращает значение 1, если выражение XPath выбирает хотя бы один XML-узел. В противном случае возвращает значение 0

JSON-функции и предикаты:

Имя

Синтаксис

Описание

FORMAT JSON

value FORMAT JSON

Помечает значение как входные данные в JSON-формате

JSON_VALUE

JSON_VALUE(jsonValue, path)

Извлекает скалярное значение SQL из JSON с помощью выражения JSON-пути

JSON_QUERY

JSON_QUERY(jsonValue, path)

Извлекает JSON-объект или массив из JSON с помощью выражения JSON-пути

JSON_TYPE

JSON_TYPE(jsonValue)

Возвращает тип значения JSON

JSON_EXISTS

JSON_EXISTS(jsonValue, path)

Возвращает значение TRUE, если JSON соответствует указанному выражению пути

JSON_DEPTH

JSON_DEPTH(jsonValue)

Возвращает глубину вложенности значения JSON

JSON_KEYS

JSON_KEYS(jsonValue[, path])

Возвращает ключи JSON-объекта

JSON_PRETTY

JSON_PRETTY(jsonValue)

Возвращает JSON с форматированием (в удобочитаемом виде)

JSON_LENGTH

JSON_LENGTH(jsonValue[, path])

Возвращает длину значения JSON

JSON_REMOVE

JSON_REMOVE(jsonValue, path[, path]…)

Удаляет данные, которые были выбраны выражениями путей, и возвращает обновленный JSON

JSON_STORAGE_SIZE

JSON_STORAGE_SIZE(jsonValue)

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

JSON_OBJECT

JSON_OBJECT(jsonKey : jsonValue[, jsonKey : jsonValue]…)

Создает JSON-объект из пар «ключ-значение»

JSON_ARRAY

JSON_ARRAY([jsonValue[, jsonValue]…])

Создает JSON-массив из значений

IS JSON

value IS [NOT] JSON [VALUE]

Проверяет, является ли значение JSON или нет

IS JSON OBJECT

value IS [NOT] JSON OBJECT

Проверяет, является ли значение JSON-объектом или нет

IS JSON ARRAY

value IS [NOT] JSON ARRAY

Проверяет, является ли значение JSON-массивом или нет

IS JSON SCALAR

value IS [NOT] JSON SCALAR

Проверяет, является ли значение скалярным значением JSON или нет

Функции и операторы коллекций:

Имя

Синтаксис

Описание

ARRAY

ARRAY[value[, value]…] или ARRAY(query)

Создает массив из значений или из результата запроса

MAP

MAP[key, value[, key, value]…] или MAP(query)

Создает карту (map) из пар «ключ-значение» или из результата запроса

ITEM

array[index] или map[key]

Возвращает элемент из массива или карты

CARDINALITY

CARDINALITY(collection)

Возвращает количество элементов в коллекции

IS EMPTY

collection IS EMPTY

Проверяет, является ли коллекция пустой (не содержит элементов)

IS NOT EMPTY

collection IS NOT EMPTY

Проверяет, содержит ли коллекция хотя бы один элемент

Другие функции и операторы:

Имя

Синтаксис

Описание

ROW

ROW(value[, value]…)

Создает строковое значение

CAST

CAST(value AS type)

Преобразует значение в указанный тип

Infix cast

value::type

Преобразует значение в указанный тип с помощью синтаксиса приведения PostgreSQL

TYPEOF

TYPEOF(value)

Возвращает тип значения в DataGrid SQL

COALESCE

COALESCE(value, value[, value]…)

Возвращает первое значение из списка, которое не является NULL

NVL

NVL(value1, value2)

Возвращает значение value1, если оно не является NULL. В противном случае возвращает значение value2

NULLIF

NULLIF(value1, value2)

Возвращает NULL, если значения равны. В противном случае возвращает значение value1

CASE

CASE WHEN condition THEN result [ELSE result] END

Возвращает результат, который был выбран на основе условий ветвления

DECODE

DECODE(value, search, result[, search, result]…[, default])

Сравнивает значение с искомыми значениями и возвращает соответствующий результат

LEAST

LEAST(value[, value]…)

Возвращает наименьшее значение из переданных аргументов

GREATEST

GREATEST(value[, value]…)

Возвращает наибольшее значение из переданных аргументов

COMPRESS

COMPRESS(string)

Сжимает строку и возвращает данные в бинарном виде

OCTET_LENGTH

OCTET_LENGTH(binary)

Возвращает количество байт в бинарных данных

QUERY_ENGINE

QUERY_ENGINE()

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

SYSTEM_RANGE

TABLE(SYSTEM_RANGE(start, end[, increment]))

Возвращает таблицу с одним столбцом BIGINT с именем X и одной строкой для каждого значения в заданном диапазоне

Поддерживаемые типы данных#

Типы данных, которые поддерживает SQL-движок на основе Calcite:

Тип данных

Java-класс

BOOLEAN

java.lang.Boolean

DECIMAL

java.math.BigDecimal

DOUBLE

java.lang.Double

REAL/FLOAT

java.lang.Float

INT

java.lang.Integer

BIGINT

java.lang.Long

SMALLINT

java.lang.Short

TINYINT

java.lang.Byte

CHAR/VARCHAR

java.lang.String

DATE

java.sql.Date

TIME

java.sql.Time

TIMESTAMP

java.sql.Timestamp

INTERVAL YEAR TO MONTH

java.time.Period

INTERVAL DAY TO SECOND

java.time.Duration

BINARY/VARBINARY

byte[]

UUID

java.util.UUID

OTHER

java.lang.Object

Оптимизация запросов с помощью подсказок#

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

Примечание

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

Формат подсказок#

Подсказки SQL задаются специальным комментарием /*+ HINT */, который называется блоком подсказок. Пробелы до и после названия подсказки обязательны. Блок подсказок размещается сразу после реляционного оператора, обычно после SELECT. Несколько блоков для одного реляционного оператора использовать нельзя.

Пример

SQL#
SELECT /*+ NO_INDEX */ T1.* FROM TBL1 where T1.V1=? and T1.V2=?

Допускается задавать несколько подсказок для одного реляционного оператора. Для этого разделите их запятыми (пробелы необязательны).

Пример

SQL#
SELECT /*+ NO_INDEX, EXPAND_DISTINCT_AGG */ SUM(DISTINCT V1), AVG(DISTINCT V2) FROM TBL1 GROUP BY V3 WHERE V3=?

Параметры подсказок#

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

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

Пример

SQL#
SELECT /*+ FORCE_INDEX(TBL1_IDX2,TBL2_IDX1) */ T1.V1, T2.V1 FROM TBL1 T1, TBL2 T2 WHERE T1.V1 = T2.V1 AND T1.V2 > ? AND T2.V2 > ?;

SELECT /*+ FORCE_INDEX('TBL2_idx1') */ T1.V1, T2.V1 FROM TBL1 T1, TBL2 T2 WHERE T1.V1 = T2.V1 AND T1.V2 > ? AND T2.V2 > ?;

Области видимости подсказок#

Подсказки определяются для реляционного оператора, обычно для SELECT.

Большинство подсказок видны своим реляционным операторам, последующим операторам, запросам и подзапросам. Определенные в подзапросе подсказки видны только ему самому и его подзапросам. Подсказку не видно предыдущему реляционному оператору, если ее определили после него.

Пример

SQL#
SELECT /*+ NO_INDEX(TBL1_IDX2), FORCE_INDEX(TBL2_IDX2) */ T1.V1 FROM TBL1 T1 WHERE T1.V2 IN (SELECT T2.V2 FROM TBL2 T2 WHERE T2.V1=? AND T2.V2=?);

SELECT T1.V1 FROM TBL1 T1 WHERE T1.V2 IN (SELECT /*+ FORCE_INDEX(TBL2_IDX2) */ T2.V2 FROM TBL2 T2 WHERE T2.V1=? AND T2.V2=?);

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

SQL#
SELECT /*+ FORCE_INDEX */ V1 FROM TBL1 WHERE V1=? AND V2=?
UNION ALL
SELECT V1 FROM TBL1 WHERE V3>?

Исключение: подсказки уровня движка или оптимизатора, например DISABLE_RULE и QUERY_ENGINE, нужно определять в начале запроса. Они относятся ко всему запросу.

Ошибки в подсказках#

Оптимизатор пытается применить каждую подсказку и ее параметры, если это возможно. Он пропускает подсказку или параметр, если:

  • такой подсказки не существует или она не поддерживается;

  • не передали необходимые для подсказки параметры;

  • параметры подсказки передали, но она их не поддерживает;

  • параметр подсказки некорректен или ссылается на несуществующий объект (например, индекс или таблицу);

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

Поддерживаемые подсказки#

FORCE_INDEX#

Принудительно использует индексы для сканирования таблиц.

Параметры:

  • Пусто — принудительно использует индексы для сканирования таблиц. Оптимизатор выберет любой доступный индекс.

  • Одно название индекса — оптимизатор использует указанный индекс.

  • Несколько названий индексов (могут относиться к разным таблицам) — оптимизатор выберет указанные индексы при сканировании.

Пример

SQL#
SELECT /*+ FORCE_INDEX */ T1.* FROM TBL1 T1 WHERE T1.V1 = T2.V1 AND T1.V2 > ?;

SELECT /*+ FORCE_INDEX(TBL1_IDX2, TBL2_IDX1) */ T1.V1, T2.V1 FROM TBL1 T1, TBL2 T2 WHERE T1.V1 = T2.V1 AND T1.V2 > ? AND T2.V2 > ?;

NO_INDEX#

Отключает сканирование индексов.

Параметры:

  • Пусто — не использовать индексы при сканировании таблиц. Оптимизатор отключит все индексы.

  • Одно название индекса — оптимизатор пропустит указанный индекс.

  • Несколько названий индексов (могут относиться к разным таблицам) — оптимизатор пропустит указанные индексы при сканировании.

Пример

SQL#
SELECT /*+ NO_INDEX */ T1.* FROM TBL1 T1 WHERE T1.V1 = T2.V1 AND T1.V2 > ?;

SELECT /*+ NO_INDEX(TBL1_IDX2, TBL2_IDX1) */ T1.V1, T2.V1 FROM TBL1 T1, TBL2 T2 WHERE T1.V1 = T2.V1 AND T1.V2 > ? AND T2.V2 > ?;

ENFORCE_JOIN_ORDER#

Устанавливает порядок соединений (JOIN), который указан в запросе. Оптимизатор не будет пытаться изменить порядок исполнения. Позволяет ускорить построение плана запроса с большим количеством соединений.

Пример

SQL#
SELECT /*+ ENFORCE_JOIN_ORDER */ T1.V1, T2.V1, T2.V2, T3.V1, T3.V2, T3.V3 FROM TBL1 T1 JOIN TBL2 T2 ON T1.V3=T2.V1 JOIN TBL3 T3 ON T2.V3=T3.V1 AND T2.V2=T3.V2

SELECT t1.v1, t3.v2 FROM TBL1 t1 JOIN TBL3 t3 on t1.v3=t3.v3 WHERE t1.v2 in (SELECT /*+ ENFORCE_JOIN_ORDER */ t2.v2 FROM TBL2 t2 JOIN TBL3 t3 ON t2.v1=t3.v1)

EXPAND_DISTINCT_AGG#

Принудительно разворачивает несколько операций агрегирования с ключевым словом DISTINCT через соединения (JOIN). Удаляет дубликаты перед объединением (JOIN) и ускоряет его.

Пример

SQL#
SELECT /*+ EXPAND_DISTINCT_AGG */ SUM(DISTINCT V1), AVG(DISTINCT V2) FROM TBL1 GROUP BY V3

QUERY_ENGINE#

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

Параметры:

Требуется один параметр — название движка.

Пример

SQL#
SELECT /*+ QUERY_ENGINE('calcite') */ V1 FROM TBL1

DISABLE_RULE#

Отключает определенные правила оптимизатора. Указание на уровне оптимизатора.

Параметры:

Одно или несколько правил оптимизатора для пропуска.

Пример

SQL#
SELECT /*+ DISABLE_RULE('MergeJoinConverter') */ T1.* FROM TBL1 T1 JOIN TBL2 T2 ON T1.V1=T2.V1 WHERE T2.V2=?