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

CRUD у SQLScript: запис у БД

Шість statement-ів змінюють дані в HANA: INSERT, UPDATE, UPSERT (= REPLACE), MERGE INTO, DELETE, TRUNCATE TABLE. Кожен має кілька форм — для одного рядка, для пакету, для запису з підзапиту, з умовою або без. Ця сторінка — швидкий лук-ап усіх форм з пастками, які ловлять у production.

Після кожного DML-statement-у:

  • ::ROWCOUNT — кількість змінених рядків
  • SQL_ERROR_CODE — код помилки (доступна в EXIT HANDLER)
-- Усі колонки за порядком оголошення
INSERT INTO tab_1 VALUES (1, 1, 'First row');
INSERT INTO tab_1 VALUES (1, 2, 'Date: ' || CURRENT_DATE);
-- Окремий список колонок — інший порядок або частковий набір
INSERT INTO tab_1 (key1, value1) VALUES (5, 'No key2 — буде NULL');
-- Список VALUES
INSERT INTO tab_1 VALUES (1, 'A'), (2, 'B'), (3, 'C');
-- З SELECT-підзапиту
INSERT INTO tab_archive
SELECT * FROM tab_active WHERE status = 'CLOSED';
-- З table variable
DO BEGIN
lt_data = SELECT * FROM source_tab;
INSERT INTO target_tab SELECT * FROM :lt_data;
END;

⚠️ Грабля: INSERT не оновлює

INSERT падає на duplicate primary key. Якщо хочеш upsert-семантику (вставити або оновити) — використовуй UPSERT або MERGE INTO. Не лови виняток unique constraint violated як спосіб реалізації upsert — це антипатерн, який вбиває продуктивність на великих обʼємах.

UPDATE tab_2
SET value1 = 'Updated',
value2 = 'Line ' || key1 -- вираз, що використовує існуюче значення
WHERE key1 < 300;

SET приймає будь-який вираз — на основі тих самих або інших колонок рядка. WHERE визначає, які рядки зачіпає.

UPDATE з посиланням на іншу таблицю

Section titled “UPDATE з посиланням на іншу таблицю”

Дві форми синтаксису, та сама семантика — оновити одну таблицю значеннями з іншої:

-- Форма 1: UPDATE FROM (Postgres-стиль)
UPDATE tab_2 t2
SET t2.value1 = t1.value1
FROM tab_2 AS t2,
tab_1 AS t1
WHERE t2.key1 = t1.key1 + t1.key2 * 100;

⚠️ Грабля: SAP рекомендує MERGE замість UPDATE FROM

SAP-нота 2241598 явно радить використовувати MERGE INTO замість UPDATE ... FROM — синтаксис чіткіший і поведінка прогнозованіша. На production-коді користуйся MERGE.

UPSERT і REPLACE — синоніми (повністю однакові statement-и). Семантика: вставити, якщо нема такого ключа; оновити, якщо є.

-- Якщо є рядок з key1=1 → оновлюється; якщо нема → вставляється
UPSERT tab_1 VALUES (1, 1, 'Inserted with UPSERT')
WITH PRIMARY KEY;
UPSERT tab_1 VALUES (1, 2, 'Inserted with UPSERT')
WITH PRIMARY KEY;
-- Тепер update — той самий ключ
UPSERT tab_1 VALUES (1, 2, 'Changed with UPSERT')
WITH PRIMARY KEY;

WITH PRIMARY KEY обовʼязкове — без нього UPSERT не знає, як ідентифікувати рядок.

З column-list (рекомендований варіант)

Section titled “З column-list (рекомендований варіант)”
-- Тільки оновлювати/вставляти ці колонки. Решта — NULL/DEFAULT при INSERT
UPSERT tab_1 (key1, key2, value1)
VALUES (1, 2, 'Updated value1')
WITH PRIMARY KEY;

Column-list робить код стійким до змін структури таблиці.

UPSERT tab_1 (key1, key2, value1)
VALUES (1, 2, 'X')
WHERE key1 = 1 AND key2 = 2; -- WHERE мусить покрити повний primary key

Масовий UPSERT з підзапиту

Section titled “Масовий UPSERT з підзапиту”
-- Зсуваємо ключі на 10 і вставляємо/оновлюємо
UPSERT tab_1
SELECT key1 + 10, key2, 'Inserted by subquery'
FROM tab_1
WITH PRIMARY KEY;
-- З column-list
UPSERT tab_1 (key1, key2, value1)
SELECT key1 + 10, key2, 'Updated by subquery'
FROM tab_1
WHERE key2 = 2
WITH PRIMARY KEY;

⚠️ Грабля: UPSERT vs ABAP MODIFY

ABAP-statement MODIFY dbtab FROM TABLE @itab всередині HANA транслюється саме в UPSERT. Тобто semantics збігається. Але MODIFY itab (на internal table) — це просто оновлення в памʼяті, без відношення до БД. Не плутати.

Найпотужніша форма upsert — модифікація однієї таблиці на основі іншого джерела з декларативними правилами WHEN MATCHED / WHEN NOT MATCHED:

MERGE INTO tab_1
USING tab_2 AS t2
ON (tab_1.key1 = t2.key1)
WHEN MATCHED THEN
UPDATE SET tab_1.key1 = t2.key1,
tab_1.key2 = 1,
tab_1.value1 = 'UPDATE: ' || tab_1.value1
WHEN NOT MATCHED THEN
INSERT VALUES (t2.key1, 1, 'INSERT: ' || t2.value2);

Будь-яка з гілок (WHEN MATCHED або WHEN NOT MATCHED) — опціональна. Якщо лишити одну, statement зводиться до UPDATE або INSERT відповідно.

MERGE з table variable як джерелом

Section titled “MERGE з table variable як джерелом”

USING приймає назву таблиці — не SELECT-підзапит. Але можна підставити table variable:

DO BEGIN
lt_tmp = SELECT * FROM tab_2;
MERGE INTO tab_1
USING :lt_tmp AS t2
ON (tab_1.key1 = t2.key1)
WHEN MATCHED THEN
UPDATE SET tab_1.value1 = t2.value1
WHEN NOT MATCHED THEN
INSERT VALUES (t2.key1, 1, t2.value2);
END;
КритерійUPSERTMERGE INTO
Джерело — VALUES
Джерело — SELECT✅ (один рядок або підзапит)✅ (тільки named source)
Різна логіка для INSERT і UPDATE
Можна тільки оновлювати, не вставляти✅ (WHEN MATCHED без WHEN NOT MATCHED)
ЧитабельністьКомпактний синтаксисЯвні гілки

Правило: для простого “вставити або оновити з тими самими значеннями” — UPSERT. Для складнішої логіки (різні правила для existing / new рядків) — MERGE.

-- З WHERE — тільки відповідні рядки
DELETE FROM tab_1 WHERE key1 < 100;
-- Без WHERE — усі рядки (але метадані таблиці зберігаються)
DELETE FROM tab_1;

Можна посилатися на іншу таблицю через підзапит:

DELETE FROM tab_1
WHERE key1 IN (
SELECT key1 FROM tab_archive WHERE archived_at < ADD_YEARS(CURRENT_DATE, -3)
);

⚠️ Грабля: DELETE без WHERE

Класичний косяк — забути WHERE. Завжди перевіряй: спочатку SELECT COUNT(*) FROM ... WHERE ... зі своїм фільтром, тоді той самий WHERE у DELETE. На дев-системі в анонімному блоці — оточуй DML транзакцією з ROLLBACK до тих пір, поки не впевнений.

Швидке видалення всіх рядків таблиці:

TRUNCATE TABLE tab_1;

Відмінності від DELETE FROM tab_1:

КритерійDELETE FROMTRUNCATE TABLE
WHERE✅ дозволено❌ заборонено
ШвидкістьЗалежить від обʼємуМайже миттєво
ТранзакціяТак, можна ROLLBACKImplicit commit — не можна відкотити
Тригери BEFORE DELETEСпрацьовуютьНе спрацьовують
::ROWCOUNT післяК-сть видаленихНе визначено

⚠️ Грабля: TRUNCATE — це DDL

Через implicit commit TRUNCATE не годиться у звичайному application-коді з транзакціями. Використовуй для швидкого очищення staging-таблиць у ETL, тестових сценаріях. У production-bulk-операціях — ні.

Decтрукція даних в table variable працює по-іншому — це не SQL-таблиця, а SQLScript-конструкція:

DO BEGIN
DECLARE lt_t TABLE (id INT, val NVARCHAR(50));
-- Можна assign-ити повністю
lt_t = SELECT 1 AS id, 'A' AS val FROM dummy
UNION ALL
SELECT 2, 'B' FROM dummy;
-- INSERT/UPDATE/DELETE — теж працюють
INSERT INTO :lt_t VALUES (3, 'C');
UPDATE :lt_t SET val = 'X' WHERE id = 1;
DELETE FROM :lt_t WHERE id = 2;
SELECT * FROM :lt_t;
END;

Зверни увагу — у DML на table variable префікс : обовʼязковий і перед імʼям, не як у читанні.

StatementПризначенняMass / singleImplicit commit
INSERTВставка новихОбидваНі
UPDATEОновлення існуючихMass через WHEREНі
UPSERT / REPLACEINSERT-or-UPDATE по PKОбидваНі
MERGE INTOINSERT-and-UPDATE з джерелом-таблицеюMassНі
DELETEВидалення з WHEREMass через WHEREНі
TRUNCATE TABLEВидалення всьогоMassТак