英文:
Node-oracledb: Only last value is being inserted multiple times in the loop
问题
I am reading a XLSX file and inserting the records to ORACLE database. XLSX contains below values
H
H
H
H
JK
But only JK is being inserted 5 times. Below is the code used
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=:1',
[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,:1,SYSDATE,NULL)',
[act_fam],
function(err, results) {
if (err) throw err;
console.log('Rows Inserted: ', results.rowsAffected);
// do work on the rows here
}
);
}
}
);
}
}
);
And also I am not able to use variable "cnt" value outside "connection.execute" function though it is a global variable.
英文:
I am reading a XLSX file and inserting the records to ORACLE database. XLSX contains below values
H
H
H
H
JK
But only JK is being inserted 5 times. Below is the code used
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
}
);
}
});
}
}
);
And also I am not able to use variable "cnt"
value outside connection.execute
function though it is global variable.
答案1
得分: 1
I will provide the translated code without the code-related parts:
我将提供已翻译的代码,不包括代码相关部分:
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);
}
}
}
希望这有所帮助。
英文:
I will segregate tasks in different functions and wrap them in promises instead soing callbacks in loops. the code will look something like this
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);
}
}
}
Hope this helps
答案2
得分: 0
你应该创建一个单独的异步函数来访问数据库。因为当我们执行一些数据库操作时,需要一些时间。我们必须等待完成后再执行下一个操作。所以我不会在这里放整个代码,只是一个示例。
async function main(){
//将数据发送到访问数据库的函数
for(i in xlData){
await insertData(xlData[i].ACCOUNT_FAMILY);
}
}
async function insertData(data){
//在这里执行数据库操作
}
**注意:**我在执行异步函数时使用了`await`关键字。
英文:
You should create a separate async function for access the database. Because when we perform some database operations it takes some time. we have to wait until finish that to do process next one. So i'm not going to put whole code here. Just a example.
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
}
NOTE: I'm using await
keyword for perform asynchronous function.
答案3
得分: 0
我喜欢Ashish Modi的回答,但我认为他不知道驱动程序已经支持Promises,所以他的代码可以简化。
首先,你不需要一个getConnection
函数。如果你不传递回调函数,驱动程序的getConnection
函数已经返回一个Promise,所以这个函数不添加任何内容。
get
函数也有相同的问题。它不需要,因为驱动程序的execute
方法已经支持Promises。
代码可以像这样简化:
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);
}
}
}
然而,这段代码仍然存在着一些问题: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中的相
英文:
I like Ashish Modi's answer, but I don't think he knew that the driver already supports promises, so his code could be simplified.
First, you don't need a getConnection
function. The driver's getConnection
function already returns a promise if you don't pass a callback, so this function doesn't add anything.
The get
function has the same problem. It's not needed because the driver's execute
method already supports promises.
The code could have looked like this:
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);
}
}
}
However, there are still big problems with the code: SQL injection vulnerabilities and excessive round trips.
The code is currently using string concatenation to pass values to SQL statements, this will open you up to SQL injection and performance issues. You should use bind variables instead, as documented here: https://oracle.github.io/node-oracledb/doc/api.html#bind
The code would look like this after:
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);
}
}
}
Now the code is simple and safe. You could stop there if you're working with just a few rows (and the number will not grow over time) and performance is fine. Otherwise, please continue...
The current implementation is doing what we call row-by-row or slow-by-slow processing. As a developer, you should try to avoid excessive network round trips (network is the worst kind of I/O). You have two execute
calls in the loop, so that's two round trips for each iteration of the loop.
With Oracle you have lots of tools available to cut down on round trips, so you could take different approaches here. For example, you could look into executeMany
: https://oracle.github.io/node-oracledb/doc/api.html#-30-database-round-trips
However, in this case, I think the simplest approach would likely be to ship the statements to the database as an anonymous PL/SQL block. That would look like this:
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
}
}
);
}
I've not tested this code so there are likely syntax errors. Note that the first parameter I'm passing to execute
is a large string of code, a PL/SQL block. The second parameter is the bind variable, which is an array of numbers (I assumed ACCOUNT_FAMILY was a number, but you could easily change it to a string if needed).
The code and values will be sent to the database via a single network roundtrip. The PL/SQL code implements the same logic you had before in JavaScript. If you run a test with this code compared to the previous version, you should see a noticeable performance improvement (the more rows fetched, the more noticeable the improvement will be).
答案4
得分: 0
如果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引入了标识列(identity columns),因此可以减少使用序列的复杂性:
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
了。
英文:
If MNEFAMCOR
is unique, e.g. like:
CREATE TABLE TFAMCORGDS_TEST (CODFAMCOR NUMBER, MNEFAMCOR VARCHAR2(20) PRIMARY KEY, DATMOD DATE, DATFINACT VARCHAR2(10));
then another way (on top of Dan's answers) to reduce round trips is by using the batchErrors
mode of executeMany()
. This will allow new records to be inserted while flagging the records that were duplicates:
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();
If your spreadsheet is really, really huge, then you may need to call executeMany()
several times with subsets of the records.
A side note: Oracle 12 introduced identity columns, so the complexity of using sequences can be reduced:
CREATE TABLE TFAMCORGDS_TEST (CODFAMCOR NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY, MNEFAMCOR VARCHAR2(20) PRIMARY KEY, DATMOD DATE, DATFINACT VARCHAR2(10));
With this, you don't need to include SCORGDS.NEXTVAL
in the INSERT
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论