NodeJs : bulk insert into SQL Server one-to-many - node.js

I want to using nodejs mssql package to bulk insert data with below json:
[
{
"name": "Tom",
"registerDate": "2021-10-10 00:00:00",
"gender": 0,
"consumeRecord":[
{
"date": "2021-10-11 00:00:00",
"price": 102.5
},
{
"date": "2021-10-12 00:00:00",
"price": 200
}
]
},
{
"name": "Mary",
"registerDate": "2021-06-10 00:00:00",
"gender": 1,
"consumeRecord":[
{
"date": "2021-07-11 00:00:00",
"price": 702.5
},
{
"date": "2021-12-12 00:00:00",
"price": 98.2
}
]
}
]
I am try to mssql bulk insert for the member record with multiple consume data?
Is there anything can insert one to many with bulk insert like below.
because it seems need to insert the member table and get the id (primary key) first. Then using the id (primary key) for the consume table relation data
const sql = require('mssql')
// member table
const membertable = new sql.Table('Member')
table.columns.add('name', sql.Int, {nullable: false})
table.columns.add('registerDate', sql.VarChar(50), {nullable: false})
table.columns.add('gender', sql.VarChar(50), {nullable: false})
// consume record table
const consumeTable = new sql.Table('ConsumeRecord')
table.columns.add('MemberId', sql.Int, {nullable: false})
table.columns.add('Date', sql.VarChar(50), {nullable: false})
table.columns.add('price', sql.Money, {nullable: false})
// insert into member table
jsonList.forEach(data => {
table.rows.add(data.name)
table.rows.add(data.registerDate)
table.rows.add(data.gender)
consumeTable.rows.add(data.memberId) // <---- should insert member table id
consumeTable.rows.add(data.consumeRecord.data)
consumeTable.rows.add(data.consumeRecord.price)
const request = new sql.Request()
request.bulk(consumeTable , (err, result) => {
})
})
const request = new sql.Request()
request.bulk(membertable , (err, result) => {
})
Expected Record:
Member Table
id (auto increment)
name
registerDate
gender
1
Tom
2021-10-10 00:00:00
0
2
Mary
2021-06-10 00:00:00
1
Consume Record Table
id
MemberId
Date
price
1
1
2021-10-10 00:00:00
102.5
2
1
2021-10-12 00:00:00
200
3
2
2021-07-11 00:00:00
702.5
4
2
2021-12-12 00:00:00
98.2

The best way to do this is to upload the whole thing in batch to SQL Server, and ensure that it inserts the correct foreign key.
You have two options
Option 1
Upload the main table as a Table Valued Parameter or JSON blob
Insert with OUTPUT clause to select the inserted IDs back to the client
Correlate those IDs back to the child table data
Bulk Insert that as well
Option 2 is a bit easier: do the whole thing in SQL
Upload everything as one big JSON blob
Insert main table with OUTPUT clause into table variable
Insert child table, joining the IDs from the table variable
CREATE TABLE Member(
Id int IDENTITY PRIMARY KEY,
name varchar(50),
registerDate datetime NOT NULL,
gender tinyint NOT NULL
);
CREATE TABLE ConsumeRecord(
MemberId Int NOT NULL REFERENCES Member (Id),
Date datetime not null,
price decimal(9,2)
);
Note the more sensible datatypes of the columns
DECLARE #ids TABLE (jsonIndex nvarchar(5) COLLATE Latin1_General_BIN2 not null, memberId int not null);
WITH Source AS (
SELECT
j1.[key],
j2.*
FROM OPENJSON(#json) j1
CROSS APPLY OPENJSON(j1.value)
WITH (
name varchar(50),
registerDate datetime,
gender tinyint
) j2
)
MERGE Member m
USING Source s
ON 1=0 -- never match
WHEN NOT MATCHED THEN
INSERT (name, registerDate, gender)
VALUES (s.name, s.registerDate, s.gender)
OUTPUT s.[key], inserted.ID
INTO #ids(jsonIndex, memberId);
INSERT ConsumeRecord (MemberId, Date, price)
SELECT
i.memberId,
j2.date,
j2.price
FROM OPENJSON(#json) j1
CROSS APPLY OPENJSON(j1.value, '$.consumeRecord')
WITH (
date datetime,
price decimal(9,2)
) j2
JOIN #ids i ON i.jsonIndex = j1.[key];
db<>fiddle
Unfortunately, INSERT only allows you to OUTPUT from the inserted table, not from any non-inserted columns. So we need to hack it with a weird MERGE

Related

Inserting multiple rows into SQL Server from Node.js

I am working on a project that will upload some records to SQL Server from a node.js program. Right now, this is my approach (inside an async function):
con = await sql.connect(`mssql://${SQL.user}:${SQL.password}#${SQL.server}/${SQL.database}?encrypt=true`);
for (r of RECORDS) {
columns = `([column1], [column2], [column3])`;
values = `(#col1, #col2, #col3)`;
await con
.request()
.input("col1", sql.Int, r.col1)
.input("col2", sql.VarChar, r.col2)
.input("col3", sql.VarChar, r.col3)
.query(`INSERT INTO [dbo].[table1] ${columns} VALUES ${values}`);
}
Where records is an array of objects in the form:
RECORDS = [
{ col1: 1, col2: "asd", col3: "A" },
{ col1: 2, col2: "qwerty", col3: "B" },
// ...
];
This code works, nevertheless, I have the feeling that it is not efficient at all. I have an upload of around 4k records and it takes roughly 10 minutes, it does not look good.
I believe if I can create a single query - instead of wrapping single inserts inside a for loop - with all the record values it will be faster, and I know there is a syntax for reaching that in SQL:
INSERT INTO table1 (column1, column2, column3) VALUES (1, "asd", "A"), (2, "qwerty", "B"), (...);
However I cannot find any documentation from mssql module for node on how to prepare the parameterized inputs to do everything in a single transaction.
Can anyone guide me into the right direction?
Thanks in advance.
Also, very similar to the bulk insert, you can use a table valued parameter.
sql.connect("mssql://${SQL.user}:${SQL.password}#${SQL.server}/${SQL.database}?encrypt=true")
.then(() => {
const table = new sql.Table();
table.columns.add('col1', sql.Int);
table.columns.add('col2', sql.VarChar(20));
table.columns.add('col3', sql.VarChar(20));
// add data
table.rows.add(1, 'asd', 'A');
table.rows.add(2, 'qwerty', 'B');
const request = new sql.Request();
request.input('table1', table);
request.execute('procMyProcedure', function (err, recordsets, returnValue) {
console.dir(JSON.stringify(recordsets[0][0]));
res.end(JSON.stringify(recordsets[0][0]));
});
});
And then for the SQL side, create a user defined table type
CREATE TYPE typeMyType AS TABLE
(
Col1 int,
Col2 varchar(20),
Col3 varchar(20)
)
And then use this in the stored procedure
CREATE PROCEDURE procMyProcedure
#table1 typeMyType READONLY
AS
BEGIN
INSERT INTO table1 (Col1, Col2, Col3)
SELECT Col1, Col2, Col3
FROM #MyRecords
END
This gives you more control over the data and lets you do more with the data in sql before you actually insert.
As pointed out by #JoaquinAlvarez, bulk insert should be used as replied here: Bulk inserting with Node mssql package
For my case, the code was like:
return await sql.connect(`mssql://${SQL.user}:${SQL.password}#${SQL.server}/${SQL.database}?encrypt=true`).then(() => {
table = new sql.Table("table1");
table.create = true;
table.columns.add("column1", sql.Int, { nullable: false });
table.columns.add("column2", sql.VarChar, { length: Infinity, nullable: true });
table.columns.add("column3", sql.VarChar(250), { nullable: true });
// add here rows to insert into the table
for (r of RECORDS) {
table.rows.add(r.col1, r.col2, r.col3);
}
return new sql.Request().bulk(table);
});
The SQL data types have to match (obviously) the column type of the existing table table1. Note the case of column2, which is a column defined in SQL as varchar(max).
Thanks Joaquin! I went down on the time significantly from 10 minutes to a few seconds

Formatting the result of an SQL query into JSON

I have a small database as defined in the code snippet below. I want to query this to get all of the information and send it to an vue app via a JSON file via a Flask API. At the moment the query that I am using is
SELECT tbl_room.room, tbl_room.room_id, tbl_device.name, tbl_display.display, tbl_function.function, tbl_device.format
FROM tbl_device
INNER JOIN tbl_room ON tbl_room.id = tbl_device.room_id
INNER JOIN tbl_display ON tbl_display.id = tbl_device.display_id
INNER JOIN tbl_function ON tbl_function.id = tbl_device.function_id
ORDER BY tbl_room.room_id;
this gives me an output like:
Bedroom (Main) bedroom_main bme280/1 gauge temperature {"min": 0, "max": 50, "dp": 1, "units": "°C"}
Bedroom (Main) bedroom_main bme280/1 gauge humidity {"min": 0, "max": 100, "dp": 1, "units": "%"}
Bedroom (Main) bedroom_main bme280/1 gauge pressure {"min": 0, "max": 1100, "dp": 1, "units": "hPa"}
Front Room front_room ds18b20/heater gauge temperature {"min": 0, "max": 50, "dp": 1, "units": "°C"}
I would like to get it into a JSON file so that it is arranged as:
[
{ "name": "Office",
"id": "office",
"devices": []
},
{ "name": "Front Room",
"id": "front_room",
"devices": []
}
}
]
Can this be done in a single sql query? Or do I have to do a query for each room in a loop? Or is it more efficient to dump the whole dataset out in one query and process it in pyhton afterwards? This is a small dataset but I'm interested to know which is the most efficient method.
Thank you in advance,
Martyn
Here is my table structure:
-- Table: tbl_device
CREATE TABLE tbl_device (
name VARCHAR NOT NULL ON CONFLICT ROLLBACK,
room_id INTEGER CONSTRAINT fk_room REFERENCES tbl_room (id)
NOT NULL,
function_id INTEGER CONSTRAINT fk_function REFERENCES tbl_function (id)
NOT NULL ON CONFLICT ROLLBACK,
display_id INTEGER CONSTRAINT fk_display REFERENCES tbl_display (id)
NOT NULL ON CONFLICT ROLLBACK,
format VARCHAR NOT NULL ON CONFLICT ROLLBACK
DEFAULT [default],
UNIQUE (
name,
room_id,
function_id,
display_id
)
ON CONFLICT ROLLBACK
);
-- Table: tbl_display
CREATE TABLE tbl_display (
id INTEGER PRIMARY KEY AUTOINCREMENT,
display VARCHAR NOT NULL ON CONFLICT ROLLBACK
UNIQUE ON CONFLICT ROLLBACK
);
-- Table: tbl_function
CREATE TABLE tbl_function (
id INTEGER PRIMARY KEY AUTOINCREMENT,
function VARCHAR NOT NULL ON CONFLICT ROLLBACK
UNIQUE ON CONFLICT ROLLBACK,
control BOOLEAN NOT NULL
DEFAULT (0)
);
-- Table: tbl_room
CREATE TABLE tbl_room (
id INTEGER PRIMARY KEY AUTOINCREMENT,
room_id VARCHAR NOT NULL
UNIQUE ON CONFLICT ROLLBACK,
room VARCHAR NOT NULL ON CONFLICT ROLLBACK
);
First, There is no way to directly feed JSON response from MySQL database to VueJS or any other App. VueJS App is the fronted of your application. You have to create a Backend which connects to MySQL database, fetch necessary data from MYSQL Database, Convert them to JSON and send to Vue App.
To Develop a backend, you may use languages such as PHP, Python, Java, NodeJS etc.
If you can continue with PHP, it is very easy to fetch data and convert to JSON.
But If you still need to continue with Python, you have to use Flask or any other python web framework to do that.
Here is the sample php code
<?php
$dbhost = 'hostname';
$dbuser = 'username';
$dbpass = 'password';
$dbname = 'database';
$db = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
if ($db->connect_errno) {
printf("Failed to connect to database");
exit();
}
$result = $db->query("SELECT * FROM "); // Your SQL query
$data = array();
while ( $row = $result->fetch_assoc()) {
$data[]=$row;
}
echo json_encode($data);
?>
If your version of sqlite was compiled with the JSON1 extension, enabled, something like:
SELECT json_group_array(json_object('name', tbl_room.name,
'id', tbl_room.room_id,
'devices', json_array()))
FROM tbl_room
GROUP BY tbl_room.name, tbl_room.room_id;

How to join table that contains no data yet with sqlite

I am trying to join two tables: users and favourites. There is a possibility that a user has no favourites yet and when I tried to INNER JOIN the two I didn't get back the user without any favourites. Is there any way to join even if the second table has no data for that user?
I created the users tabel with the following code:
db.run(`CREATE TABLE Users(
UserId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
Name TEXT NOT NULL,
Password TEXT NOT NULL,
Phone VARCHAR,
Email TEXT,
RestaurantId INTEGER,
FOREIGN KEY(RestaurantId) REFERENCES Restaurants(RestaurantId))`, (err) => {
if(err) {
console.error(err.message);
} else {
//insert some values
var insert = 'INSERT INTO Users (Name, Password, Phone, Email, RestaurantId) VALUES (?, ?, ?, ?, ?)';
db.run(insert, [
'Liam',
'blabla',
'+32412345678',
'email#email.com',
1
]);
}
}
);
And the favourites table with:
db.run(`CREATE TABLE Favourites(
FavouriteId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
UserId INTEGER NOT NULL,
RestaurantId INTEGER NOT NULL,
FOREIGN KEY(UserId) REFERENCES Users(UserId),
FOREIGN KEY(RestaurantId) REFERENCES Restaurants(RestaurantId))`, (err) => {
if(err) {
console.error(err.message);
} else {
//insert some values
var insert = 'INSERT INTO Favourites (UserId, RestaurantId) VALUES (?, ?)';
db.run(insert, [
1,
1
]);
}
}
);
There is no problem with the data that exists in the table that was generated after these inserts. The problem only exists when a new user without favourites gets added to the database.
You are looking for LEFT JOIN. Take a look at the documentation: https://www.w3resource.com/sqlite/sqlite-left-join.php.
LEFT JOIN returns all the records on the left side of the join, with the matched records from the right side.

JSONB Query Sequelize

I have one table in postgres table and table structure is
ID
Name
Details
Context
CreatedDate
Where as Context is JSONB field and CreatedDate is a timestamp
I am saving data in Context this way {"trade": {"id": 102}, "trader": {"id": 100}}
I am trying to select record from Context based on trader id and this is my query
this.findAll({
where: {
context: {
$contains: {
trader: [{id: '100'}]
}
}
}
})
I tried nested keys as well but no result yeild.
this.findAll({
where: {
'context.trader.id': {
$eq: '100'
}
}
})
Can you please suggest how I can select the records based on my structure.
In continuity to that how I can get records based on two statements like adding createdtime in this where clause

Cassandra polymorphism

How can I do in Cassandra polymorphism?
For example, as shown below store json?
The first type, when the attachments added photos
{
"id": 1,
"text": "Hello",
"read": true,
"attachment_type": 1,
"attachment": {
"photo_id": 1,
"photo_big_url": "htt://photo.com/1_big.jpg",
"photo_medium_url": "htt://photo.com/1_medium.jpg",
"photo_small_url": "htt://photo.com/1_small.jpg"
}
}
And the second type, when the attachments added videos
{
"id": 2,
"text": "Hi",
"read": true,
"attachment_type": 2,
"attachment": {
"video_id": 1,
"video_url": "htt://video.com/1.mpg",
}
}
Sure, you can combine your fields in a single Cassandra table:
CREATE TABLE media (
id int,
name text,
read boolean,
attachment_type int,
photo_id int,
photo_big_url text,
photo_medium_url text,
photo_small_url" text,
video_id int,
video_url text,
PRIMARY KEY ((id), attachment_type)
);
Fields that are not completed are not stored in Cassandra, so you are not wasting hard disk space.
Depending on the driver you are using, you can declare different entities for Photo and Video, each having only the relevant fields, and map your results to entities accordingly.
With the DataStax driver for C# it would be something like:
mapper.Fetch<Photo>("SELECT * FROM media WHERE id = 1 and attachment_type = 1");
mapper.Fetch<Video>("SELECT * FROM media WHERE id = 1 and attachment_type = 2");

Resources