Read several sources stating how COPY is faster than INSERT statements in PostgreSQL when performing bulk ingests. Sequelize.js has a bulkCreate() function for doing bulk INSERTs.
Is it possible for Sequelize to do COPY instead of INSERT, and do you expect COPY to be faster? I am ingesting 10,000-1,000,000 rows at a time in each of the 5 parallel processes running for a total of 2 hours. Hope some speedups can be achieved using COPY and Sequelize.
Actual database is using TimescaleDB extension
Example Code
let Note = sequelize.define('notes', {
description: Sequelize.STRING
});
let notes = [
{ description: 'Tai chi in the morning' },
{ description: 'Visited friend' },
{ description: 'Went to cinema' },
{ description: 'Listened to music' },
{ description: 'Watched TV all day' },
{ description: 'Walked for a hour' },
];
Note.bulkCreate(notes, { logging: console.log }).then(() => {
console.log('notes created');
}).catch((err) => {
console.log(err);
}).finally(() => {
sequelize.close();
});
Generated SQL
INSERT INTO "notes" ("id","description") VALUES (DEFAULT,'Tai chi in the morning'),
(DEFAULT,'Visited friend'),(DEFAULT,'Went to cinema'),(DEFAULT,'Listened to music'),
(DEFAULT,'Watched TV all day'),(DEFAULT,'Walked for a hour') RETURNING *;
Related
Hello I am trying to populate the data and then trying to paginate that data.
Here is the example
Schema A (Users)
{
name: 'Demo',
postId: 'someObjectId',
}
Schema B (Posts)
{
id: 'someObjectId',
postName: 'Post 1',
date: 'date of creation'
}
Here is my code
const users = UserModel.find({
name: 'Demo'
}).populate({
path: 'postId',
select: 'date',
match: {'postId.date' : {$lt: 'today'}}
}).page(pagination).limit(20)
Not getting the result needed. Can someone point out what's wrong?
NOTE: I have just given the overview. Please don't take it as real code. I know I haven't written what we would write in javascript
A populate have following things:
Post.find({})
.populate([
// here array is for our memory.
// because may need to populate multiple things
{
path: 'user',
select: 'name',
model:'User',
options: {
sort:{ },
skip: 5,
limit : 10
},
match:{
// filter result in case of multiple result in populate
// may not useful in this case
}
}
]);
.exec((err, results)=>{
console.log(err, results)
});
here is my problem
I want to loop data from two entities Project and User, my Project entity have a field with User id called creatorId.
I looped it and tried to add my user data _id, displayname and avatarUrl.
For now that work, but when I send it from the backend to the frontend nothing appears, like the array is empty.
const projectList = [];
Project.find({}, function(err, projects) {
projects.map(project => {
User.findById(project.creatorId, function(err, creator){
projectList.push({
_id: project._id,
title: project.title,
description: project.description,
avatarUrl: project.avatarUrl,
creationDate: project.creationDate,
projectCreator: {
_id: creator._id,
displayname: creator.displayname,
avatarUrl: creator.avatarUrl
},
git: project.git
})
})
})
});
res.send(projectList);
I have a working serverless application deployed on Lambda (nodejs6.10) and can create and read users from my DynamoDB, however, I have been having problems trying to perform an update on a specific attribute.
Basically, my table has a key of userId and two attributes called email and score.
The application detects if a referral code (userId) was supplied and if so it should increment their score by 1. Below are the params that I am passing to the dynamoDb.update function.
if (refcode) {
console.log("A referral code: " + refcode + " was detected");
const params = {
TableName: USERS_TABLE,
Key: {
userId: refcode
},
UpdateExpression: "set score = score + :val",
ExpressionAttributeValues: {
":val": 1
},
ReturnValues: "UPDATED_NEW"
};
console.log(params);
dynamoDb.update(params, (error, result) => {
console.log("Checking for error...");
if (error) {
console.log(error);
res.status(400), json({ error: "Could not GET user" });
}
console.log("Checking for result...");
if (result.Item) {
console.log("Item updated");
const { userId, email, score } = result.Item;
} else {
res.status(404).json({ error: "Invalid referral code" });
console.log("Invalid ref code");
}
});
}
In Cloudwatch I can see that my function has entered this part of the logic successfully, however, it looks like it never runs the dynamoDb.update part. Here are the cloudwatch logs:
START RequestId: 7d92d4da-a710-11e8-abdd-039e23e278bd Version: $LATEST
2018-08-23T20:09:52.392Z 7d92d4da-a710-11e8-abdd-039e23e278bd A referral code: cEBeGM1sk was detected
2018-08-23T20:09:52.393Z 7d92d4da-a710-11e8-abdd-039e23e278bd { TableName: '**<redacted>**',
Key: { userId: 'cEBeGM1sk' },
UpdateExpression: 'set score = score + :val',
ExpressionAttributeValues: { ':val': 1 },
ReturnValues: 'UPDATED_NEW' }
2018-08-23T20:09:52.550Z 7d92d4da-a710-11e8-abdd-039e23e278bd Reached the end - taking user to thank you page
END RequestId: 7d92d4da-a710-11e8-abdd-039e23e278bd
REPORT RequestId: 7d92d4da-a710-11e8-abdd-039e23e278bd Duration: 1530.76 ms Billed Duration: 1600 ms Memory Size: 128 MB Max Memory Used: 45 MB
Any help much appreciated! It should work according to the atomic update example given on the AWS documentation: AWS Documentation
Use add not set. If initial value is undefined, 0 will be used.
This code does what is expected to be done:
const AWS = require('aws-sdk');
const getEnv = require('../../../helpers/environment/get');
AWS.config.update({
accessKeyId: getEnv('AWS_ACCESS_KEY'),
secretAccessKey: getEnv('AWS_ACCESS_KEY_SECRET'),
region: getEnv('AWS_REGION'),
});
const client = new AWS.DynamoDB.DocumentClient();
const queryResult = await dynamo.update({
TableName: getEnv('AWS_DYNAMO_TABLE_LOG'),
Key: {
pk: 'a',
t: 1,
},
UpdateExpression: 'add #test :value',
ExpressionAttributeNames: {
'#test': 'test_incr',
},
ExpressionAttributeValues: {
':value': 2,
},
ReturnConsumedCapacity: 'TOTAL',
ReturnValues: 'ALL_NEW',
}, (error, data) => {console.log({error, data})});
Consider using a newer version of NodeJS with your lambda ;) any was supported recent LTS is often best choice https://github.com/nodejs/Release#release-schedule
Also for DynamoDB nodejs client i personaly find this doc most usefull: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html
P.s. about lambda version, at the moment supported versions are node 14 and 12 without additional config. Yet some of aws services when integrating requires older version, aka 12
Thanks, Lukas!
The documentation was very confusing for a new developer - your answer has explained and fixed my problem, and shown how to use the ExpressionAttributeNames correctly, Thank you!
const params = {
TableName: USERS_TABLE,
Key: {
userId: refcode
},
UpdateExpression: "add #test :value",
ExpressionAttributeNames: {
"#test": "score"
},
ExpressionAttributeValues: {
":value": 2
},
ReturnConsumedCapacity: "TOTAL",
ReturnValues: "ALL_NEW"
};
recently I discovered this on the sequelize documentation where you can create using include. Now I trying to do it on my program but only creates the records of the "parent" model and not for the children.
This is my model and my controller.
var MainMenu = sequelize.define('MainMenu', {
Name: {
type: DataTypes.STRING(50)
},
Day: {
type: DataTypes.DATE
},
RecordStatus:{
type: DataTypes.BOOLEAN,
defaultValue: true
},
DeletedAt: {
type: DataTypes.DATE
}
},
{
associate: function(models){
models.MainMenu.hasMany(models.MainMeal, {as: 'Menu'});
}
}
);
exports.createIn = (req, res) => {
let Menu = {
Name: 'MenuTest',
MainMeal: [{
Type: 'Breakfast',
Name: 'MealTest1'
}, {
Type: 'Lunch',
Name: 'MealTest2'
}]
};
db.MainMenu.create(Menu, {
include: [{
model: db.MainMeal,
as: 'Menu'
}]
})
.then( mainmenu => {
if (!mainmenu) {
return res.send('users/signup', {
errors: 'Error al registrar el mainmenu.'
});
} else {
return res.jsonp(mainmenu);
}
})
.catch( err => {
console.log(err);
return res.status(400)
.send({
message: errorHandler.getErrorMessage(err)
});
});
};
On my case it only creates the MainMenu record and not the MainMeal records. What am I doing wrong?
Change your menu object, and include Menu array and not MainMeal
You have to give the aliased name in the object
let mainMenu = {
Name: 'MenuTest',
Menu: [{
Type: 'Breakfast',
Name: 'MealTest1'
}, {
Type: 'Lunch',
Name: 'MealTest2'
}]
};
Now,
db.MainMenu.create(mainMenu, {
include: [{
model: db.MainMeal,
as: 'Menu'
}]
})
.then( mainmenu => {
if (!mainmenu) {
return res.send('users/signup', {
errors: 'Error al registrar el mainmenu.'
});
} else {
return res.jsonp(mainmenu);
}
})
.catch( err => {
console.log(err);
return res.status(400)
.send({
message: errorHandler.getErrorMessage(err)
});
});
The main thing is of course the naming of Menu should be within the data passed to .create() itself, along with the arguments presented there and if you really need to specify the alias "twice", which you do not. But there are some other things to be aware of.
I'd personally prefer storing the association as it's own export and including that within the statement. This generally becomes a bit clearer when you understand the usage of that association later.
I would also strongly encourage that when you are "writing" things across multiple tables, then you implement transactions to ensure all related items are actually created and not left orphaned should any errors arise.
As a brief listing based on the example:
const Sequelize = require('sequelize');
const sequelize = new Sequelize('sqlite:menu.db',{ logging: console.log });
const MainMeal = sequelize.define('MainMeal', {
Type: { type: Sequelize.STRING(50) },
Name: { type: Sequelize.STRING(50) }
});
const MainMenu = sequelize.define('MainMenu', {
Name: { type: Sequelize.STRING(50) }
});
MainMenu.Meals = MainMenu.hasMany(MainMeal, { as: 'Menu' });
(async function() {
try {
await sequelize.authenticate();
await MainMeal.sync({ force: true });
await MainMenu.sync({ force: true });
let result = await sequelize.transaction(transaction =>
MainMenu.create({
Name: 'MenuTest',
Menu: [
{ Type: 'Breakfast', Name: 'MealTest1' },
{ Type: 'Lunch', Name: 'MealTest2' }
]
},{
include: MainMenu.Meals,
transaction
})
);
} catch(e) {
console.error(e);
} finally {
process.exit();
}
})();
Which would output something like:
Executing (default): SELECT 1+1 AS result
Executing (default): DROP TABLE IF EXISTS `MainMeals`;
Executing (default): CREATE TABLE IF NOT EXISTS `MainMeals` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `Type` VARCHAR(50), `Name` VARCHAR(50), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `MainMenuId` INTEGER REFERENCES `MainMenus` (`id`) ON DELETE
SET NULL ON UPDATE CASCADE);
Executing (default): PRAGMA INDEX_LIST(`MainMeals`)
Executing (default): DROP TABLE IF EXISTS `MainMenus`;
Executing (default): CREATE TABLE IF NOT EXISTS `MainMenus` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `Name` VARCHAR(50), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL);
Executing (default): PRAGMA INDEX_LIST(`MainMenus`)
Executing (3d645847-56ca-435a-b786-6be62a05e8d5): BEGIN DEFERRED TRANSACTION;
Executing (3d645847-56ca-435a-b786-6be62a05e8d5): INSERT INTO `MainMenus` (`id`,`Name`,`createdAt`,`updatedAt`) VALUES (NULL,'MenuTest','2018-04-14 08:08:17.132 +00:00','2018-04-14 08:08:17.132 +00:00');
Executing (3d645847-56ca-435a-b786-6be62a05e8d5): INSERT INTO `MainMeals` (`id`,`Type`,`Name`,`createdAt`,`updatedAt`,`MainMenuId`)
VALUES (NULL,'Breakfast','MealTest1','2018-04-14 08:08:17.152 +00:00','2018-04-14 08:08:17.152 +00:00',1);
Executing (3d645847-56ca-435a-b786-6be62a05e8d5): INSERT INTO `MainMeals` (`id`,`Type`,`Name`,`createdAt`,`updatedAt`,`MainMenuId`)
VALUES (NULL,'Lunch','MealTest2','2018-04-14 08:08:17.153 +00:00','2018-04-14 08:08:17.153 +00:00',1);
Executing (3d645847-56ca-435a-b786-6be62a05e8d5): COMMIT;
The important part there being the transaction BEGIN and COMMIT wrapping all of those INSERT statements as data is created. Even without the transaction implemented, you still see both items being created along with the related "parent". But the point of the argument is this is where you "should" be implementing transactions.
Also note that the "aliased" Menu as used in the data creation and for subsequent access, is not actually "required" to be included within the .create() method on the include option. It's "optional" and is already defined under the .hasMany() arguments, so you don't really need to do it again.
Even if you did, then that part would still be the "association" as used with the model argument:
{
include: {
model: MainMenu.Meals,
as: 'Menu'
},
transaction
}
So that's not to be confused with the original name of the model for the "table" which is referenced, which also might be another point of confusion.
I am currently using StrongLoop as my API backend server and Mongodb as data storage engine.
Let's say there is a collection called article. It has two fields title, and content. And there are two frontend pages to display a list of articles and view a single article.
Obviously the data list page only need title field and the view page need both. Currently the GET method of StrongLoop API return all fields including content. It cost extra traffic. Is there any way that can just return specific field?
Mongodb support projection in find() method for this. How can I do the same thing by StrongLoop?
Have you taken a look at the filters offered. http://docs.strongloop.com/display/LB/Querying+models
Query for NodeAPI:
server.models.Student.findOne({where: {RFID: id},fields: {id: true,schoolId: true,classId: true}}, function (err, data) {
if (err)
callback(err);
else {
callback();
}
})
Query for RestAPI :
$http.get('http://localhost:3000/api/services?filter[fields][id]=true&filter[fields][make]=true&filter[fields][model]=true')
.then(function (response) {
}, function (error) {
});
You can use fields projections,
Sample Record:
{ name: 'Something', title: 'mr', description: 'some desc', patient: { name: 'Asvf', age: 20, address: { street: 1 }}}
First Level Projection:
model.find({ fields: { name: 1, description: 1, title: 0 } })
and I think Strong loop is not yet supporting for second-level object filter, does anyone know how to filter second-level object properties or is yet to implement?.
Second Level Projection: (Need help here)
Ex: 2
model.find({ fields: { name: 1, 'patient.name': 1, 'patient.age': 1, 'patient.address': 0 } })
// Which results { name } only