Sequelize and querying on complex relations - node.js

I'm trying to understand how best to perform a query over multiple entities using Sequelize and Node.js.
I have defined a model "User" which has a belongsToMany relation with a model "Location". I then have a model "Asset" which also has a belongsToMany relation with "Location". When I have an instance of a User I would like to fetch all Assets that are associated with Locations that the User is associated with.
I tried the following which doesn't seem to work...
user.getLocations().then(function(userLocations) { return Asset.findAll({ where: { "Locations" : { $any : userLocations } }) })
Could anyone offer any suggestions?

Try this query:
User.findById(user_id, {
include: [{
model: Location,
required: true
}]
}).then(user => Asset.findAll({
where: {
user_id: user.id,
location_id: {
$in: user.locations.map(location => location.id)
}
}
})).then(assets => {
// The rest of your logic here...
});

This was the final result...
User.findById(user_id, {
include: [{
model: Location,
as: 'Locations', // Was needed since the original relation was defined with 'as'
required: true
}]
}).then(user => Asset.findAll({
include: [{
model: Location,
as: 'Locations',
where: {
id: {
$in: user.Locations.map(location => location.id)
}
}
}]
})).then(assets => {
// The rest of your logic here...
});

Related

sequelize join up the tables

Working on an already setup db and also new to sequelize. I have like four tables
customer
library
books
excerpt
excerpt has redundant book_id from books. books has redundant library_id from library, and library has redundant customer_id from customer.
They were not declared foreign in db but its kinda acting the foreign key type and I make the association when I hit the orm functions.
My question:
I have a customer_id and book_id, and I have to fetch excerpt records based on book_id but have to go top the db to match the customer_id as well. (for multi tenancy)
the flow is: excerpt> book_id - books > library_id - library > customer_id - customer
I have written this code but its not working
async read_book_id(customer_id, book_id) {
const excerpts = await this.model.findAll({ // this.model being the excerpt model
where: { book_id: book_id},
include: [
{
model: this.db.Books,
association:
this.model.belongsTo(this.db.Books, {
foreignKey: 'book_id',
}),
where: { book_id: book_id},
include: [
{
model: this.db.Library,
association: this.model.belongsTo(this.db.Library, {
foreignKey: 'library_id',
}),
where: { customer_id: customer_id },
},
],
},
],
});
Basically this is something extended from this another code i wrote which is working for me. If I have to check only one level above thats working fine for me e.g
// reading books based on library_id
async read(customer_id, library_id) {
const books= await this.model.findAll({
where: {
library_id: library_id,
},
include: [
{
model: this.db.Library,
association: this.model.belongsTo(this.db.Library, {
foreignKey: 'library_id',
}),
where: { customer_id: customer_id },
},
],
});
}
This works fine for me.
Can you please tell how to run the first code block?
Assuming you already registered all associations just like I suggested in the comments above you need to indicate the correct models in include options along with correct conditions:
const excerpts = await this.model.findAll({ // this.model being the excerpt model
where: { book_id: book_id},
include: [
{
model: this.db.Books,
include: [
{
model: this.db.Library,
where: { customer_id: customer_id },
required: true
},
],
},
],
});```

Unable to properly access additional fields on junction table

Apologies if this is obvious as I don't have much experience with sequelize yet. I am trying to get a field that is in the junction table CustomerContact. Currently I am getting a contact and then retrieving its attached customers
const contactList: Contact[] = await db()
.Contact.newActiveScope()
.withCustomers()
.findAll();
const mainCustomer: Customer = contactList[i].getMainCustomer();
const mainCustomerContacts = mainCustomer && mainCustomer?.customerContacts;
const thisCustomerContactRole = mainCustomerContacts?.find((c) => c.contactId === contact.id)?.role;
Outputting the mainCustomer gets me the following information
{
id: 'a94fd13a-1fdd-11ec-a12c-121c563gb2f5',
accountName: 'Test Tests',
CustomerContact: { role: 1, relationship: 2, status: 'active' }
}
I want to use the properties in CustomerContact, but trying to access it gives me a typescript error, saying CustomerContact does not exist on type ContactWithCustomerContactStatic, which has the following shape.
export type ContactWithCustomerContactStatic = Partial<Customer> &
Pick<CustomerContact, 'role' | 'relationship'>;
I associate the Customer in the Contact Model
this.belongsToMany(Customer, {
as: 'customers',
foreignKey: 'contactId',
otherKey: 'customerId',
through: CustomerContact as CustomerContactStatic,
});
this.addScope('withCustomers', {
include: [
{
as: 'customers',
model: Customer as CustomerStatic,
where: { isArchived: false },
required: false,
},
],
});
and vice versa
Customer.belongsToMany(Contact, {
as: 'contacts',
through: CustomerContact,
foreignKey: 'customerId',
otherKey: 'contactId',
});
Customer.addScope('contacts', () => ({
include: [
{
as: 'contacts',
attributes: ['id', 'firstName', 'lastName'],
model: (Contact as ContactStatic).newScope(),
required: false,
through: {
where: { status: CustomerContactStatus.active },
},
},
],
}));
I can technically retrieve the values, but the typescript error gives me pause.
I'm positive I'm missing something but having trouble determining what that something is. Any help would be appreciated!
I believe Typescript is showing this error because of the way you are declaring the ContactWithCustomerContactStatic type.
export type ContactWithCustomerContactStatic = Partial<Customer> &
Pick<CustomerContact, 'role' | 'relationship'>;
When declaring this way you say to Typescript that ContactWithCustomerContactStatic type has the following shape
interface ContactWithCustomerContactStatic {
// all properties from Customer interface comes here
role // it's in the root
relationship // it's also in the root
}
But, they are not in the root of the object, given the output that you presented
{
id: 'a94fd13a-1fdd-11ec-a12c-121c563gb2f5',
accountName: 'Test Tests',
CustomerContact: { role: 1, relationship: 2, status: 'active' }
}
This should work
type ContactWithCustomerContactStatic = Partial<Customer>
& { customerContact: Pick<CustomerContact, 'role' | 'relationship'>
}

Node.js sequelize model - can I define order of records on the model?

I am newish to node, and can't figure out how to only return 1 record of a hasMany relationship, based on an attribute.
There's a user, level, and levels_user table.
On user, I want to include 1 levels_user where level_id is highest.
Can I put a condition on this model file, something like:
order_by: ['level_id', 'DESC']
LevelsUsers Model
'use strict';
module.exports = (sequelize, DataTypes) => {
let LevelsUsers = sequelize.define('LevelsUsers', {
user_id: DataTypes.INTEGER,
level_id: DataTypes.INTEGER,
created_at: DataTypes.DATE,
updated_at: DataTypes.DATE,
},{
timestamps: false,
freezeTableName: true,
schema: "public",
tableName: "levels_users"
});
return LevelsUsers;
};
Users model association:
Users.hasMany(models.LevelsUsers, {
as: 'levels_users',
targetKey: 'id',
foreignKey: 'user_id',
});
This is my call:
users.getProfileByUserId = (req, res) => {
models.Users.findOne({
where: {id: req.params.userid},
include: [
{
model: models.LevelsUsers,
as: 'levels_users',
limit: 1,
}
]
order: []
}).then(user ....
I tried adding:
`order: [
[ models.LevelsUsers , 'level_id', 'DESC']
]`
Did not work and I think it's evaluated after limit: 1 anyways.
Can I put order_by on the model, to return highest to lowest by level_id each time? If not, what's a better way to accomplish returning only the highest levels_users record where level_id is highest?
The hasMany property is more suited when you want to include all the levels in the user object. In your case, I would advise to just pull the user without it's levels, and then do a second request to pull the higher level linked to that user by querying directly the LevelsUsers model :
models.LevelsUsers.findAll({
where: {
user_id: user.id,
},
order: ['level_id', 'DESC'],
limit: 1,
});
Well i reading over the documentation of sequelize for working with ordering and limit inside the includes tag.
Update query
users.getProfileByUserId = (req, res) => {
models.Users.findOne({
where: {id: req.params.userid},
include: [
{
model: models.LevelsUsers,
as: 'levels_users',
order: [
[ { model: models.LevelsUsers }, 'level_id', 'DESC']
],
limit: 1,
}
]
}).then(user ....
For references go over the following links - https://github.com/sequelize/sequelize/issues/4553#issuecomment-341261957

Sequelize: Joining Many to Many tag table with another table in postgresql

I am trying to setup many to many relationship in Sequelize for my Postgres tables using a tag (lookup) table. I was successful in setting up the relationship.
Now my lookup table, has an additional field which is a foreign key to another table. I want the data for that table to be included in my result set. This is where I am having hard time figuring out the syntax.
My models:
Models.user = sequelize.define('user', { //attributes});
Models.school = sequelize.define('school', { //attributes });
Models.role = sequelize.define('role', { //attributes });
Models.userSchools = sequelize.define('user_school', {
user_school_id: { type: Sequelize.INTEGER, primaryKey: true }
});
Models.user.belongsToMany(Models.school, { through: Models.userSchools, foreignKey: 'user_id', otherKey: 'school_id' });
Models.school.belongsToMany(Models.user, { through: Models.userSchools, foreignKey: 'school_id', otherKey: 'user_id' });
Models.userSchools.belongsTo(Models.role, { foreignKey: 'role_id' });
Working query: This query gives me all the users and their schools.
Models.user.findAll({
where: {
//condition
},
include: [Models.school]
})
Now I want to include the role for each schools. None of the below queries are working.
Models.user.findAll({
where: {
//conditions
},
include: [Models.schools, Models.role]
})
Above query is throwing an error saying role is not associated to user, which is expected.
Models.user.findAll({
where: {
//conditions
},
include: [Models.schools, {model: Models.userSchools, include: Models.role}]
})
This query is also throwing an error:
"userSchools is not associated to user".
Can any one help with syntax to how I can get the role information from the userSchools model?
Edit:
I have add a "through" condition which gets me the role_id but not the related role data.
Models.user.findAll({
where: {
//conditions
},
include: [{
model: Models.school,
as: 'schools',
through: {
attributes: ['role_id']
}
}]
})

Sequelize: how to implement a search based on associated keywords?

I am looking to return all articles from the database associated with one or more keywords, but I am not sure there right way to go about this?
I am using Sequelize 3.x, with node.js 3.7.0.
The data model looks at follows:
const Article = sequelize.define('article', {
...
}
const Keyword = sequelize.define('keyword', {
name: Sequelize.STRING,
lang: Sequelize.STRING
}
const ArticleKeyword = sequelize.define('article_keyword', {
articleId: Sequelize.INTEGER,
keywordId: Sequelize.INTEGER
}
(Article).belongsToMany(
Keyword, { through: ArticleKeyword, as: 'keyword' });
(Keyword).belongsToMany(
Article { through: ArticleKeyword, as: 'article' });
Then the query I tried:
var keywordFilter;
if (req.body.keywords) {
var keywords = req.body.keywords);
if (typeof keywords === 'string') {
keywords = keywords.split(/ *, */);
}
keywordFilter = { name: { $in: keywords } };
}
Article.findAll({
where: {
deleted: false
},
include: [{
model: Keyword,
as: 'keywords',
where: keywordFilter,
attributes: ['name'],
through: {
attributes: []
}
}]
}).then(function(articles) {
...
});
The issue I am finding here is rather than selecting just the articles with the matching keywords it returns all the articles and then simply selects the keywords specified in the query for the results.
Can anyone suggest the right way to go about this?
Hi can you try passing
require:true
in the include block for inner join and check
Ref : https://stackoverflow.com/a/31680398/4583460

Resources