Tarantool CE/EE Documentation portal logo
Помощь
Обновлена 15 сентября 2026 г. в 08:55

Руководство по SQL для начинающих

Это руководство описывает, как начать работу с SQL в Tarantool, и содержит описание необходимых концепций.

Руководство по SQL для начинающих посвящено базам данных в целом, а также связи между продуктами Tarantool NoSQL и SQL. Большая часть материала руководства уже знакома тем, кто ранее работал с реляционными базами данных.

Предварительные требования

Перед началом этого руководства:

  1. Установите утилиту tt CLI.

  2. Запустите экземпляр Tarantool в интерактивном режиме с помощью команды tt run -i:

    $ tt run -iTarantool 3.0.0-0-g6ba34da7f8type 'help' for interactive helptarantool>
  3. Инициализируйте экземпляр и переключите язык ввода на SQL:

    tarantool> box.cfg{}tarantool> \set language sqltarantool>  \set delimiter ;

Теперь запущен экземпляр Tarantool, принимающий ввод на SQL.

Пример таблицы

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

TABLE          [1]              [2]              [3]       +-----------------+----------------+----------------+ Row#1 | Row#1,Column#1  | Row#1,Column#2 | Row#1,Column#3 |       +-----------------+----------------+----------------+ Row#2 | Row#2,Column#1  | Row#2,Column#2 | Row#2,Column#3 |       +-----------------+----------------+----------------+ Row#3 | Row#3,Column#1  | Row#3,Column#2 | Row#3,Column#3 |       +-----------------+----------------+----------------+

Но порядковые номера в заголовках вводят в заблуждение — обычно строки и столбцы идентифицируются не по их порядковым позициям, а по содержимому. Например:

MODULES+-----------------+------+---------------------+| NAME            | SIZE | PURPOSE             |+-----------------+------+---------------------+| box             | 1432 | Database Management || clock           |  188 | Seconds             || crypto          |    4 | Cryptography        |+-----------------+------+---------------------+

Поэтому вместо навигации по координатам, говоря «Строка №2, Столбец №2», используют содержимое столбца Name и имя столбца Size, говоря «размер, где имя — clock». Точнее говоря:

SELECT size FROM modules WHERE name = 'clock';

Если вы знакомы с архитектурой Tarantool — в идеале вы прочитали об этом до того, как перейти к этой главе — то знаете, что получить те же данные можно с помощью NoSQL:

box.space.MODULES:select()[2][2]

Да, это возможно. Одно из преимуществ Tarantool заключается в том, что если данные можно получить с помощью SQL-запроса, то те же данные можно получить и через NoSQL-запрос. Обратное неверно, поскольку не все наборы кортежей NoSQL можно определить как SQL-таблицы. Для SQL действуют следующие ограничения, которые не применяются к NoSQL:

  1. Каждый столбец должен иметь имя.
  2. Каждый столбец должен иметь скалярный тип (Tarantool нестрого относится к тому, какой именно скалярный тип можно использовать, но индексировать и искать массивы, таблицы внутри таблиц или то, что MessagePack называет «maps», невозможно.)

Предложение «format» в Tarantool/NoSQL накладывает те же ограничения.

Таким образом, SQL-«таблица» — это «набор кортежей с ограничениями формата» в NoSQL, SQL-«строка» — это «кортеж» в NoSQL, SQL-«столбец» — это «список полей в наборе кортежей» в NoSQL.

Создание таблицы

Так создается таблица modules:

CREATE TABLE modules (name STRING, size INTEGER, purpose STRING, PRIMARY KEY (name));

Слова, написанные ЗАГЛАВНЫМИ БУКВАМИ, являются «ключевыми словами» (хотя выделение ключевых слов заглавными буквами — это лишь соглашение, принятое в данном руководстве; на практике многие программисты предпочитают не выделять их заглавными буквами). Ключевые слова имеют значение для SQL-парсера, поэтому многие из них зарезервированы и не могут использоваться в качестве имен, если они не заключены в кавычки.

Слово «modules» — это «имя таблицы», а слова «name», «size» и «purpose» — «имена столбцов». Все таблицы и все столбцы должны иметь имена.

Слова «STRING» и «INTEGER» — это «типы данных». STRING означает «содержимое должно состоять из символов, длина не ограничена, эквивалентный тип NoSQL — 'string'». INTEGER означает «содержимое должно быть числом без десятичной точки, эквивалентный тип NoSQL –- 'integer'». Tarantool поддерживает и другие типы данных, но в примере таблицы из этого раздела используются типы данных из двух основных групп, а именно: типы данных для чисел и типы данных для строк.

Последнее выражение, PRIMARY KEY (name), означает, что столбец name является главным столбцом, используемым для идентификации строки.

Значения NULL

Часто бывает необходимо, хотя бы временно, чтобы значение столбца было NULL. Типичные ситуации: значение неизвестно или неприменимо. Например, можно создать модуль в качестве заполнителя, не указывая его размер или назначение. Если такое возможно, столбец допускает значения NULL. Столбец name в таблице из примера не может содержать значения NULL, и его можно явно определить как "name STRING NOT NULL", но в данном случае это излишне — столбец, определенный как PRIMARY KEY, автоматически является NOT NULL.

Является ли NULL в SQL тем же самым, что и nil в Lua? Нет, но они достаточно похожи, чтобы вызвать путаницу. Когда nil означает «неизвестно» или «неприменимо» — да. Но когда nil означает «несуществующий» или «тип — nil», нет. NULL — это значение, оно имеет тип данных, так как находится внутри столбца, определенного с этим типом данных.

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

Так создаются индексы для таблицы modules:

CREATE INDEX size ON modules (size);CREATE UNIQUE INDEX purpose ON modules (purpose);

Создавать индекс для столбца name не нужно, так как индекс создается автоматически при наличии условия PRIMARY KEY в операторе CREATE TABLE. На самом деле создавать индексы для столбцов size или purpose тоже не нужно — если индексов не существует, столбцы все равно можно использовать для поиска. Обычно непервичные индексы, также называемые вторичными индексами, создаются, когда становится ясно, что таблица сильно разрастется, а поиск будет частым, поскольку поиск по индексу, как правило, выполняется намного быстрее, чем без него.

Другое применение индексов — обеспечение уникальности. Если для столбца purpose создается индекс с помощью CREATE UNIQUE INDEX, в этом столбце не может быть повторяющихся значений.

Изменение данных

Добавление данных в таблицу называется "вставкой". Изменение данных называется "обновлением". Удаление данных называется "удалением". Вместе три SQL-оператора — INSERT, UPDATE и DELETE — являются тремя основными операторами "изменения данных".

Так можно вставить, обновить и удалить строку в таблице modules:

INSERT INTO modules VALUES ('json', 14, 'format functions for JSON');UPDATE modules SET size = 15 WHERE name = 'json';DELETE FROM modules WHERE name = 'json';

Соответствующие запросы Tarantool без SQL выглядели бы так:

box.space.MODULES:insert{'json', 14, 'format functions for JSON'}box.space.MODULES:update('json', {{'=', 2, 15}})box.space.MODULES:delete{'json'}

Так можно заполнить таблицу значениями, показанными ранее:

INSERT INTO modules VALUES ('box', 1432, 'Database Management');INSERT INTO modules VALUES ('clock', 188, 'Seconds');INSERT INTO modules VALUES ('crypto', 4, 'Cryptography');

Ограничения

Некоторые операторы изменения данных недопустимы из-за особенностей определения таблицы. Это называется «ограничением допустимых действий». Некоторые типы ограничений уже были показаны выше:

NOT NULL — если столбец определен с предложением NOT NULL, в него нельзя поместить NULL. Столбец первичного ключа автоматически является NOT NULL.

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

Домен данных — если столбец определен с типом данных INTEGER, в него нельзя поместить нечисловое значение. В более общем виде: если значение не соответствует типу данных из определения, оно недопустимо. Некоторые системы управления базами данных (СУБД) проявляют гибкость и пытаются скорректировать некорректные значения, а не отклонять их; Tarantool строже таких СУБД.

Ниже описаны другие типы ограничений:

CHECK — в описании таблицы может присутствовать предложение «CHECK (условное выражение)». Например, если оператор CREATE TABLE modules выглядит так:

CREATE TABLE modules (name STRING,                      size INTEGER,                      purpose STRING,                      PRIMARY KEY (name),                      CHECK (size > 0));

то следующий оператор INSERT будет недопустимым: INSERT INTO modules VALUES ('box', 0, 'The Database Kernel'); поскольку ограничение CHECK требует, чтобы второй столбец — столбец size — не содержал значение, меньшее или равное нулю. Попробуйте вместо этого: INSERT INTO modules VALUES ('box', 1, 'The Database Kernel');

FOREIGN KEY — в описании таблицы может присутствовать предложение «FOREIGN KEY (список столбцов) REFERENCES таблица (список столбцов)». Например, если существует новая таблица «submodules», которая зависит от таблицы modules, ее можно определить следующим образом:

CREATE TABLE submodules (name STRING,                         module_name STRING,                         size INTEGER,                         purpose STRING,                         PRIMARY KEY (name),                         FOREIGN KEY (module_name) REFERENCES                         modules (name));

Теперь попробуйте вставить новую строку в таблицу submodules:

INSERT INTO submodules VALUES  ('space', 'Box', 10000, 'insert etc.');

Вставка завершится ошибкой, так как второй столбец (module_name) ссылается на столбец name в таблице modules, а столбец name в таблице modules не содержит значения 'Box'. Однако в нем содержится значение 'box'. По умолчанию в SQL Tarantool используется бинарное сопоставление. Следующий вариант сработает:

INSERT INTO submodules  VALUES ('space', 'box', 10000, 'insert etc.');

Теперь попробуйте удалить соответствующую строку из таблицы modules:

DELETE FROM modules WHERE name = 'box';

Удаление завершится ошибкой, так как второй столбец (module_name) в таблице submodules ссылается на столбец name в таблице modules, а при успешном удалении столбец name в таблице modules больше не будет содержать значение 'box'. Таким образом, ограничение FOREIGN KEY затрагивает как таблицу, содержащую предложение FOREIGN KEY, так и таблицу, на которую это предложение ссылается.

Ограничения в определении таблицы — NOT NULL, UNIQUE, домен данных, CHECK и FOREIGN KEY — выступают гарантами целостности базы данных. Важно, что они являются фиксированными и четко определенными частями определения, которые трудно обойти средствами SQL. Это часто рассматривается как различие между SQL и NoSQL: SQL делает акцент на законе и порядке, а NoSQL — на свободе и собственных правилах.

Связи между таблицами

Вспомним две таблицы, которые рассматривались ранее:

CREATE TABLE modules (name STRING,                      size INTEGER,                       purpose STRING,                       PRIMARY KEY (name),                       CHECK (size > 0));CREATE TABLE submodules (name STRING,                         module_name STRING,                         size INTEGER,                         purpose STRING,                         PRIMARY KEY (name),                         FOREIGN KEY (module_name) REFERENCES                         modules (name));

Благодаря условию FOREIGN KEYS в таблице submodules, между ними явно прослеживается связь «многие к одному»:

submodules –>> modules

то есть каждая строка таблицы submodules должна ссылаться на одну (и только одну) строку таблицы modules, тогда как на каждую строку таблицы modules может ссылаться ноль и более строк таблицы submodules.

Связи между таблицами важны, но будьте осторожны: не верьте тем, кто говорит, что базы данных, созданные с помощью SQL, являются реляционными «потому что между таблицами есть связи». Это неверно, что станет ясно из дальнейшего обсуждения того, что делает базу данных реляционной.

Выборка с использованием WHERE

Ранее мы приводили простой пример оператора SELECT:

SELECT size FROM modules WHERE name = 'clock';

Предложение "WHERE name = 'clock'" допустимо и в других операторах –- оно встречается в примерах с UPDATE и DELETE — но здесь будут приведены примеры только с SELECT.

Первое отличие состоит в том, что указывать предложение WHERE вообще не обязательно. Таким образом, следующий оператор вернет все строки:

SELECT size FROM modules;

Второе отличие — оператором сравнения не обязательно должно быть =. Это может быть любой подходящий оператор: > или >= или < или <=, либо LIKE — оператор для работы со строками, который может содержать подстановочные символы _ (означает "совпадение с любым одним символом") или `%`` (означает "совпадение с любым количеством символов, включая ноль"). Это допустимые операторы, которые возвращают все строки:

SELECT size FROM modules WHERE name >= '';SELECT size FROM modules WHERE name LIKE '%';

Третье отличие заключается в том, что IS [NOT] NULL — это специальное условие. Напомним, что значение NULL может означать "неизвестно, каким должно быть значение", и предположим, что в какой-то строке size равно NULL. Тогда условие "size > 10" не является заведомо истинным и не является заведомо ложным, поэтому оно вычисляется как "unknown" (неизвестно). Обычно применение предложения WHERE отфильтровывает как ложные, так и неизвестные результаты. Поэтому при поиске NULL используйте IS NULL, а при поиске всего, что не является NULL, используйте IS NOT NULL. Этот оператор вернет все строки, так как (согласно определению) в столбце name нет значений NULL:

SELECT size FROM modules WHERE name IS NOT NULL;

Четвертое отличие: условия можно комбинировать с помощью AND / OR и инвертировать с помощью NOT.

Таким образом, этот оператор вернет все строки (первое условие ложно, но второе истинно, а OR означает "вернуть истину, если хотя бы одно условие истинно"):

SELECT sizeFROM modulesWHERE name = 'wombat' OR size IS NOT NULL;

Выборка со списком выборки

И снова приведем простой пример оператора SELECT:

SELECT size FROM modules WHERE name = 'clock';

Слова между SELECT и FROM составляют список выборки. В данном случае список выборки состоит всего из одного слова: size. Формально это означает, что требуется вернуть значения size, а технически выбор конкретного столбца называется "проекцией".

Первое отличие: можно указать любой столбец в любом порядке:

SELECT name, purpose, size FROM modules;

Второе отличие: можно указать выражение — это не обязательно должно быть имя столбца, оно может вообще не содержать имени столбца. Распространенными операторами выражений для чисел являются арифметические операторы + - / *; распространенным оператором выражений для строк является оператор конкатенации ||. Например, этот оператор вернет 8, 'XY':

SELECT size * 2, 'X' || 'Y' FROM modules WHERE size = 4;

Третье отличие: после каждого выражения можно добавить предложение [AS name], чтобы заголовки столбцов в возвращаемом результате были осмысленными. Это особенно важно, когда заголовок в противном случае может быть неоднозначным или бессмысленным. Например, этот оператор вернет 8, 'XY', как и раньше,

SELECT size * 2 AS double_size, 'X' || 'Y' AS concatenated_literals  FROM modules  WHERE size = 4;

но при отображении в виде таблицы результат будет выглядеть так:

+----------------+------------------------+| DOUBLE_SIZE    | CONCATENATED_LITERALS  |+----------------+------------------------+|              8 | XY                     |+----------------+------------------------+

Выборка со списком выборки со звездочкой

Вместо перечисления столбцов в списке выборки можно просто указать '*'. Например:

SELECT * FROM modules;

Это то же самое, что и

SELECT name, size, purpose FROM modules;

Использование "*" экономит время автора, но может быть непонятно читателю, который не запомнил имена столбцов. Кроме того, это нестабильно, так как существует способ изменить определение таблицы (оператор ALTER — это продвинутая тема). Тем не менее, хотя использовать это в production-среде может быть не лучшей идеей, для ознакомительных целей это удобно, поэтому "*" будет встречаться в некоторых последующих примерах.

Выборка с подзапросами

Напомним, что существует таблица modules и таблица submodules. Предположим, нужно вывести список подмодулей, которые ссылаются на модули, назначение которых — X. Иными словами, требуется выполнить поиск в одной таблице, используя значение из другой. Это можно сделать, включив «(SELECT ...)» в предложение WHERE. Например:

SELECT name FROM submodulesWHERE module_name =    (SELECT name FROM modules WHERE purpose LIKE '%Database%');

Подзапросы также полезны в списке выборки, когда требуется объединить информацию из нескольких таблиц. Например, следующий оператор выведет строки из таблицы submodules, но также включит значения из таблицы modules:

SELECT name AS submodules_name,    (SELECT purpose FROM modules     WHERE modules.name = submodules.module_name)     AS modules_purpose,    purpose AS submodules_purposeFROM submodules;

Что же такое «modules.name» и «submodules.name»? Везде, где встречается запись вида «x.y», используется «составное имя столбца», где первая часть — это идентификатор таблицы, а вторая — идентификатор столбца. Использование составных имён столбцов всегда допустимо, но до сих пор в этом не было необходимости. Теперь же это необходимо — или, по крайней мере, желательно, поскольку в обеих таблицах есть столбец с именем «name».

Результат будет выглядеть так:

+-------------------+------------------------+--------------------+| SUBMODULES_NAME   | MODULES_PURPOSE        | SUBMODULES_PURPOSE |+-------------------+------------------------+--------------------+| space             | Database Management    | insert etc.        |+-------------------+------------------------+--------------------+

Возможно, вы где-то читали, что SQL расшифровывается как «Structured Query Language» (язык структурированных запросов). Это уже не так. Но верно то, что синтаксис запросов допускает использование структурного компонента — а именно подзапроса, — и именно в этом заключалась изначальная идея. Однако существует и другой способ объединения таблиц –- с помощью соединений (joins) вместо подзапросов.

Выборка с декартовым соединением

До сих пор в инструкциях SELECT использовались только конструкции FROM modules или FROM submodules. Что если в предложении FROM указано более одной таблицы? Например:

SELECT * FROM modules, submodules;

или

SELECT * FROM modules JOIN submodules;

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

{ columns from modules table }         { columns from submodules table }+--------+------+---------------------+-------+-------------+-------+-------------+| NAME   | SIZE | PURPOSE             | NAME  | MODULE_NAME | SIZE  | PURPOSE     |+--------+------+---------------------+-------+-------------+-------+-------------+| box    | 1432 | Database Management | space | box         | 10000 | insert etc. || clock  |  188 | Seconds             | space | box         | 10000 | insert etc. || crypto |    4 | Cryptography        | space | box         | 10000 | insert etc. |+--------+------+---------------------+-------+-------------+-------+-------------+

Это не ошибка. Смысл такого типа соединения (JOIN) — «объединить каждую строку из таблицы 1 с каждой строкой из таблицы 2». Поскольку связь между таблицами не указана, в результат попадает всё, включая строки, где подмодуль никак не связан с модулем.

Приведённый выше результат, называемый результатом «декартова соединения», удобно рассмотреть, чтобы понять, что именно было бы желательно получить. Вероятно, в данном случае имеет смысл только та строка, где modules.name = submodules.module_name, и лучше явно указать это как в списке выборки, так и в предложении WHERE:

SELECT modules.name AS modules_name,       modules.size AS modules_size,       modules.purpose AS modules_purpose,       submodules.name,       module_name,       submodules.size,       submodules.purposeFROM modules, submodulesWHERE modules.name = submodules.module_name;

Результат:

+----------+-----------+------------+--------+---------+-------+-------------+| MODULES_ |  MODULES_ | MODULES_   | NAME   | MODULE_ | SIZE  | PURPOSE     || NAME     |  SIZE     | PURPOSE    |        | NAME    |       |             |+----------+-----------+--------- --+--------+---------+-------+-------------+| box      |      1432 | Database   | space  | box     | 10000 | insert etc. ||          |           | Management |        |         |       |             |+----------+-----------+------------+--------+---------+-------+-------------+

Иными словами, в предложении FROM можно задать декартово соединение, затем отфильтровать нерелевантные строки в предложении WHERE, а затем переименовать столбцы в списке выборки. Это допустимо, и такая возможность поддерживается любой SQL СУБД. Однако вызывает беспокойство то, что количество строк при декартовом соединении всегда равно (количеству строк в первой таблице, умноженному на количество строк во второй таблице), то есть концептуально фильтрация часто выполняется на большом наборе строк.

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

Выборка с соединением и предложением ON

Предложение ON содержит те же сравнения, что и предложение WHERE, показанное в предыдущем разделе, но иной синтаксис подчеркивает, что это «именно для соединения». Таким образом становится понятно, что, по крайней мере концептуально, это является предварительным шагом перед фильтрацией строк результата. Например:

SELECT * FROM modules JOIN submodules  ON (modules.name = submodules.module_name);

Это то же самое, что:

SELECT * FROM modules, submodules  WHERE modules.name = submodules.module_name;

Выборка с соединением с использованием предложения USING

Предложение USING использует совпадающие имена столбцов в обеих таблицах, предполагая, что цель — сопоставить эти столбцы с помощью сравнений =. Например,

SELECT * FROM modules JOIN submodules USING (name);

даст тот же результат, что и

SELECT * FROM modules JOIN submodules WHERE modules.name = submodules.name;

Если бы при создании таблицы заранее планировалось использовать предложения USING, это сэкономило бы время. Но этого не произошло. Поэтому, хотя приведенный выше пример «работает», результаты не будут осмысленными.

Выборка с естественным соединением

Естественное соединение (NATURAL JOIN) использует совпадающие имена столбцов в двух таблицах, выполняет фильтрацию автоматически на основе этого совпадения и отбрасывает повторяющиеся столбцы.

Если бы таблицы были созданы заранее с учетом использования естественных соединений, это было бы очень удобно. Но этого сделано не было. Поэтому, хотя следующий пример и «работает», результаты не будут иметь смысла.

SELECT * FROM modules NATURAL JOIN submodules;

Результат: ничего, так как modules.name не совпадает с submodules.name, и так далее. И даже если бы был результат, он включал бы только четыре столбца: name, module_name, size, purpose.

Выборка с левым соединением

Что если требуется соединить модули с подмодулями, но при этом необходимо гарантированно получить все модули? Иными словами, предположим, что нужно получить модули, даже если условие submodules.module_name = modules.name не выполняется, потому что у модуля нет подмодулей.

Когда возникает такое требование, используется тип соединения OUTER JOIN (внешнее соединение) — в отличие от использованного ранее типа INNER JOIN (внутреннее соединение). В данном случае формат будет LEFT [OUTER] JOIN, так как основная таблица modules находится слева. Например:

SELECT *FROM modules LEFT JOIN submodulesON modules.name = submodules.module_name;

Результат:

{ columns from modules table }         { columns from submodules table }+--------+------+---------------------+-------+-------------+-------+-------------+| NAME   | SIZE | PURPOSE             | NAME  | MODULE_NAME | SIZE  | PURPOSE     |+--------+------+---------------------+-------+-------------+-------+-------------+| box    | 1432 | Database Management | space | box         | 10000 | insert etc. || clock  |  188 | Seconds             | NULL  | NULL        | NULL  | NULL        || crypto |    4 | Cryptography        | NULL  | NULL        | NULL  | NULL        |+--------+------+---------------------+-------+-------------+-------+-------------+

Таким образом, для подмодулей модуля clock и подмодулей модуля crypto, которых не существует, в каждой колонке содержится NULL.

Выборка с функциями

Функция может принимать любое выражение, включая выражение, содержащее другую функцию, и возвращать скалярное значение. Таких функций много. Здесь будет описана только одна — SUBSTR, которая возвращает подстроку строки.

Формат: SUBSTR({input-string}, {start-with} [, {length}])

Описание: SUBSTR принимает входную строку (input-string), удаляет все символы до позиции start-with, удаляет все символы после (start-with плюс length) и возвращает результат.

Пример: SUBSTR('abcdef', 2, 3) возвращает 'bcd'.

Выборка с агрегацией, GROUP BY и HAVING

Напомним, что таблица modules выглядит так:

MODULES+-----------------+------+---------------------+| NAME            | SIZE | PURPOSE             |+-----------------+------+---------------------+| box             | 1432 | Database Management || clock           |  188 | Seconds             || crypto          |    4 | Cryptography        |+-----------------+------+---------------------+

Предположим, что нет необходимости знать все отдельные значения size, важна только их агрегация, то есть получение атрибутов коллекции. SQL поддерживает агрегатные функции, включая: AVG (среднее), SUM (сумма), MIN (минимум), MAX (максимум) и COUNT (количество). Например:

SELECT AVG(size), SUM(size), MIN(size), MAX(size), COUNT(size) FROM modules;

Результат будет выглядеть так:

+-----------+-----------+-----------+-----------+-----------+| COLUMN_1  | COLUMN_2  | COLUMN_3  | COLUMN_4  | COLUMN_5  |+-----------+-----------+-----------+-----------+-----------||       541 |      1624 |         4 |      1432 |         3 |+-----------+-----------+-----------+-----------+-----------+

Предположим, что требуются агрегации, но агрегации строк, имеющих некоторый общий признак. Допустим, строки нужно разделить на две группы: те, чьи имена начинаются с 'b', и те, чьи имена начинаются с 'c'. Это можно сделать, добавив конструкцию [GROUP BY выражение]. Например:

SELECT SUBSTR(name, 1, 1), AVG(size), SUM(size), MIN(size), MAX(size), COUNT(size)FROM modulesGROUP BY SUBSTR(name, 1, 1);

Результат будет выглядеть так:

+------------+--------------+-----------+-----------+-----------+-------------+| COLUMN_1   | COLUMN_2     | COLUMN_3  | COLUMN_4  | COLUMN_5  | COLUMN_6    |+------------+--------------+-----------+-----------+-----------+-------------+| b          |         1432 |      1432 |      1432 |      1432 |           1 || c          |           96 |       192 |         4 |       188 |           2 |+------------+--------------+-----------+-----------+-----------+-------------+

Выборка с обобщенным табличным выражением

С помощью предложения WITH можно определить временную (виртуальную) таблицу внутри оператора, как правило, оператора SELECT. Например:

WITH tmp_table AS (SELECT x1 FROM t1) SELECT * FROM tmp_table;

Выборка с сортировкой, ограничением и смещением

До сих пор при каждом поиске в таблице modules строки выводились в алфавитном порядке по имени: 'box', затем 'clock', затем 'crypto'. Однако, чтобы действительно убедиться в порядке сортировки или задать другой порядок, необходимо явно указать это, добавив выражение: ORDER BY column-name [ASC|DESC]. (ASC означает возрастание (ASCending), DESC — убывание (DESCending).) Например:

SELECT * FROM modules ORDER BY name DESC;

Результатом будут те же строки, но в обратном алфавитном порядке: 'crypto', затем 'clock', затем 'box'.

После выражения ORDER BY может идти выражение LIMIT n, где n –- максимальное количество возвращаемых строк. Например:

SELECT * FROM modules ORDER BY name DESC LIMIT 2;

Результатом будут первые две строки: 'crypto' и 'clock'.

После выражений ORDER BY и LIMIT может идти выражение OFFSET n, где n — номер строки, с которой начинается вывод. Первое смещение — 0. Например:

SELECT * FROM modules ORDER BY name DESC LIMIT 2 OFFSET 2;

Результатом будет третья строка: 'box'.

Представления

Представление — это сохраненный запрос SELECT. Если есть сложный запрос SELECT, который нужно выполнять часто, создайте представление, а затем делайте простой SELECT из этого представления. Например:

CREATE VIEW v AS SELECT size, (size *5) AS size_times_5FROM modulesGROUP BY size, nameORDER BY size_times_5;SELECT * FROM v;

Транзакции

В Tarantool используется журнал предзаписи (Write Ahead Log, WAL). Результаты выполнения операторов изменения данных записываются в журнал до того, как они будут сохранены на диске. Благодаря этому, хотя базы данных целиком могут храниться в оперативной памяти, они защищены от сбоев при потере питания.

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

Рассмотрим следующие операторы:

CREATE TABLE things (remark STRING, PRIMARY KEY (remark));START TRANSACTION;INSERT INTO things VALUES ('A');COMMIT;START TRANSACTION;INSERT INTO things VALUES ('B');ROLLBACK;SELECT * FROM things;

Результат будет следующим: одна строка, содержащая 'A'. Оператор ROLLBACK отменил второй оператор INSERT, но не отменил первый, так как он уже был зафиксирован.

Обычно каждый оператор фиксируется автоматически.

После START TRANSACTION операторы не фиксируются автоматически — считается, что транзакция теперь «активна», пока она не завершится оператором COMMIT или ROLLBACK. Пока транзакция активна, все операторы допустимы, за исключением еще одного START TRANSACTION.

Реализация SQL в Tarantool поверх NoSQL

Данные SQL в Tarantool — это те же данные, что и данные NoSQL в Tarantool. При создании таблицы или индекса средствами SQL создается спейс или индекс в NoSQL. Например:

CREATE TABLE things (remark STRING, PRIMARY KEY (remark));INSERT INTO things VALUES ('X');

в некоторой степени аналогично следующему коду на Lua:

box.schema.space.create('THINGS',{    format = {              [1] = {["name"] = "REMARK", ["type"] = "string"}              }})box.space.THINGS:create_index('pk_unnamed_THINGS_1',{unique=true,parts={1,'string'}})box.space.THINGS:insert{'X'}

Таким образом, можно использовать возможности NoSQL Tarantool, даже если SQL — основной язык. Ниже приведены некоторые из них.

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

  2. Хранимые процедуры можно писать на Lua, комбинируя управляющие конструкции и библиотечные функции Lua с SQL-запросами. Эти процедуры выполняются на сервере — это главное преимущество хранимых процедур на чистом SQL.

  3. Некоторые возможности реализованы в NoSQL, но (пока) не реализованы в SQL. Например, с помощью NoSQL можно изменить параметры индекса и запретить доступ пользователям с именем 'guest'.

  4. К системным спейсам, таким как _space и _index, можно обращаться с помощью SQL-запросов SELECT. Это не совсем то же самое, что information_schema, но означает, что для доступа к каталогу метаданных базы данных можно использовать SQL.

Поля в спейсах NoSQL доступны через SQL тогда и только тогда, когда они являются скалярными и определены в предложениях format. Индексы спейсов NoSQL используются с SQL тогда и только тогда, когда они являются индексами типа TREE.

Реляционные базы данных

Эдгар Ф. Кодд, внёсший наибольший вклад в исследование и объяснение концепций реляционных баз данных, перечислил основные критерии в виде (12 правил Кодда).

Хотя Tarantool не позиционируется как «реляционная» СУБД, утверждается, что он соответствует этим правилам со следующими оговорками и исключениями.

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

Правила гласят, что должен существовать динамический онлайн-каталог. В Tarantool он есть, но в нем отсутствуют некоторые метаданные.

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

Правила требуют, чтобы данные были физически независимы (от изменений в базовом хранилище) и логически независимы (от изменений в прикладных программах). На данный момент недостаточно опыта, чтобы дать такую гарантию.

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

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

Подробнее об SQL в Tarantool см. в справочнике.