Сохранение данных с использованием параметров JSON

Сохранение данных с использованием параметров 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_values_f2 как объекты.

@json_changes_f3 аналогичен @json_changes_f2, но всегда получает полный набор, включая колонки с пустыми значениями.

"f1", "f2" и "f3" - это просто индексы формата.

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

  • @json_columns (или @JsonColumns) получает актуальный набор имен колонок.
  • @table_name (или @TableName) получает имя объекта запроса.
  • @edit_action (или @EditAction) получает значения "INSERT", "UPDATE", "DELETE" или "MERGE" в зависимости от операции.

Настройка

Есть три подхода к привязке процедур сохранения изменений:

  1. Использование таблицы xls.objects
  2. Использование представления списка запросов
  3. Использование суффиксов

Ниже приведен пример настройки в таблице xls.objects:

IDTABLE_SCHEMATABLE_NAMETABLE_TYPETABLE_CODEINSERT_OBJECTUPDATE_OBJECTDELETE_OBJECT
 s02view_cashbookVIEWs02.view_cashbook_inserts02.view_cashbook_updates02.view_cashbook_delete
 s02usp_cashbookPROCEDUREs02.usp_cashbook_update

Ниже приведен пример настройки для представления списка запросов:

IDTABLE_SCHEMATABLE_NAMETABLE_TYPETABLE_CODEINSERT_PROCEDUREUPDATE_PROCEDUREDELETE_PROCEDUREPROCEDURE_TYPE
 s02view_cashbookVIEWs02.view_cashbook_inserts02.view_cashbook_updates02.view_cashbook_delete
 s02usp_cashbookPROCEDUREs02.usp_cashbook_update

Все настройки выше не являются обязательными, т.к. продукты SaveToDB привязывают процедуры автоматически по суффиксам _insert, _update и _delete.

Соответственно, вы можете использовать эту технику даже без установленного SaveToDB Framework.

Вторые строки конфигурации используют единственную процедуру UPDATE. Используйте этот подход для процедур с параметрами json_changes, или когда для всех операций используется одна и та же процедура (см. ниже).

Детали реализации

SaveToDB и DBEdit выполняют заданные процедуры, передавая JSON в параметры.

DBGate и ODataDB выполняют процедуры на стороне сервера, формируя JSON для параметров на основе команд POST, PUT, DELETE.

Примеры

Ниже представлены детальные примеры для каждого случая.

Пример для @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_f3 получает значения в виде объектов.

Ниже приведен пример хранимой процедуры 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