Node-oracledb:在循环中多次插入最后一个值

Node-oracledb: Only last value is being inserted multiple times in the loop

我正在读取 XLSX 文件并将记录插入 ORACLE 数据库。 XLSX 包含以下值

H
H
H
H
JK

但是只有JK被插入了5次。下面是使用的代码

var XLSX = require('xlsx')
var workbook = XLSX.readFile('Accounts.xlsx');
var sheet_name_list = workbook.SheetNames;
var xlData = XLSX.utils.sheet_to_json(workbook.Sheets[sheet_name_list[0]]);
var connection;
var oracledb = require('oracledb');
oracledb.autoCommit = true;
var dbConfig = require(__dirname + '/dbconfig.js');
var cnt;


 oracledb.getConnection(
    dbConfig,
     function(err, connection) {
        if (err) throw err;

        for (i in xlData) 
        {

            var act_fam = xlData[i].ACCOUNT_FAMILY;



               connection.execute(
                `SELECT * FROM TFAMCORGDS_TEST WHERE MNEFAMCOR='`+ act_fam + `'`,
                function(err, results) {
                    if (err) throw err;
                    cnt = results.rows.length;

                    if (cnt === 0)
                    {
                         connection.execute(
                `INSERT INTO TFAMCORGDS_TEST (CODFAMCOR,MNEFAMCOR,DATMOD,DATFINACT) VALUES (SCORGDS.NEXTVAL,'`+ act_fam + `',SYSDATE,NULL)`,
                function(err, results) {
                    if (err) throw err;
                    console.log('Rows Inserted: ', results.rowsAffected);
                    //do work on the rows here


                }
            );
                    }
             });







        }
    }
);

而且我也无法在 connection.execute 函数之外使用变量 "cnt" 值,尽管它是全局变量。

您应该创建一个单独的异步函数来访问数据库。因为我们在进行一些数据库操作的时候是需要一些时间的。我们必须等到完成它才能处理下一个。所以我不打算把整个代码放在这里。举个例子。

async function main(){

    //send the data to database accessing function
    for(i in xlData){
       await insertData(xlData[i].ACCOUNT_FAMILY);
    }

}


async function insertData(data){

  //do the database stuff here

}

注意: 我正在使用 await 关键字来执行异步功能。

我将在不同的函数中分离任务并将它们包装在承诺中,而不是在循环中进行回调。代码看起来像这样

var XLSX = require("xlsx");
var workbook = XLSX.readFile("Accounts.xlsx");
var sheet_name_list = workbook.SheetNames;
var xlData = XLSX.utils.sheet_to_json(workbook.Sheets[sheet_name_list[0]]);
var connection;
var oracledb = require("oracledb");
oracledb.autoCommit = true;
var dbConfig = require(__dirname + "/dbconfig.js");
var cnt;

function getConnection() {
  return new Promise((resolve, reject) => {
    oracledb.getConnection(dbConfig, function(err, connection) {
      if (err) {
        reject(err);
      } 
      resolve(connection);
    });
  });
}

function get(connection, act_fam) {
  return new Promise((resolve, reject) => {
    connection.execute(`SELECT * FROM TFAMCORGDS_TEST WHERE MNEFAMCOR='` + act_fam + `'`, function(err, results) {
      if (err) {
        reject(err);
      } 
      resolve(results);
    });
  });
}

function insert(connection, act_fam) {
  return new Promise((resolve, reject) => {
    connection.execute(
      `INSERT INTO TFAMCORGDS_TEST (CODFAMCOR,MNEFAMCOR,DATMOD,DATFINACT) VALUES (SCORGDS.NEXTVAL,'` +
        act_fam +
        `',SYSDATE,NULL)`, function(err, results) {
      if (err) {
        reject(err);
      } 
      resolve(results);
    });
  });
}

async function main() {
  const connection = await getConnection();
  for (i in xlData) 
  {
      var act_fam = xlData[i].ACCOUNT_FAMILY;
      const results = await get(connection, act_fam);
      cnt = results.rows.length;
      if (cnt === 0) {
        const insertResult = await insert(connection, act_fam);
        console.log('Rows Inserted: ', insertResult.rowsAffected);
      }
  }

}

希望对您有所帮助

我喜欢 Ashish Modi 的回答,但我不认为他知道驱动程序已经支持承诺,因此他的代码可以简化。

首先,您不需要 getConnection 函数。如果您不传递回调,驱动程序的 getConnection 函数已经 returns 一个承诺,因此此函数不会添加任何内容。

get函数也有同样的问题。不需要它,因为驱动程序的 execute 方法已经支持承诺。

代码可能如下所示:

var XLSX = require("xlsx");
var workbook = XLSX.readFile("Accounts.xlsx");
var sheet_name_list = workbook.SheetNames;
var xlData = XLSX.utils.sheet_to_json(workbook.Sheets[sheet_name_list[0]]);
var connection;
var oracledb = require("oracledb");
oracledb.autoCommit = true;
var dbConfig = require(__dirname + "/dbconfig.js");

async function main() {
  const connection = await oracledb.getConnection(dbConfig);
  for (i in xlData) 
  {
      var act_fam = xlData[i].ACCOUNT_FAMILY;
      const results = await connection.execute(`SELECT * FROM TFAMCORGDS_TEST WHERE MNEFAMCOR='` + act_fam + `'`);
      var cnt = results.rows.length;
      if (cnt === 0) {
        const insertResult = await connection.execute(
      `INSERT INTO TFAMCORGDS_TEST (CODFAMCOR,MNEFAMCOR,DATMOD,DATFINACT) VALUES (SCORGDS.NEXTVAL,'` +
        act_fam +
        `',SYSDATE,NULL)`);
        console.log('Rows Inserted: ', insertResult.rowsAffected);
      }
  }

}

但是,代码仍然存在很大的问题:SQL 注入漏洞和过多的往返。

代码当前使用字符串连接将值传递给 SQL 语句,这将使您面临 SQL 注入和性能问题。您应该改用绑定变量,如此处所述:https://oracle.github.io/node-oracledb/doc/api.html#bind

代码如下:

var XLSX = require("xlsx");
var workbook = XLSX.readFile("Accounts.xlsx");
var sheet_name_list = workbook.SheetNames;
var xlData = XLSX.utils.sheet_to_json(workbook.Sheets[sheet_name_list[0]]);
var connection;
var oracledb = require("oracledb");
oracledb.autoCommit = true;
var dbConfig = require(__dirname + "/dbconfig.js");

async function main() {
  const connection = await oracledb.getConnection(dbConfig);
  for (i in xlData) 
  {
      var act_fam = xlData[i].ACCOUNT_FAMILY;
      const results = await connection.execute('SELECT * FROM TFAMCORGDS_TEST WHERE MNEFAMCOR= :act_fam', [act_fam]);
      var cnt = results.rows.length;
      if (cnt === 0) {
        const insertResult = await connection.execute(
      'INSERT INTO TFAMCORGDS_TEST (CODFAMCOR,MNEFAMCOR,DATMOD,DATFINACT) VALUES (SCORGDS.NEXTVAL, :act_fam,SYSDATE,NULL)', [act_fam]);
        console.log('Rows Inserted: ', insertResult.rowsAffected);
      }
  }

}

现在代码简单安全。如果您只处理几行(并且数字不会随着时间的推移而增加)并且性能很好,则可以到此为止。否则,请继续...

当前的实现正在进行我们所说的逐行或缓慢处理。作为开发者,你应该尽量避免过多的网络往返(网络是最糟糕的一种I/O)。循环中有两个 execute 调用,因此循环的每次迭代都是两次往返。

使用 Oracle,您可以使用许多工具来减少往返行程,因此您可以在这里采用不同的方法。例如,您可以查看 executeManyhttps://oracle.github.io/node-oracledb/doc/api.html#-30-database-round-trips

但是,在这种情况下,我认为最简单的方法可能是将语句作为匿名 PL/SQL 块发送到数据库。看起来像这样:

var XLSX = require("xlsx");
var workbook = XLSX.readFile("Accounts.xlsx");
var sheet_name_list = workbook.SheetNames;
var xlData = XLSX.utils.sheet_to_json(workbook.Sheets[sheet_name_list[0]]);
var connection;
var oracledb = require("oracledb");
oracledb.autoCommit = true;
var dbConfig = require(__dirname + "/dbconfig.js");

async function main() {
  const connection = await oracledb.getConnection(dbConfig);
  const act_fams = [];
  for (i in xlData) 
  {
    act_fams.push(xlData[i].ACCOUNT_FAMILY);
  }

  await connection.execute(
   `declare

      type number_aat is table of number
        index by pls_integer;

      l_act_fam_arr number_aat;
      l_count       number;

    begin

      l_act_fam_arr := :act_fam_arr;

      for act_fam in 1 .. l_act_fam_arr.count
      loop
        select count(*) 
        into l_count
        from tfamcorgds_test 
        where mnefamcor=act_fam;

        if l_count = 0
        then
          insert into tfamcorgds_test (
            codfamcor,mnefamcor,datmod,datfinact
          ) values (scorgds.nextval, act_fam,sysdate,null);
        end if;
      end loop;
    end;`,
    {
      act_fam_arr: {
        type: oracledb.NUMBER,
        val: act_fams
      }
    }
  );
}

我没有测试此代码,因此可能存在语法错误。请注意,我传递给 execute 的第一个参数是一大串代码,一个 PL/SQL 块。第二个参数是绑定变量,它是一个数字数组(我假设 ACCOUNT_FAMILY 是一个数字,但如果需要,您可以轻松地将其更改为字符串)。

代码和值将通过单次网络往返发送到数据库。 PL/SQL 代码实现了与之前在 JavaScript 中相同的逻辑。如果您 运行 与以前的版本相比使用此代码进行测试,您应该会看到明显的性能改进(获取的行越多,改进就越明显)。

如果 MNEFAMCOR 是唯一的,例如喜欢:

CREATE TABLE TFAMCORGDS_TEST (CODFAMCOR NUMBER, MNEFAMCOR VARCHAR2(20) PRIMARY KEY, DATMOD DATE, DATFINACT VARCHAR2(10));

然后另一种减少往返的方法(在 Dan 的回答之上)是使用 executeMany()batchErrors 模式。这将允许在标记重复记录的同时插入新记录:

var XLSX = require("xlsx");
var workbook = XLSX.readFile("Accounts.xlsx");
var sheet_name_list = workbook.SheetNames;
var xlData = XLSX.utils.sheet_to_json(workbook.Sheets[sheet_name_list[0]]);
var connection;
var oracledb = require("oracledb");
var dbConfig = require(__dirname + "/dbconfig.js");

async function main() {
  const connection = await oracledb.getConnection(dbConfig);

  const sql = `INSERT INTO TFAMCORGDS_TEST (CODFAMCOR,MNEFAMCOR,DATMOD,DATFINACT) 
               VALUES (SCORGDS.NEXTVAL, :ACCOUNT_FAMILY, SYSDATE, NULL)`;

  const options = {
    batchErrors: true,
    bindDefs: {
      ACCOUNT_FAMILY: { type: oracledb.STRING, maxSize: 20 }
    }
  };

  const result = await connection.executeMany(sql, xlData, options);
  await connection.commit();

  console.log("Result is:", result);

}

main();

如果您的电子表格真的非常大,那么您可能需要使用记录的子集多次调用 executeMany()

旁注:Oracle 12 引入了标识列,因此可以降低使用序列的复杂性:

CREATE TABLE TFAMCORGDS_TEST (CODFAMCOR NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY, MNEFAMCOR VARCHAR2(20) PRIMARY KEY, DATMOD DATE, DATFINACT VARCHAR2(10));

这样,您就不需要在 INSERT 中包含 SCORGDS.NEXTVAL