Implementation of search function of Sequelize - node.js

Before the question, the post table of my current project has the following structure.
module.exports = class Post extends Model {
static init(sequelize) {
return super.init({
title: {
type: DataTypes.TEXT,
allowNull: false,
},
desc: {
type: DataTypes.TEXT,
},
ingredient: {
type: DataTypes.TEXT,
allowNull: false,
},
recipes: {
type: DataTypes.TEXT,
allowNull: false,
},
tips: {
type: DataTypes.TEXT,
},
tags: {
type: DataTypes.TEXT,
},
}, {
modelName: 'Post',
tableName: 'posts',
charset: 'utf8mb4',
collate: 'utf8mb4_general_ci',
sequelize,
});
}
static associate(db) {
db.Post.belongsTo(db.User);
db.Post.belongsToMany(db.Hashtag, { through: 'PostHashtag' });
db.Post.hasMany(db.Comment);
db.Post.hasMany(db.Image);
db.Post.belongsToMany(db.User, { through: 'Like', as: 'Likers' });
}
};
I implemented the hashtag post search function using the following router.
router.get('/:tag', async (req, res, next) => {/hashtag/1
try {
const where = {};
if (parseInt(req.query.lastId, 10)) {
where.id = { [Op.lt]: parseInt(req.query.lastId, 10)};
}
const posts = await Post.findAll({
where,
limit: 10,
order: [['createdAt', 'DESC']],
include: [{
model: Hashtag,
where: { name: decodeURIComponent(req.params.tag) },
}, {
model: User,
attributes: ['id', 'nickname'],
}, {
model: User,
as: 'Likers',
attributes: ['id'],
}, {
model: Comment,
include: [{
model: User,
attributes: ['id', 'nickname'],
}],
}, {
model: Image,
}]
});
res.status(200).json(posts);
} catch (error) {
console.error(error);
next(error);
}
});
The execution result was successful, and I was able to get the results I wanted.
However, we thought that it was not enough to search only hashtags, so we added the following conditions to additionally search the title and contents of the post.
router.get('/:tag', async (req, res, next) => {
try {
const where = {
title: { [Op.like]: "%" + decodeURIComponent(req.params.tag) + "%" }, // Search for the title of a post
recipes: { [Op.like]: "%" + decodeURIComponent(req.params.tag) + "%" }, // Search the content of the post
};
if (parseInt(req.query.lastId, 10)) {
where.id = { [Op.lt]: parseInt(req.query.lastId, 10)};
}
const posts = await Post.findAll({
where,
limit: 10,
:
:
After that, the result of running the code did not load any posts, contrary to what I expected.
What part should be modified to implement a search function that includes the title, content, and hashtag of a post?

I guess the problem with your function is that you are using where with AND
instead of OR.
Post.findAll({
where: {
[Op.and]:
[{ title: {[Op.like]...} },
{ recipes: {[Op.like]...} }], // (title= %S) AND (recipes = %S)
...
)}
// that is the same as your example
const where = {
// Search for the title of a post
title: { [Op.like]: "%"...,
// Search the content of the post
recipes: { [Op.like]: "%"... },
};
// this is an AND operation
What you should do is use OR. More info: operators
[Op.or]: [{ a: 5 }, { b: 6 }], // (a = 5) OR (b = 6)
In your case:
Post.findAll({
where: {
[Op.or]:
[{ title: {[Op.like]...} },
{ recipes: {[Op.like]...} }], // (title= %S) AND (recipes = %S)
...
)}
That is also not the proper way of searching. Maybe you should consider looking for full-text search implementations like Elastic Search or look if the database you are using has this feature built-in.

Related

Can't update a specific column in Sequelize

I have a model. It is for the intermediate(pivot) table.
UserCars.init({
carId: DataTypes.INTEGER,
userId: DataTypes.INTEGER,
title: DataTypes.STRING,
}, {
timestamps: false,
sequelize,
modelName: 'UserCars',
});
and here is my migration for this:
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('UserCars', {
carId: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: 'Cars',
key: 'id'
},
},
userId: {
allowNull: false,
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
},
},
title: {
type: Sequelize.STRING
},
}, {
uniqueKeys: {
Items_unique: {
fields: ['carId', 'userId']
}
}
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('UserCars');
}
};
And I'm doing this below to create/update it:
userCar = await UserCars.findOne({
where: {
carId: 10,
userId: req.user.id,
}
});
if(userCar) {
userCar.userId = 20; // <--- This doesn't change
userCar.title = 'some other thing'; // <--- This changes
await userCar.save();
} else {
userCar = await UserCars.create({
userId: 20,
title: 'something'
});
}
The problem is, the title is being updated but the userId is not.
(I believe) this is due to constraints, you cannot use instance method to update FK value.
You need to use M-N association functions, or otherwise you could use raw SQL.
const car = await Car.findByPk(10);
const user = await User.findByPk(newValue);
// This also takes care of deleting the old associations
await car.setUsers(user, {
through: {'title': 'new value'}
});
I hope the upsert function is implemented in the future.
ref: https://github.com/sequelize/sequelize/issues/11836

How to server side filtering each field of a table in sequelize (nodejs orm)

I have some trouble to find out how i have to do the filtering part in sequelize ORM.
I already did, server-side pagination and sorting, Following this tutorial for Angular https://blog.angular-university.io/angular-material-data-table/
I'am using Sequelize 5.8.5 but not in typescript.
But, in my case, the backend is in nodejs using Sequelize. And i want to do a search of the filter string parameter on each fields of my joined table.
If "string" like field1 OR "string" like field2 OR .... etc
Here is my router.get in sequelize.
router.get('', auth.checkToken, function(req, res) {
let options = {
include: [{
model: models.intervention,
attributes: ['id','start_date','end_date','agent_id','state_id'],
required: false,
include: [{
model: models.agent,
required: false,
},
{
model: models.state,
required: false,
},
{
model: models.intervention_participation,
required: false,
include: [{
model: models.agent,
attributes: ['id','access_token','agent_type_id','firstname','lastname','mail','password'],
required: false,
include: [{
model: models.shift_do,
attributes: ['id','shift_id','agent_id','shift_date'],
required: false,
on: {
'agent_id': {
[Op.eq]: Sequelize.col('intervention->agent.id')
},
'shift_date': {
[Op.eq]: Sequelize.col('intervention.start_date')
},
},
include: [{
model: models.shift,
required: false,
}]
}]
}]
},
{
model: models.operating_range_do,
required: false,
include: [{
model: models.operating_range,
required: false,
}]
},
{
model: models.equipment_intervention,
required: false,
include: [{
model: models.equipment,
required: false,
include: [{
model: models.equipment_type,
required: false,
include: [{
model: models.work_field,
required: false,
}]
},
{
model: models.equipment_location,
required: false,
include: [{
model: models.structure,
required: false,
},
{
model: models.local,
required: false,
},
{
model: models.place,
required: false,
}]
}]
}]
},
{
model: models.intervention_helper,
required: false,
include: [{
model: models.external_actor,
required: false,
}]
}]
},
{
model: models.work_type,
required: false
},
{
model: models.di_externe,
required: false
}]
};
if (req.query.filter) {
options.where = {
$or: [
{ id: { $like: req.query.filter}},
{ title: { $like: req.query.filter}},
{ description: { $like: req.query.filter}},
{ start_date: { $like: req.query.filter}},
]
};
}
if (req.query.sort) {
options.order = [['id', req.query.sort || 'DESC']];
}
if (req.query.page && req.query.pageSize) {
options.offset = req.query.page * req.query.pageSize;
options.limit = parseInt(req.query.page, 10) + parseInt(req.query.pageSize, 10);
}
models.intervention_maincourante
.findAll(options)
.then(all => {
res.send(all);
});
});
its this part who don't work :
if (req.query.filter) {
options.where = {
$or: [
{ id: { $like: req.query.filter}},
{ title: { $like: req.query.filter}},
{ description: { $like: req.query.filter}},
{ start_date: { $like: req.query.filter}},
]
};
}
I also tried this way because sometimes the alias for operators didn't works? But it didn't change anything.
[Op.or]: [
{ id: { [Op.like]: "%" + req.query.filter + "%"}},
{ title: { [Op.like]: "%" + req.query.filter + "%"}},
{ description: { [Op.like]: "%" + req.query.filter + "%"}},
{ start_date: { [Op.like]: "%" + req.query.filter + "%"}},
]
Then i tried :
options.where = {
[Op.or]: [{
title: {
[Op.like]: "%" + req.query.filter + "%",
},
description: {
[Op.like]: "%" + req.query.filter + "%",
},
start_date: {
[Op.like]: "%" + req.query.filter + "%",
}
}]
};
What's in the documentation :
{
[Op.or]: [
{
title: {
[Op.like]: 'Boat%'
}
},
{
description: {
[Op.like]: '%boat%'
}
}
]
}
Thanks a lot, if someone know how to do it.
As you suggested in the comments that query doesn't logs with filter. So assuming there should be any error.
Try to catch the error and log it to identify the issue
models.intervention_maincourante
.findAll(options)
.then(all => {
res.send(all);
}).catch(err => {
console.log("err", err);
});
with the help of this, you will be identifying the exact root cause.
Update:
Based on the error, Please check your like clause.
Your like clause should be something like this. Also, make sure you are referring right column names.
options.where = {
[Op.or]: [{
title: {
[Op.like]: `%${req.query.filter}%`,
},
description: {
[Op.like]: `%${req.query.filter}%`,
},
start_date: {
[Op.like]: `%${req.query.filter}%`,
}
}]
};
In Sequelize ORM when you use $like operator you can pass the search string directly or wrapped inside % OR _. if you want to match some part of the string you can use $substring operator.
if your query is a plain string like this 'examplematch' $like operator will look for an exact match.
Refer this doc https://sequelize.org/master/manual/querying.html#operators
It's working.
const whereStatement = {}
const { status, startDate, endDate, search } = req.query;
if (status) {
whereStatement.status = { [Op.eq]: status };
}
if (startDate && endDate) {
whereStatement.createdAt = { [Op.between]: [startDate, endDate] }
}
if (search) {
whereStatement.username = { [Op.like]: `%${search}%` }
}
let Datefilter = "";
if (startDate && endDate) {
Datefilter =
startDate && endDate ? {
createdAt: {
$gte: moment(startDate).startOf("day").toDate(),
$lte: moment(endDate).endOf("day").toDate(),
},
} : {};
console.log("startDateto", Datefilter);
} else if (startDate) {
console.log("startDate");
Datefilter = startDate
? {
createdAt: {
$gte: moment(startDate).startOf("day").toDate(),
$lte: moment(new Date()).endOf("day").toDate(),
},
} : {};
}
feedback = await feedbackModel.paginate({
where: whereStatement,
Datefilter
});

sequelize includes not returning data

I am trying to get data mapped with empid from 2 tables viz-skillsrepo and certifications and render it to frontend,I am getting all data from certifications table,but i need data only of the empid which i send in request
tried using includes method
app.get('/updateprofile/:id', function (req, res) {
db.skillsrepo.find({
where: { employeeId: req.params.id },
include: [
{
model: db.certifications
},
{
model: db.attachments
},
{
model: db.project
}
]
}).then(result => {
if (result != null) {
res.render('updateprofile', {
user: result,
eid: req.params.id,
});
console.log("**********", result)
}
})
});
This is the Schema:
var skillsrepo = exports.skillsrepo = connection.define('skillsrepo', {
firstname: {
type: Sequelize.STRING(23)
},
lastname: {
type: Sequelize.STRING(23)
},
highQual: {
type: Sequelize.STRING(23)
},
fivekeystrenghts: {
type: Sequelize.TEXT
},
domain: {
type: Sequelize.STRING(23)
},
technicalskills: {
type: Sequelize.STRING(23)
},
typesoftesting: {
type: Sequelize.STRING(23)
},
employeeId: {
type: Sequelize.INTEGER(11),
references: {
model: 'employeemastertablee',
key: 'id'
}
}
});
skillsrepo.hasMany(certifications, {
foreignKey: "employeeId"
});
certifications.belongsTo(skillsrepo, {
foreignKey: "employeeId"
});

Sequelize: OR between parent where clause and child where clause

I have 2 models:
const User = sequelize.define('User', {
email: {
type: DataTypes.STRING,
},
password: {
type: DataTypes.STRING,
},
});
User.associate = (models) => {
User.hasOne(models.Profile, {
foreignKey: {
name: 'user_id',
},
});
};
const Profile = sequelize.define('Profile', {
name: {
type: DataTypes.STRING,
},
avatar: {
type: DataTypes.STRING,
},
}, {
tableName: 'profiles',
freezeTableName: true,
timestamps: false,
});
Profile.associate = (models) => {
Profile.belongsTo(models.User, {
foreignKey: {
name: 'user_id',
},
});
};
I would like to get all users where the email address OR the name matches a certain condition. Something like:
User
.all({
where: {
email: {
$like: filter
},
},
include: [{
model: Profile,
where: {
name: {
$like: filter
},
},
}],
})
.then(users => res.status(200).send(users))
.catch(error => {
return res.sendStatus(500);
});
but it returns all users where user.email AND profile.name matches the condition. I would like to have OR between the 2 where clause.
Is it possible?
Note:
I'm using Sequelize 4.0.0.
Update:
In case of anybody else struggles with this, the solution is:
User
.all({
where: {
$or: {
email: {
$like: filter
},
'$Profile.name$': {
$like: filter
}
}
},
include: [{
model: Profile,
}],
})
.then(users => res.status(200).send(users))
.catch(error => {
return res.sendStatus(500);
});
In case if anyone else is looking for this, here is how I managed to solve it:
User
.all({
where: {
$or: {
email: {
$like: filter
},
'$Profile.name$': {
$like: filter
}
}
},
include: [{
model: Profile,
}],
})
.then(users => res.status(200).send(users))
.catch(error => {
return res.sendStatus(500);
});
Thanks #Ninja Coding for confirming the solution.

Sequelize Many to Many failing with 'is not associated to' when trying to associate entries?

I am having a problem with my many to many configuration with Sequelize, where it complains that site_article_keyword is not associated to article_keyword. The code below represents a minimal test case to try to understand what I am doing wrong (I hoped to provide something smaller, but this is what I have). I am using bluebird for the Promise API.
const Sequelize = require('sequelize');
var sequelize = new Sequelize(undefined, undefined, undefined, {
dialect: 'sqlite',
storage: './mydatabase',
});
const SiteArticle = sequelize.define('site_article', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
ownerId: {
type: Sequelize.INTEGER,
field: 'owner_id'
},
title: Sequelize.STRING
// other fields omitted
}, {
timestamps: true
});
const ArticleKeyword = sequelize.define('article_keyword', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: Sequelize.STRING,
language: Sequelize.STRING
// other fields omitted
}, {
timestamps: true
});
const SiteArticleKeyword = sequelize.define('site_article_keyword', {
siteArticleId: {
type: Sequelize.INTEGER,
field: 'site_article_id',
references: {
model: SiteArticle,
key: 'id'
}
},
articleKeywordId: {
type: Sequelize.INTEGER,
field: 'article_keyword_id',
references: {
model: ArticleKeyword,
key: 'id'
}
}
// other fields omitted
}, {
timestamps: true
});
(ArticleKeyword).belongsToMany(
SiteArticle, { through: SiteArticleKeyword });
(SiteArticle).belongsToMany(
ArticleKeyword, { through: SiteArticleKeyword });
That's the model defined, now for trying to create the source and destination entries, that I then want to associate. The failure happens on the line where I call ArticleKeyword.findAll():
sequelize.sync({}).then(function() {
// populate some data here
let siteArticle;
SiteArticle.create({
ownerId: 1,
title: 'hello world'
}).then(function(entry) {
siteArticle = entry;
console.log('site article: ', JSON.stringify(entry, undefined, 2));
return ArticleKeyword.findOrCreate({
where: {
name: 'keyword1',
language: 'en'
}
});
}).spread(function(entry, success) {
console.log('article keyword: ', JSON.stringify(entry, undefined, 2));
return siteArticle.addArticle_keyword(entry);
}).spread(function(entry, success) {
console.log('site article keyword: ', JSON.stringify(entry, undefined, 2));
const siteArticleId = 1;
const language = 'en';
return ArticleKeyword.findAll({
where: {
language: language,
},
include: [{
model: SiteArticleKeyword,
where: {
siteArticleId: siteArticleId
}
}]
});
}).then(function(articleKeywords) {
if (articleKeywords) {
console.log('entries: ', JSON.stringify(articleKeywords, undefined, 2));
} else {
console.log('entries: ', 'none');
}
}).catch(function(error) {
console.log('ERROR: ', error);
}.bind(this));
}).catch(function(error) {
console.log(error);
});
I am basing my code on the 'Mixin BelongsToMany' example in the Sequelize documentation.
Can anyone suggest what I am doing wrong?
The issue turns out that the reason site_article_keyword is not associated is because it is the association! With that in mind the code becomes:
return ArticleKeyword.findAll({
where: {
language: language,
},
include: [{
model: SiteArticle,
as: 'SiteArticle',
siteArticleId: siteArticleId
}]
});
BTW one minor tweak to my code, is in the inclusion of 'as' to the belongsToMany:
ArticleKeyword.belongsToMany(
SiteArticle,
{ through: SiteArticleKeyword, as: 'SiteArticle' }
);
SiteArticle.belongsToMany(
ArticleKeyword,
{ through: SiteArticleKeyword, as: 'ArticleKeyword' }
);
This allows for addArticleKeyword() instead of addArticle_Keyword().
In through relationships the following should work
ArticleKeyword.findAll({
include: [{
model: SiteArticle,
through: {
attributes: ['createdAt', 'startedAt', 'finishedAt'],
where: {
siteArticleId: siteArticleId
}
}
}]
});

Resources