Перейти до вмісту

Основи мови SQLScript

SQLScript — мова програмування на стороні SAP HANA, розширення SQL. Те, що в ABAP називається AMDP, всередині написане саме на SQLScript. На відміну від звичайного SQL, тут є змінні, цикли, IF/ELSE, винятки, процедури і функції. Декларативна частина (SELECT, JOIN) майже така сама, як у SQL — імперативні розширення дають справжнє програмування на базі.

Ця сторінка — про базові елементи мови: синтаксис, типи літералів, змінні, оператори, NULL, модуляризацію в процедури/функції/бібліотеки. AMDP-обгортку з боку ABAP дивись на сторінці AMDP.

КритерійSQLSQLScript
ВиконанняОкремі statement-иСкрипт як ціле, в логічному контейнері
ЗмінніТільки host-параметриСкалярні, табличні, масиви
Контроль потокуНемаєIF/ELSE, FOR, WHILE, курсори
ВиняткиНемаєDECLARE EXIT HANDLER, SIGNAL
МодуляризаціяНемаєProcedures, functions, libraries
ПризначенняCRUDCRUD + бізнес-логіка на стороні БД

SQLScript — суперсет: будь-який SQL-statement є валідним SQLScript-statement.

Statement-и, пропуски, коментарі

Section titled “Statement-и, пропуски, коментарі”

Statement закінчується крапкою з комою (;). Пропуски, табуляції і переноси рядків між токенами ігноруються — форматуй як зручно.

-- Однорядковий коментар (HANA-стиль)
/* Багаторядковий
коментар */
* В AMDP — також коментар (ABAP-стиль). При збереженні в БД
* перетворюється на --. У "чистому" SQLScript не використовуй.
ТипПрикладДані
String (ASCII)'Jörg'VARCHAR
String (Unicode)N'Jörg'NVARCHAR
Binaryx'FFF'VARBINARY
Integer-10INTEGER
Decimal-1.2345DECIMAL
Float-17.126E30DOUBLE
Hex0xFFINTEGER
DateDATE'2017-11-10'DATE
TimeTIME'15:42:04.123'TIME
TimestampTIMESTAMP'2011-12-31 23:59:59'TIMESTAMP
SELECT 'Jörg' AS string,
N'Jörg' AS unicode,
DATE'2017-11-10' AS d,
TIMESTAMP'2011-12-31 23:59:59' AS ts
FROM dummy;

Без префікса DATE/TIME/TIMESTAMP дата у лапках — звичайний рядок, який буде неявно конвертований за потреби. Краще завжди ставити префікс.

Дві нотації:

Simple notation — букви, цифри, _, #, $. Регістронезалежна — внутрішньо приводиться до верхнього регістру:

SELECT id, status, title FROM tasks; -- ID, STATUS, TITLE, TASKS

Special notation — у подвійних лапках, дозволяє Unicode, пробіли, спецсимволи. Зберігає регістр:

CREATE TABLE id_with_space("ID" int, " ID" int, "ID " int);

⚠️ Грабля: спеціальна нотація — пастка

У прикладі вище створено таблицю з трьома “однаковими” колонками — ID, пробіл+ID, ID+пробіл. Синтаксично валідно, у виводі візуально неможливо розрізнити. Завжди використовуй simple notation, бажано в UPPER_CASE.

В SQLScript змінні мають префікс : коли читаєш, без префікса коли пишеш:

DO BEGIN
DECLARE lv_count INTEGER = 0;
lv_count = lv_count + 1; -- запис без :
SELECT :lv_count FROM dummy; -- читання з :
END;

Параметри процедур/функцій працюють так само — :param_name у SQLScript-частині коду.

Префікс :: (два двокрапки):

ЗміннаЩо містить
::CURRENT_OBJECT_NAMEІмʼя поточної процедури/функції (NULL у анонімних блоках)
::CURRENT_OBJECT_SCHEMASchema поточного обʼєкта
::ROWCOUNTКількість записів, оновлених останнім DML-statement
::SQL_ERROR_CODEКод помилки (доступна в EXIT HANDLER)
::SQL_ERROR_MESSAGEТекст помилки (доступна в EXIT HANDLER)
::CURRENT_LINE_NUMBERНомер рядка в коді (з SAP HANA 2.0 SPS 02)

Арифметичні: +, -, *, /. Унарний - — знак.

Рядкові: || — конкатенація.

Реляційні: =, != (або <>), <, >, <=, >=. Результат — TRUE, FALSE або UNKNOWN (якщо хоч один операнд NULL).

Логічні: AND, OR, NOT.

Пріоритет (зверху вниз): дужки → знак → * /+ -|| → реляційні → NOTANDOR.

⚠️ Грабля: NULL ламає логіку

Будь-який оператор з NULL-операндом дає NULL (для логічних — UNKNOWN). WHERE x = NULL ніколи не спрацює — потрібно WHERE x IS NULL. Це причина, чому LEFT JOIN + WHERE right_table.field = 'X' несподівано фільтрує рядки без матчу — додавай OR right_table.field IS NULL.

Використовуються в WHERE, HAVING, CASE — повертають TRUE/FALSE/UNKNOWN:

ПредикатПризначення
LIKE 'pattern'Шаблон з % (будь-які символи) і _ (один символ)
IN (val1, val2, ...)Належність до списку
BETWEEN a AND bВключний діапазон
IS NULL / IS NOT NULLПеревірка NULL
EXISTS (subquery)Підзапит повернув хоча б один рядок
CONTAINS(...)Повнотекстовий пошук
MEMBER OF (multiset)Належність до multiset

⚠️ Грабля: предикати в IF/WHILE

У імперативних IF і WHILE не дозволені CONTAINS і MEMBER OF. EXISTS і IN дозволені тільки з SAP HANA 2.0 SPS 04. Квантори ALL, ANY, SOME — теж лише в SQL-частині.

Скалярні — одне значення (INTEGER, NVARCHAR, DATE, DECIMAL…). Деталі — на сторінці Типи даних і функції SQLScript.

Табличні — потрібні для table variables і table parameters. Створюються трьома способами:

-- 1) З наявної таблиці БД
DECLARE lt_tasks TABLE LIKE tasks;
-- 2) Через CREATE TYPE — окремий обʼєкт у каталозі
CREATE TYPE tt_task AS TABLE (id INT, title NVARCHAR(100));
DECLARE lt_tasks TABLE LIKE tt_task;
-- 3) Inline-визначення колонок
DECLARE lt_tasks TABLE (id INT, title NVARCHAR(100));

NULL — відсутність значення, не нуль і не порожній рядок. Окрема категорія з власною логікою:

SELECT 1 + NULL FROM dummy; -- NULL (не 1)
SELECT 'X' || NULL FROM dummy; -- NULL (не 'X')
SELECT CASE WHEN NULL = NULL
THEN 'eq' ELSE 'ne' END
FROM dummy; -- 'ne' (UNKNOWN ≠ TRUE)

Оброблення NULL у виразах:

COALESCE(col, 0) -- перше не-NULL значення
IFNULL(col, 0) -- те саме для двох аргументів
NULLIF(col, 0) -- NULL якщо col = 0

DUMMY — системна таблиця з єдиним рядком (X). Використовується, коли потрібно виконати SELECT без реального джерела даних:

SELECT CURRENT_DATE, CURRENT_USER FROM dummy;
SELECT 'Hello' || ' ' || 'World' FROM dummy;

Альтернатива — взагалі без FROM:

SELECT CURRENT_DATE;

Обидва варіанти працюють у HANA. FROM dummy — традиційна форма, успадкована з Oracle і часто зустрічається в legacy-коді.

Блок — це обгортка з BEGIN ... END навколо statement-ів. Може бути standalone, тілом процедури/функції або частиною контрольної конструкції:

-- Анонімний блок (виконується одразу в SQL Console)
DO BEGIN
DECLARE lv_x INTEGER = 42;
SELECT :lv_x FROM dummy;
END;
-- Тіло процедури
CREATE PROCEDURE my_proc AS
BEGIN
...
END;
-- Блок у IF
IF :lv_x > 0 THEN
BEGIN
...
END;
END IF;

Структура блоку:

  1. Декларації (DECLARE)
  2. Обробники винятків (DECLARE EXIT HANDLER)
  3. Список statement-ів

Видимість змінних — у межах поточного блоку і його вкладених блоків.

Процедура — іменована підпрограма, що зберігається в БД. Може мати будь-яку кількість IN/OUT/INOUT параметрів довільних типів, читати і змінювати дані:

CREATE PROCEDURE get_tasks_by_status (
IN iv_status INTEGER,
OUT et_tasks TABLE (id INT, title NVARCHAR(100))
) AS
BEGIN
et_tasks = SELECT id, title
FROM tasks
WHERE status = :iv_status;
END;
-- Виклик
CALL get_tasks_by_status (
iv_status => 3,
et_tasks => ? -- ? — placeholder для виводу в SQL Console
);

Дві форми залежно від типу повернення:

Table function — повертає таблицю:

CREATE FUNCTION udf_tasks_in_status (iv_status INTEGER)
RETURNS TABLE (id INT, title NVARCHAR(100))
AS BEGIN
RETURN SELECT id, title FROM tasks WHERE status = :iv_status;
END;
-- Виклик у FROM як таблиця
SELECT * FROM udf_tasks_in_status(3);

Scalar function — повертає одне значення:

CREATE FUNCTION udf_username (iv_id INTEGER)
RETURNS NVARCHAR(100)
AS BEGIN
DECLARE lv_name NVARCHAR(100);
SELECT firstname || ' ' || lastname INTO lv_name
FROM users WHERE id = :iv_id;
RETURN :lv_name;
END;
-- Виклик у виразі
SELECT udf_username(assignee) AS name FROM tasks;
КритерійProcedureTable UDFScalar UDF
К-сть OUT-параметрівБудь-яка1 (таблиця)1+ (скаляр)
Тип IN-параметрівСкаляри і таблиціСкаляри і таблиціТільки скаляри
Можна вживати як виразНіЯк таблицю в FROMЯк скаляр у SELECT, WHERE
DML-операціїТакНі (тільки читання)Ні (без table-statement-ів)
Виклик іншихБудь-якихFunctions + read-only procТільки scalar UDF

Правило вибору: якщо потрібно тільки читати і повертати значення/таблицю — UDF (зручніше використовувати у виразах). Якщо треба змінювати дані або складна логіка з кількома виходами — procedure.

⚠️ Грабля: scalar UDF — без таблиць

Scalar UDF не може містити table-statement-и. Тобто SELECT ... INTO scalar дозволено, а SELECT ... INTO TABLE — ні. Якщо в ході роботи треба проміжна таблиця — переписуй у table UDF або procedure.

Бібліотека — контейнер для повторно використовуваних процедур, функцій, змінних і констант. Створюється через CREATE LIBRARY:

CREATE LIBRARY math_utils LANGUAGE SQLSCRIPT AS
BEGIN
PUBLIC CONSTANT pi DECIMAL(10,8) = 3.14159265;
PUBLIC FUNCTION square (iv_x DECIMAL)
RETURNS DECIMAL(20,4) AS
BEGIN
RETURN :iv_x * :iv_x;
END;
END;
-- Використання — з префіксом імені бібліотеки
DO BEGIN
USING math_utils AS m;
SELECT m:square(5), m:pi FROM dummy;
END;

Видимість — PUBLIC (доступно зовні) або PRIVATE (тільки всередині бібліотеки). Бібліотеки доречні для DRY: спільні утиліти, константи, повторювані обчислення.

Анонімні блоки vs логічні контейнери

Section titled “Анонімні блоки vs логічні контейнери”
Анонімний блок (DO BEGIN ... END)Procedure / Function / Library
Виконується одразу в SQL ConsoleЗберігається в БД як обʼєкт
Без іменіМає імʼя і schema
Не можна викликати ззовніВикликається з ABAP, інших procedure, SQL Console
Зручно для разових тестівДля production-логіки