I need to import the data from a CSV file (around 20K rows) to my database. Some of the rows may already exist in the database, thus they only need to be updated, but the new rows must be inserted. If any of the operations fail, the transaction must be cancelled.
How can I do this? This is the code I was using:
var vehicle = {
id: row.id,
lastUpdate: moment(row.lastUpdate, 'DD/MM/YYYY - HH:mm').toDate(),
version: row.version,
color: row.color,
location: row.location,
status: row.status
};
Vehicle.forge({
id: row.id
})
.save(vehicle, { transacting: t, patch: true })
.then(model => {
console.log('*************' + vehicle.id);
})
.catch(Vehicle.NoRowsUpdatedError, err => {
// There are no rows for this chassis, so let's insert it
Vehicle.forge(vehicle)
.save(null, { transacting: t, method: 'insert' })
.then(model => {
console.log('++++++++++++++' + vehicle.id);
})
.catch(err => {
console.log(`INSERT ERROR: ${err.message}`);
t.rollback();
return res.json({ status: false, count: 0, error: err.message });
});
})
.catch(err => {
console.log(`UPDATE ERROR: ${err.message}`);
t.rollback();
return res.json({ status: false, count: 0, error: err.message });
});
This code is in a for loop, but it fails in the second iteration, probably because of concurrency between the promises.
I also tried to add a custom function to my model file, but it says that the function does not exist.
let bookshelf = require('./base');
var Vehicle,
Vehicles;
Vehicle = bookshelf.Model.extend({
tableName: 'vehicles',
/**
* Insert a model based on data
* #param {Object} data
* #param {Object} [options] Options for model.save
* #return {Promise(bookshelf.Model)}
*/
create: function (data, options) {
return this.forge(data).save(null, options);
},
/**
* Select a model based on a query
* #param {Object} [query]
* #param {Object} [options] Options for model.fetch
* #param {Boolean} [options.require=false]
* #return {Promise(bookshelf.Model)}
*/
findOne: function (query, options) {
options = extend({ require: true }, options);
return this.forge(query).fetch(options);
},
/**
* Select a model based on data and update if found, insert if not found
* #param {Object} selectData Data for select
* #param {Object} updateData Data for update
* #param {Object} [options] Options for model.save
*/
upsert: function (selectData, updateData, options) {
return this.findOne(selectData, extend(options, { require: false }))
.bind(this)
.then(function (model) {
return model
? model.save(
updateData,
extend({ patch: true, method: 'update' }, options)
)
: this.create(
extend(selectData, updateData),
extend(options, { method: 'insert' })
)
});
}
});
Vehicles = bookshelf.Collection.extend({
model: Vehicle
});
module.exports = {
Vehicle: bookshelf.model('Vehicle', Vehicle),
Vehicles: bookshelf.collection('Vehicles', Vehicles)
};
Instead of using bookshelf, you could utilize knex directly to do this. Just grab the instance of knex you pass to bookshelf, and you can use it like this:
knex.transaction((trx) => {
return Bluebird.map(vehicles, vehicle => {
const insert = knex('vehicles').insert(vehicle).toString();
delete vehicle.id;
const update = knex('vehicles').update(vehicle).toString();
const set = update.substring(18);
return trx.raw(`${insert} ON CONFLICT (id) DO UPDATE ${set}`);
});
});
We can utilize Knex's handy toString method to generate most of our raw query for us; in this way, we can do an upsert even though it's not directly supported by Knex. Bluebird's map function is perfect for cleanly handling an array of data like this and would let you await having to loop through it altogether.
Related
Strapi doesn't have any endpoint to get random data for this purpose you should write some custom code for your endpoint
custom route for that endpoint you want
// path: ./src/api/[your-endpiont]/routes/[custom-route].js
module.exports = {
"routes": [
{
"method": "GET",
"path": "/[your-endpiont]/random", // you can define everything you want for url endpoint
"handler": "[your-endpiont].random", // random is defined as a method
"config": {
"policies": []
}
}
]
}
now you have to run yarn develop or npm ... to display a random method in your strapi panel
Save this setting and retry to reach the random endpoint.
create a function as a service for getting random data in your endpoint API services.
// path: ./src/api/[your-endpiont]/services/[your-endpiont].js
'use strict';
/**
* news-list service.
*/
const { createCoreService } = require('#strapi/strapi').factories;
module.exports = createCoreService('api::news-list.news-list', ({ strapi }) => ({
async serviceGetRandom({ locale, id_nin }) { // these parametrs come from query
function getRandomElementsFromArray(array, numberOfRandomElementsToExtract = 1) {
const elements = [];
function getRandomElement(arr) {
if (elements.length < numberOfRandomElementsToExtract) {
const index = Math.floor(Math.random() * arr.length)
const element = arr.splice(index, 1)[0];
elements.push(element)
return getRandomElement(arr)
} else {
return elements
}
}
return getRandomElement([...array])
}
const newsListArray = await strapi
.db
.query("api::news-list.news-list")
.findMany({
where: {
locale: locale, // if you have multi-language data
$not: {
id: id_nin, // depend on where this endpoint API use
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
},
//? filter object throws an error when you used populate object, everything you want to filter properly best write into where{}
// filters: {
// publishedAt: {
// $notNull: true,
// },
// locale: locale
// }
})
if (!newsListArray.length) {
return null
}
return getRandomElementsFromArray(newsListArray, 2)
}
}));
explain code:
Strapi provides a Query Engine API to interact with the database layer at a lower level
strapi.db.query("api::news-list.news-list").findMany({})
The Query Engine allows operations on database entries,
I wrote this for my purpose probably you should change based on what you needed
{
where: {
locale: locale,
$not: {
id: id_nin
},
publishedAt: {
$notNull: true,
},
},
sort: [{ datetime: 'asc' }],
limit: 10,
populate: {
content: {
populate: {
thumbnail: true,
},
},
}
}
when you get data from your query, passed it to that function getRandomElementsFromArray(newsListArray, 2) to get some random item (how many random items do you want ? pass the second parameter)
At least if your array is null return null otherwise return data
create the controller
Controllers are JavaScript files that contain a set of methods, called actions, reached by the client according to the requested route so we going to call our services in this section
// path: ./src/api/[your-endpoint]/controllers/[your-endpoint].js
'use strict';
/**
* news-list controller
*/
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::news-list.news-list', ({ strapi }) => ({
async random(ctx) { // name of this methods related to something we define in route ("handler": "[your-endpiont].random",)
const entity = await strapi.service('api::news-list.news-list').serviceGetRandom(ctx.query) // call our services, you can send all query you get from url endpoint (notice that you should write your endpoint api in strapi.service("your-endpoint"))
const sanitizedEntity = await this.sanitizeOutput(entity, ctx);
return this.transformResponse(sanitizedEntity);
// console.log(entity);
}
}));
I call this endpoint in my project nextjs & stapi cms
export const getRandomNewsItem = (id, locale) => {
return API
.get(`/news-list/random?locale=${locale}&id_nin=${id}`)
.then(res => res.data);
};
That's it, I'll hope you all get what to do
all resources you need
https://docs.strapi.io/developer-docs/latest/development/backend-customization/routes.html#creating-custom-routers
https://docs.strapi.io/developer-docs/latest/development/backend-customization/services.html#implementation
https://docs.strapi.io/developer-docs/latest/development/backend-customization/controllers.html#adding-a-new-controller
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine-api.html
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/filtering.html#and
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/entity-service/order-pagination.html#ordering
https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/query-engine/populating.html
First of all let me explain what I want to do. I have two model User and Product well define. I use method created by sequelize when defining association with hasMany() and belongsTo() to create my product.
user.hasMany(Product)
Product.belongsTo(User)
and I used this method to create the product from user.
User.findOne({where:{id:SomeID}}).then((userObj)=>{
userObj.createProduct(productobj).then((userObj)=>{
//product creation handling
}).catch((errorProduct)=>{
//product creation error handling
});
}).catch((error)=>{
//user creation error handling
});
My productobj is an array of json object that belongs to User.
It works fine when it's only one product but it failed when there is more than one product. So I wonder if there is any method that I can use to bulk create those products from the same user.
I may be doing it the wrong way. So if there is a better way then I will highly appreciate it. Thank you for your help.
The association user.hasMany(Product) will add below methods to User model.
/**
* The addAssociations mixin applied to models with hasMany.
* An example of usage is as follows:
*
* ```js
*
* User.hasMany(Role);
*
* interface UserInstance extends Sequelize.Instance<UserInstance, UserAttributes>, UserAttributes {
* // getRoles...
* // setRoles...
* addRoles: Sequelize.HasManyAddAssociationsMixin<RoleInstance, RoleId>;
* // addRole...
* // createRole...
* // removeRole...
* // removeRoles...
* // hasRole...
* // hasRoles...
* // countRoles...
* }
* ```
*
* #see https://sequelize.org/master/class/lib/associations/has-many.js~HasMany.html
* #see Instance
*/
For your case, you should use userModel.addProducts(productModels). E.g.
import { sequelize } from '../../db';
import { Model, DataTypes, HasManyAddAssociationsMixin } from 'sequelize';
class User extends Model {
public addProducts!: HasManyAddAssociationsMixin<Product, number>;
}
User.init(
{
email: {
type: DataTypes.STRING,
unique: true,
},
},
{ sequelize, modelName: 'users' },
);
class Product extends Model {}
Product.init(
{
name: DataTypes.STRING,
},
{ sequelize, modelName: 'products' },
);
User.hasMany(Product);
Product.belongsTo(User);
(async function test() {
try {
await sequelize.sync({ force: true });
// seed
await User.create(
{
email: 'example#gmail.com',
products: [{ name: 'apple' }, { name: 'amd' }],
},
{ include: [Product] },
);
// add another two products for this user
const SomeID = 1;
const productobj = [{ name: 'nvidia' }, { name: 'intel' }];
const productModels = await Product.bulkCreate(productobj);
const userObj = await User.findOne({ where: { id: SomeID } });
await userObj.addProducts(productModels);
} catch (error) {
console.log(error);
} finally {
await sequelize.close();
}
})();
data row in the database:
=# select * from users;
id | email
----+-------------------
1 | example#gmail.com
(1 row)
node-sequelize-examples=# select * from products;
id | name | userId
----+--------+--------
1 | apple | 1
2 | amd | 1
3 | nvidia | 1
4 | intel | 1
(4 rows)
NOTE: These methods only accept target model(s) as their parameter, NOT a plain javascript object or array of objects. You need to bulkCreate the products. Then, use userModel.addProducts(productModels) to associate these products to this user
It seems this is not supported as yet (although requested since 2015). We have a few options to workaround:
If your model definitions allow it, you can do as with slideshowp2's answer: bulkCreate the products, and then assign them to the user. This won't work if you are allowing nulls on the customerId column (which is probably not a good idea).
If you know the userId, you can include this id in each product item to be created, then use Products.bulkCreate(). This will work.
Create every product individually against the user. There's various ways of doing this eg something like
Promise.all(
productArray.map((prod) => {
return user.createProduct(prod)
}))
I have a controller/ user.js file to get data using a function from service/user.js
From getUsers function in service I can't return more than 1 value. for example I want to return return{User,pages}. But, i getting error while return like return{User,pages}
The below is thw error i getting while using return{User,pages}
userService.getUsers(...).then is not a function
controller/user.js
function getUsers(req, res){
userService.getUsers({
page_no:req.body.page_no,
previous:req.body.previous
}
)
.then(data => res.send(data));
};
service/user.js
const getUsers = function (data) {
let limit = 10; // number of records per page
let offset = 0;
let page = data.page_no; // page number
data = Users.findAll({where: {
DeletedAt: {
$eq: null
}
}})
const pages = Math.ceil(data.count / limit);
offset = limit * (page - 1);
const User = Users.findAll({
attributes: ['id', 'first_name', 'last_name', 'role_id','login','is_active'],
limit: limit,
order: [
['createdAt', 'DESC']
],
offset: offset,
$sort: { id: 1 },
where: {
DeletedAt: {
$eq: null
}
}
})
return User
};
Looks like you're using some ORM which returns a Promise.
When you return just User, the type of User is (probably, as you do not get error) a Promise so the function will return a promise and you can call .then method.
However when you return { User, pages }, you're not returning a promise but an Object and objects don't have then method, which is why you're getting the error.
When you return the object { User, pages }, you can change your code to extract the promise and call then method on it:
function getUsers(req, res){
const { User, pages } = userService.getUsers({
page_no:req.body.page_no,
previous:req.body.previous
})
// Call the then User which is a Promise
User.then(data => res.send(data))
};
Full code here: https://github.com/kenpeter/dl_r18_img_back
There is a nice package to hook pagination with mongoose:
According to this, it seems I can use populate with pagination, but I am not able to get it working.
list: function(page = 1, limit = 100){
return new Promise(function(resolve, reject){
let options = {
page: parseInt(page),
limit: parseInt(limit),
sort:{
createdDate: -1 //Sort by Date Added DESC
}
};
/*
Image
.paginate({}, options)
.then(function(res) {
resolve && resolve(res);
});
*/
// NOT WOKRING!!!!!
Image
.populate('category')
.execPopulate()
.paginate({}, options)
.then(function(res) {
resolve && resolve(res);
});
/*
Image
.find({})
..populate('category')
.exec()
.then(function(res) {
resolve && resolve(res);
});
*/
});
As you can see there are 2 commented-out code block Image.xxxxx. They are working individually.
How do I put them together?
The documentation says to put a populate option for populating.
Here is a snippet from the doc:
var options = {
select: 'title date author',
sort: { date: -1 },
populate: 'author',
lean: true,
offset: 20,
limit: 10 };
maybe use populate : "category"
I have a User model and a Follower model which has HasManyThrough relation with User for the follower and followee.
How can I change the default __get__followers method parameters?
I figured out that I can juse add a new remote method the normal way.
loopback.remoteMethod(
UserModel.prototype.getFollows,
{
description: 'Get the users who are followed by the user',
accepts: [
{arg: 'page', type: 'Number', http: {source: 'query'}, required: true}
],
returns: {arg: 'data', type: 'object'},
http: {verb: 'get', path: '/follows'},
isStatic: false,
}
);
Yet another way. Tree hasMany Leaves and we want to override the __get__leaves relation handler of a Tree.
/**
* Before Remote handler
*
* #param {Object} ctx - Context
* #param {Function} next - Callback function
*/
Tree.beforeRemote('**', function (ctx, unused, next) {
if (ctx.method.name === '__get__leaves') {
return Tree.getLeaves(ctx)
}
next()
})
/**
* Override Relation handler
*
* #param {Object} ctx - Context
*/
Tree.getLeaves = (ctx) => {
ctx.res.sendStatus(200)
}