Я переписываю старый 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
0as 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.
Если вы зашли так далеко, мне не нужно говорить вам, в чем проблема.
Резюме
Чего я еще не пробовал (и не думаю, что буду пробовать), так это использования транзакционного способа и последовательного вызова операторов. Я не верю, что это правильный путь.
И я также не думаю, что следует использовать ленивый способ, поскольку он использует отдельные запросы для каждой записи для вставки, когда это можно сделать, используя только один запрос. Просто это каким-то образом, я не знаю, сейчас не в моей голове. Так что, пожалуйста, если у вас есть что-то, что могло бы мне помочь, скажите мне.
Кроме того, если вы видите что-то еще, что не так с моим кодом, не стесняйтесь указывать на это. Я не считаю себя новичком, но я также не думаю, что обучение когда-либо закончится. :)