Сохранение данных с использованием JSON
Эта статья описывает, как сохранять данные с динамическими колонками, используя хранимые процедуры с JSON-параметрами.
Поддерживаются пять встроенных параметров:
- @json_values_f1(или- @JsonValuesF1)
- @json_values_f2(или- @JsonValuesF2)
- @json_changes_f1(или- @JsonChangesF1)
- @json_changes_f2(или- @JsonChangesF2)
- @json_changes_f3(или- @JsonChangesF3)
@json_values_f1 и @json_values_f2 получают все значения строки: @json_values_f1 как массив и @json_values_f2 как объект.
@json_changes_f1 и @json_changes_f2 фиксируют все изменения: @json_changes_f1 как массивы и @json_changes_f2 как объекты.
@json_changes_f3 аналогичен @json_changes_f2, но всегда возвращает полный набор, включая колонки с пустыми значениями.
"f1", "f2" и "f3" — это индексы формата.
Также полезны следующие встроенные параметры:
- @json_columns(или- @JsonColumns) — получает актуальный набор имен колонок.
- @table_name(или- @TableName) — получает имя объекта запроса.
- @edit_action(или- @EditAction) — получает значения "INSERT", "UPDATE", "DELETE" или "MERGE" в зависимости от операции.
Настройка
Существует три подхода к привязке процедур сохранения изменений:
- Использование таблицы xls.objects
- Использование представления списка запросов
- Использование суффиксов
Ниже приведен пример настройки в таблице xls.objects:
| ID | TABLE_SCHEMA | TABLE_NAME | TABLE_TYPE | TABLE_CODE | INSERT_OBJECT | UPDATE_OBJECT | DELETE_OBJECT | 
|---|---|---|---|---|---|---|---|
| s02 | view_cashbook | VIEW | s02.view_cashbook_insert | s02.view_cashbook_update | s02.view_cashbook_delete | ||
| s02 | usp_cashbook | PROCEDURE | s02.usp_cashbook_update | 
Пример настройки для представления списка запросов:
| ID | TABLE_SCHEMA | TABLE_NAME | TABLE_TYPE | TABLE_CODE | INSERT_PROCEDURE | UPDATE_PROCEDURE | DELETE_PROCEDURE | PROCEDURE_TYPE | 
|---|---|---|---|---|---|---|---|---|
| s02 | view_cashbook | VIEW | s02.view_cashbook_insert | s02.view_cashbook_update | s02.view_cashbook_delete | |||
| s02 | usp_cashbook | PROCEDURE | s02.usp_cashbook_update | 
Эти настройки не обязательны, так как продукты SaveToDB автоматически привязывают процедуры по суффиксам _insert, _update и _delete.
Таким образом, вы можете использовать эту технику даже без установленного SaveToDB Framework.
Вторые строки конфигурации используют единственную процедуру UPDATE. Используйте этот подход для процедур с параметрами json_changes, или когда для всех операций используется одна и та же процедура (см. ниже).
Детали реализации
SaveToDB и DBEdit выполняют заданные процедуры, передавая JSON в параметры.
DBGate и ODataDB выполняют процедуры на стороне сервера, формируя JSON для параметров на основе команд POST, PUT, DELETE.
Примеры
Ниже представлены детальные примеры для каждого случая.
- Пример для @json_values_f1
- Пример для @json_values_f2
- Использование универсальной процедуры обновления строк
- Пример для @json_changes_f1
- Пример для @json_changes_f2
- Использование универсальной процедуры обновления таблиц
Пример для @json_values_f1
Пример команд для процедур insert, update и delete:
EXEC [s24].[view_cashbook_json_values_f1_delete] @id = 21;
EXEC [s24].[view_cashbook_json_values_f1_update]
    @json_columns = N'["id","date","account_id","item_id","company_id","debit","credit"]'
    , @json_values_f1 = N'[1,"2022-01-10",1,1,1,200000,null]';
EXEC [s24].[view_cashbook_json_values_f1_insert]
    @json_columns = N'["id","date","account_id","item_id","company_id","debit","credit"]'
    , @json_values_f1 = N'[21,"2022-03-31",1,2,8,null,100000]';
Пример хранимых процедур Microsoft SQL Server, которые обновляют таблицу, используя значения JSON.
Обратите внимание, что процедура delete использует обычный параметр @id.
CREATE PROCEDURE [s24].[view_cashbook_json_values_f1_insert]
    @json_columns nvarchar(max)
    , @json_values_f1 nvarchar(max)
AS
BEGIN
SET @json_values_f1 = '[' + @json_values_f1 + ']'    -- Fix for OPENJSON top-level array
INSERT INTO s24.cashbook
    ([date], account_id, item_id, company_id, debit, credit)
SELECT
    t2.[date], t2.account_id, t2.item_id, t2.company_id, t2.debit, t2.credit
FROM
    OPENJSON(@json_values_f1) WITH (
        [id] int '$[0]'
        , [date] date '$[1]'
        , [account_id] int '$[2]'
        , [item_id] int '$[3]'
        , [company_id] int '$[4]'
        , [debit] float '$[5]'
        , [credit] float '$[6]'
    ) t2;
END
GO
CREATE PROCEDURE [s24].[view_cashbook_json_values_f1_update]
    @json_columns nvarchar(max)
    , @json_values_f1 nvarchar(max)
AS
BEGIN
SET @json_values_f1 = '[' + @json_values_f1 + ']'  -- Fix for OPENJSON top-level array
UPDATE s24.cashbook
SET
    [date] = t2.date
    , account_id = t2.account_id
    , item_id = t2.item_id
    , company_id = t2.company_id
    , debit = t2.debit
    , credit = t2.credit
FROM
    s24.cashbook t
    INNER JOIN OPENJSON(@json_values_f1) WITH (
        [id] int '$[0]'
        , [date] date '$[1]'
        , [account_id] int '$[2]'
        , [item_id] int '$[3]'
        , [company_id] int '$[4]'
        , [debit] float '$[5]'
        , [credit] float '$[6]'
    ) t2 ON t2.id = t.id
END
GO
CREATE PROCEDURE [s24].[view_cashbook_json_values_f1_delete]
    @id int = NULL
AS
BEGIN
DELETE FROM s24.json_test WHERE id = @id
END
GO
Пример для @json_values_f2
Пример команд для процедур insert, update и delete:
EXEC [s24].[view_cashbook_json_values_f2_delete] @id = 21;
EXEC [s24].[view_cashbook_json_values_f2_update]
    @json_values_f2 = N'{"id":1,"date":"2022-01-10","account_id":1,"item_id":1,"company_id":1,"debit":200000,"credit":null}';
EXEC [s24].[view_cashbook_json_values_f2_insert]
    @json_values_f2 = N'{"id":21,"date":"2022-03-31","account_id":1,"item_id":2,"company_id":8,"debit":null,"credit":100000}';
В отличие от @json_values_f1, @json_values_f2 получает значения в виде объектов.
Пример хранимых процедур Microsoft SQL Server, которые обновляют таблицу, используя значения JSON.
CREATE PROCEDURE [s24].[view_cashbook_json_values_f2_insert]
    @json_values_f2 nvarchar(max)
AS
BEGIN
INSERT INTO s24.cashbook
    ([date], account_id, item_id, company_id, debit, credit)
SELECT
    t2.[date], t2.account_id, t2.item_id, t2.company_id, t2.debit, t2.credit
FROM
    OPENJSON(@json_values_f2) WITH (
        [id] int '$."id"'
        , [date] date '$."date"'
        , [account_id] int '$."account_id"'
        , [item_id] int '$."item_id"'
        , [company_id] int '$."company_id"'
        , [debit] float '$."debit"'
        , [credit] float '$."credit"'
    ) t2
END
GO
CREATE PROCEDURE [s24].[view_cashbook_json_values_f2_update]
    @json_values_f2 nvarchar(max)
AS
BEGIN
UPDATE s24.cashbook
SET
    [date] = t2.date
    , account_id = t2.account_id
    , item_id = t2.item_id
    , company_id = t2.company_id
    , debit = t2.debit
    , credit = t2.credit
FROM
    s24.cashbook t
    INNER JOIN OPENJSON(@json_values_f2) WITH (
        [id] int '$."id"'
        , [date] date '$."date"'
        , [account_id] int '$."account_id"'
        , [item_id] int '$."item_id"'
        , [company_id] int '$."company_id"'
        , [debit] float '$."debit"'
        , [credit] float '$."credit"'
    ) t2 ON t2.id = t.id
END
GO
CREATE PROCEDURE [s24].[view_cashbook_json_values_f2_delete]
    @id int = NULL
AS
BEGIN
DELETE FROM s24.cashbook WHERE id = @id
END
GO
Использование универсальной процедуры обновления строк
Примеры выше демонстрируют простой случай, когда структура обновляемой таблицы известна.
Так как параметр @json_values_f2 получает значения для всех колонок, можно создать полностью универсальную процедуру для сохранения изменений.
По ссылке приведен пример такой процедуры для Microsoft SQL Server:
https://dbgate.savetodb.com/api/mssql-241/en-us/s24.xl_update_generic_row/$definition
В дополнение к значениям строки, процедура имеет параметры целевой таблицы и действия.
Для вызова процедуры можно создать процедуру-обертку или SQL-код и использовать их в поле UPDATE_OBJECT таблицы конфигурации.
CREATE PROCEDURE [s24].[view_cashbook_json_generic_row_update]
    @id int = NULL
    , @table_name nvarchar(255) = NULL
    , @edit_action nvarchar(6) = NULL
    , @json_values_f2 nvarchar(max) = NULL
AS
BEGIN
EXEC s24.xl_update_generic_row @id, '[s24].[cashbook]', @edit_action, @json_values_f2
END
Так как используется единая процедура для операций вставки, обновления и удаления, следует указать процедуру только в поле UPDATE_OBJECT.
Примеры генерируемых команд:
EXEC [s24].[view_cashbook_json_generic_row_update]
    @id = 21
    , @table_name = N'[s24].[view_cashbook_json_generic_row]'
    , @edit_action = N'DELETE'
    , @json_values_f2 = N'{"id":21,"date":"2022-03-31","account_id":1,"item_id":2,"company_id":8,"debit":null,"credit":100000}';
EXEC [s24].[view_cashbook_json_generic_row_update]
    @id = 1
    , @table_name = N'[s24].[view_cashbook_json_generic_row]'
    , @edit_action = N'UPDATE'
    , @json_values_f2 = N'{"id":1,"date":"2022-01-10","account_id":1,"item_id":1,"company_id":1,"debit":200000,"credit":null}';
EXEC [s24].[view_cashbook_json_generic_row_update]
    @id = 21
    , @table_name = N'[s24].[view_cashbook_json_generic_row]'
    , @edit_action = N'INSERT'
    , @json_values_f2 = N'{"id":21,"date":"2022-03-31","account_id":1,"item_id":2,"company_id":8,"debit":null,"credit":100000}';
Обратите внимание, что все процедуры получают имя представления, которое используется для получения данных, а не целевой таблицы.
Именно поэтому требуется использование процедуры-обертки или SQL-кода, чтобы вызвать универсальную процедуру с правильным именем целевой таблицы.
Пример для @json_changes_f1
Пример команд сохранения изменений для процедуры с параметром @json_changes_f1:
EXEC [s24].[view_cashbook_json_changes_f1_update]
    @id = NULL
    , @json_changes_f1 = N'{
        "table_name":"[s24].[view_cashbook_json_changes_f1]",
        "actions":{
            "insert":{
                "action":"insert"
                , "columns":["id","date","account_id","item_id","company_id","debit","credit"]
                , "rows":[[21,"2022-03-31",1,2,8,null,100000]]
                }
            ,"update":{
                "action":"update"
                , "columns":["id","date","account_id","item_id","company_id","debit","credit"]
                , "rows":[[1,"2022-01-10",1,1,1,200000,null]]
                }
            ,"delete":{
                "action":"delete"
                , "columns":["id"]
                , "rows":[[21]]
                }
            }
        }';
SaveToDB и DBEdit генерируют единственную команду для всех изменений и выполняют единственный вызов процедуры.
Это значительно увеличивает общую производительность.
DBGate и ODataDB для таких объектов создают обычный API, получают стандартные запросы POST, PUT и DELETE в едином пакетном запросе и выполняют процедуру с параметрами JSON на стороне сервера.
Пример процедуры SQL Server, которая использует JSON-массивы значений для обновления таблицы:
CREATE PROCEDURE [s24].[view_cashbook_json_changes_f1_update]
    @id int = NULL
    , @json_changes_f1 nvarchar(max) = NULL
AS
BEGIN
SET NOCOUNT ON
DECLARE @insert nvarchar(max),  @update nvarchar(max), @delete nvarchar(max)
SELECT
    @insert = t2.[insert]
    , @update = t2.[update]
    , @delete = t2.[delete]
FROM
    OPENJSON(@json_changes_f1) WITH (
        actions nvarchar(max) AS json
    ) t1
    CROSS APPLY OPENJSON(t1.actions) WITH (
        [insert] nvarchar(max) '$.insert' AS json
        , [update] nvarchar(max) '$.update' AS json
        , [delete] nvarchar(max) '$.delete' AS json
    ) t2
IF @insert IS NOT NULL
INSERT INTO s24.cashbook
    ([date], account_id, item_id, company_id, debit, credit)
SELECT
    t2.[date], t2.account_id, t2.item_id, t2.company_id, t2.debit, t2.credit
FROM
    OPENJSON(@insert) WITH (
        [rows] nvarchar(max) '$.rows' AS json
    ) t1
    CROSS APPLY OPENJSON(t1.[rows]) WITH (
        [id] int '$[0]'
        , [date] date '$[1]'
        , [account_id] int '$[2]'
        , [item_id] int '$[3]'
        , [company_id] int '$[4]'
        , [debit] float '$[5]'
        , [credit] float '$[6]'
    ) t2;
IF @update IS NOT NULL
UPDATE s24.cashbook
SET
    [date] = t2.[date]
    , account_id = t2.account_id
    , item_id = t2.item_id
    , company_id = t2.company_id
    , debit = t2.debit
    , credit = t2.credit
FROM
    s24.cashbook t
    INNER JOIN (
        SELECT
            t2.id AS id
            , t2.[date] AS [date]
            , t2.account_id AS account_id
            , t2.item_id AS item_id
            , t2.company_id AS company_id
            , t2.debit AS debit
            , t2.credit AS credit
        FROM
            OPENJSON(@update) WITH (
                [rows] nvarchar(max) '$.rows' AS json
            ) t1
            CROSS APPLY OPENJSON(t1.[rows]) WITH (
                [id] int '$[0]'
                , [date] date '$[1]'
                , [account_id] int '$[2]'
                , [item_id] int '$[3]'
                , [company_id] int '$[4]'
                , [debit] float '$[5]'
                , [credit] float '$[6]'
            ) t2
    ) t2 ON t2.id = t.id
IF @delete IS NOT NULL
DELETE FROM s24.cashbook
FROM
    s24.cashbook t
    INNER JOIN (
        SELECT
            t2.[id] AS [id]
        FROM
            OPENJSON(@delete) WITH (
                [rows] nvarchar(max) '$.rows' AS json
            ) t1
            CROSS APPLY OPENJSON(t1.[rows]) WITH (
                [id] int '$[0]'
            ) t2
    ) t2 ON t2.[id] = t.[id]
END
Пример для @json_changes_f2
Пример команд для процедуры с параметром @json_changes_f2:
EXEC [s24].[view_cashbook_json_changes_f2_update]
     @id = NULL
     , @json_changes_f2 = N'{
        "table_name":"[s24].[view_cashbook_json_changes_f2]"
        , "actions":{
            "insert":{
                "action":"insert"
                , "columns":["id","date","account_id","item_id","company_id","debit","credit"]
                , "rows":[{"id":21,"date":"2022-03-31","account_id":1,"item_id":2,"company_id":8,"debit":null,"credit":100000}]
                }
            ,"update":{
                "action":"update"
                , "columns":["id","date","account_id","item_id","company_id","debit","credit"],
                , "rows":[{"id":1,"date":"2022-01-10","account_id":1,"item_id":1,"company_id":1,"debit":200000,"credit":null}]
                }
            , "delete":{
                "action":"delete"
                , "columns":["id"]
                , "rows":[{"id":21}]
                }
            }
        }';
В отличие от @json_changes_f1, @json_changes_f2 получает значения в виде объектов.
Пример хранимой процедуры SQL Server, которая использует значения объектов JSON для обновления таблицы:
CREATE PROCEDURE [s24].[view_cashbook_json_changes_f2_update]
    @id int = NULL
    , @json_changes_f2 nvarchar(max) = NULL
AS
BEGIN
SET NOCOUNT ON
DECLARE @insert nvarchar(max),  @update nvarchar(max), @delete nvarchar(max)
SELECT
    @insert = t2.[insert]
    , @update = t2.[update]
    , @delete = t2.[delete]
FROM
    OPENJSON(@json_changes_f2) WITH (
        actions nvarchar(max) AS json
    ) t1
    CROSS APPLY OPENJSON(t1.actions) WITH (
        [insert] nvarchar(max) '$.insert' AS json
        , [update] nvarchar(max) '$.update' AS json
        , [delete] nvarchar(max) '$.delete' AS json
    ) t2
IF @insert IS NOT NULL
INSERT INTO s24.cashbook
    ([date], account_id, item_id, company_id, debit, credit)
SELECT
    t2.[date], t2.account_id, t2.item_id, t2.company_id, t2.debit, t2.credit
FROM
    OPENJSON(@insert) WITH (
        [rows] nvarchar(max) '$.rows' AS json
    ) t1
    CROSS APPLY OPENJSON(t1.[rows]) WITH (
        [id] int '$."id"'
        , [date] date '$."date"'
        , [account_id] int '$."account_id"'
        , [item_id] int '$."item_id"'
        , [company_id] int '$."company_id"'
        , [debit] float '$."debit"'
        , [credit] float '$."credit"'
    ) t2;
IF @update IS NOT NULL
UPDATE s24.cashbook
SET
    [date] = t2.[date]
    , account_id = t2.account_id
    , item_id = t2.item_id
    , company_id = t2.company_id
    , debit = t2.debit
    , credit = t2.credit
FROM
    s24.cashbook t
    INNER JOIN (
        SELECT
            t2.id AS id
            , t2.[date] AS [date]
            , t2.account_id AS account_id
            , t2.item_id AS item_id
            , t2.company_id AS company_id
            , t2.debit AS debit
            , t2.credit AS credit
        FROM
            OPENJSON(@update) WITH (
                [rows] nvarchar(max) '$.rows' AS json
            ) t1
            CROSS APPLY OPENJSON(t1.[rows]) WITH (
                [id] int '$."id"'
                , [date] date '$."date"'
                , [account_id] int '$."account_id"'
                , [item_id] int '$."item_id"'
                , [company_id] int '$."company_id"'
                , [debit] float '$."debit"'
                , [credit] float '$."credit"'
            ) t2
    ) t2 ON t2.id = t.id;
IF @delete IS NOT NULL
DELETE FROM s24.cashbook
FROM
    s24.cashbook t
    INNER JOIN (
        SELECT
            t2.[id] AS [id]
        FROM
            OPENJSON(@delete) WITH (
                [rows] nvarchar(max) '$.rows' AS json
            ) t1
            CROSS APPLY OPENJSON(t1.[rows]) WITH (
                [id] int '$."id"'
            ) t2
    ) t2 ON t2.id = t.id
END
Использование универсальной процедуры обновления таблиц
Примеры выше можно использовать, если структура таблицы данных известна.
Можно создать полностью универсальную процедуру для сохранения изменений в таблицу.
По ссылке приведен пример такой процедуры для Microsoft SQL Server:
https://dbgate.savetodb.com/api/mssql-241/en-us/s24.xl_update_generic_table/$definition
В дополнение к данным изменений, процедура получает имя целевой таблицы.
Для процедуры сохранения изменений необходимо определить параметры для полей первичного ключа, которые передаются для операций удаления.
Для вызова таких процедур можно создать процедуру-обертку, представленную ниже, или использовать SQL-код в поле UPDATE_OBJECT таблицы конфигурации.
CREATE PROCEDURE [s24].[view_cashbook_json_generic_table_update]
    @id int = NULL
    , @table_name nvarchar(255) = NULL
    , @json_changes_f2 nvarchar(max) = NULL
AS
BEGIN
EXEC s24.xl_update_generic_table '[s24].[cashbook]', @json_changes_f2
END