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,您可以使用许多工具来减少往返行程,因此您可以在这里采用不同的方法。例如,您可以查看 executeMany
:https://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
。
我正在读取 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,您可以使用许多工具来减少往返行程,因此您可以在这里采用不同的方法。例如,您可以查看 executeMany
:https://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
。