2

我正在重写一个旧 API,我试图使用节点模块mssql一次将多个值插入到 MSSQL-Server (2008) 数据库中。现在,我能够以某种方式做到这一点,但我想遵循最佳实践。我已经完成了我的研究并尝试了很多事情来实现我的目标。但是,我无法找到一个正确的解决方案。

你可能想知道:

好吧,你正在重写这个 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);
});

我会说,这是一个很好的猜测,实际上工作相对良好。除了这部分,它忽略了第一个项目之后的每个项目......这使得这非常没用。所以,我试了...

请求.multiple = true

...我想,它会完成这项工作。但是 - 令人惊讶的是 - 它没有,仍然只插入第一个项目。

使用 '?' 对于参数

此时我才真正开始寻找解决方案,因为第二个只是在模块文档中快速搜索。我偶然发现了这个答案并立即尝试了。没过多久,我的终端就吐出了一个

RequestError:“?”附近的语法不正确。

这么多。

批量插入

一些进一步的研究导致批量插入。非常有趣,很酷的功能以及OP解决方案对问题的出色更新!我从这里开始有些挣扎,但最终它看起来非常好:插入了多条记录,并且值似乎没问题。

直到我添加了子查询。将它用作声明的列的值不会导致任何错误,但是在检查表的值时,它只是显示一个

0
作为该列的值。一点也不意外,但每个人都可以梦想,对吧?

懒惰的方式

我真的不知道该怎么想:

// 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 构造移到其中并再次尝试。Aaand 在我的终端中弹出下一个错误:

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

如果你走到这一步,我不需要告诉你问题是什么。

概括

我还没有尝试(我不认为我会尝试这个)是使用事务方式并按顺序调用语句。我不相信这是要走的路。

而且我也不认为应该使用惰性方式,因为它对要插入的每条记录使用单个请求,而这可以通过某种方式仅使用一个请求来完成。只是这不知怎的,我不知道,现在不在我的脑海里。所以,如果你有什么可以帮助我的,请告诉我。

另外,如果您发现我的代码有任何其他问题,请随时指出。我不认为自己是初学者,但我也不认为学习会永远结束。:)

4

1 回答 1

0

我解决这个问题的方法是使用具有并发 1 的 PQueue 库。由于一个并发,它很慢,但它适用于数千个查询:

    const transaction = sql.transaction();
    const request = transaction.request();
    const queue = new PQueue({ concurrency: 1 });

    // being transaction
    await transaction.begin();

    for (const query of queries) {
      queue.add(async () => {
        try {
          await request.query(query);
        } catch (err) {
          // stop pending transactions
          await queue.clear();
          await queue.onIdle();
          // rollback transaction
          await transaction.rollback();
          // throw error
          throw err;
        }
      });
    }
    // await queue
    await queue.onIdle();
    // comit transaction
    await transaction.commit();
于 2022-02-08T18:50:38.377 回答