Вставьте несколько записей в SQL Server с помощью Node.js

Я переписываю старый API, для которого пытаюсь вставить сразу несколько значений в базу данных MSSQL-Server (2008), используя модуль узла mssql. Теперь я могу сделать это каким-то образом, но я хочу использовать рекомендации. Я провел исследование и попробовал много вещей, чтобы достичь своей цели. Однако мне не удалось найти ни одного решения, которое работало бы правильно.

До

Вы можете задаться вопросом:

Итак, вы переписываете этот API, значит, должен быть способ, которым это уже делалось раньше и который работал?

Конечно, вы правы, раньше это работало, но... не так, как мне было бы удобно использовать в переписывании. Позвольте мне показать вам, как это было сделано раньше (конечно, добавлено немного абстракции):

const request = new sql.Request(connection);
let query = "INSERT INTO tbl (col1, col2, col3, col4) VALUES ";
for (/*basic for loop w/ counter variable i*/) {
    query += "(1, @col2" + [i] + ", @col3" + [i] + ", (SELECT x FROM y WHERE z = @someParam" + [i] + "))";
    // a check whether to add a comma or not
    request.input("col2" + [i], sql.Int(), values[i]);
    // ...
}
request.query(query, function(err, recordset) {
    // ...
}

Хотя это работает, опять же, я не думаю, что это можно назвать чем-то вроде "лучшей практики". Также это показывает самую большую проблему: для вставки значения используется подвыборка.

Что я пробовал до сих пор

Простой способ

Сначала я попробовал, наверное, самое простое:

// simplified
const sQuery = "INSERT INTO tbl (col1, col2, col3, col4) VALUES (1, @col2, @col3, (SELECT x FROM y WHERE z = @col4));";
oPool.request().then(oRequest => {
    return oRequest
        .input("col2", sql.Int(), aValues.map(oValue => oValue.col2))
        .input("col3", sql.Int(), aValues.map(oValue => oValue.col3))
        .input("col4", sql.Int(), aValues.map(oValue => oValue.col4))
        .query(sQuery);
});

Я бы сказал, что это было довольно хорошее предположение и на самом деле работало относительно хорошо. За исключением той части, которая игнорирует каждый элемент после первого... что делает его довольно бесполезным. Итак, я попытался...

Request.multiple = истина

... и я подумал, что это сработает. Но - сюрприз - это не так, все равно вставляется только первый элемент.

С использованием '?' для параметров

В этот момент я действительно начал поиск решения, так как вторым был только беглый поиск в документации модулей. Я наткнулся на этот ответ и сразу попробовал. Моему терминалу не потребовалось много времени, чтобы выплюнуть

RequestError: Неправильный синтаксис рядом с '?'.

Так много для этого.

Массовая вставка

Некоторые дальнейшие исследования привели к массовой вставке. Довольно интересная, крутая функция и отличное обновление вопроса с решением ОП! У меня были некоторые трудности с началом работы, но в итоге все выглядело очень хорошо: было вставлено несколько записей, и значения казались нормальными.

Until I added the subquery. Using it as value for a column declared didn't cause any error, however when checking the values of the table, it simply displayed a

0
as value for this column. Not a big surprise at all, but everybody can dream, right?

Ленивый способ

Я действительно не знаю, что думать об этом:

// simplified
Promise.all(aValues.map(oValue => {
    return oPool.request().then(oRequest => 
        oRequest
            .input("col2", sql.Int, oValue.col2)
            .input("col3", sql.Int, oValue.col3)
            .input("col4", sql.Int, oValue.col4)
            .query(sQuery);
    });
});

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

Ленивый + транзакция

Поскольку основной проблемой последнего метода было продолжение, даже если какой-то сбой, я попытался построить транзакцию вокруг него. Все запросы успешны? Хорошо, зафиксируй. В любом запросе есть ошибка? Ну просто откатить чем. Итак, я создал транзакцию, переместил в нее свою конструкцию Promise.all и попробовал еще раз. И в моем терминале появляется следующая ошибка:

TransactionError: Can't acquire connection for the request. There is another request in progress.

Если вы зашли так далеко, мне не нужно говорить вам, в чем проблема.

Резюме

Чего я еще не пробовал (и не думаю, что буду пробовать), так это использования транзакционного способа и последовательного вызова операторов. Я не верю, что это правильный путь.

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

Кроме того, если вы видите что-то еще, что не так с моим кодом, не стесняйтесь указывать на это. Я не считаю себя новичком, но я также не думаю, что обучение когда-либо закончится. :)


person marrem97    schedule 10.12.2018    source источник
comment
Итак, я понимаю, что запрос произвольный, но примерно сколько вставок вы ожидаете выполнить?   -  person James    schedule 11.12.2018
comment
@ Джеймс от 1 до ~ нескольких сотен   -  person marrem97    schedule 11.12.2018
comment
@ marrem97 Удалось ли вам найти решение этого вопроса? У меня такая же проблема. Спасибо   -  person newbie    schedule 24.01.2021
comment
@newbie, к сожалению, не совсем так. В итоге я использовал то, что я назвал ленивым способом с Promise.all для всех данных записей. Я не работал над этим проектом намного дольше, поэтому я действительно не исследовал его намного дольше.   -  person marrem97    schedule 26.01.2021