in nested eager loading sequelize query, fetch only some attributes - javascript

Here's my query for fetching shows from a database, plus its associated venue and bands.
I really only want to get the names of the bands and venue. (name is the field in both of those tables.) The code below is fetching the whole record, though, and not just the field that I want.
const getAllShows = async (req, res) => {
try {
const shows = await Show.findAll({
include: [
{ model: User, as: 'bands', through: { attributes: ['name'] } },
{ model: Venue, through: { attributes: ['name'] }}
],
});
res.status(200).send(shows);
}
catch(err) {
res.send(400);
}
}

The attributes is misplaced - it doesn't belong under the through (btw, depending on your associations, you may not even need through).
Try changing like this:
{ model: User, as: 'bands', attributes: ['name']},
You might also consider field aliases, like this:
{ model: User, as: 'bands', attributes: [['name', 'band_name']]},
hth

Related

Sequelize self-referential many-to-many; how to specify what I'm joining on

I have a Sequelize structure that associates my User table to itself through a Follow table, with follower and following. I'm 99% certain that I have all of the necessary pieces for this to be working the way I expect it to; I've the aliased
User.belongsToMany(models.User, { through: models.Follow, as: 'Followers', foreignKey: 'follower'});
User.belongsToMany(models.User, { through: models.Follow, as: 'Following', foreignKey: 'following'});
as well as
User.hasMany(models.Follow, { foreignKey: 'following' });
User.hasMany(models.Follow, { foreignKey: 'follower' });
and
Follow.belongsTo(models.User, { foreignKey: 'following', as: 'Following' });
Follow.belongsTo(models.User, { foreignKey: 'follower', as: 'Follower' });
And I'm able to call
User.findByPk(id, {
include: {
model: Follow
}
});
which returns a single user and an array of Follow entries.
The problem I'm having is, Sequelize seems to be defaulting to creating the Follow query as where: { followING: User.id } rather than where: followER, such that if I have 1 user FOLLOWING themselves and 2 other people, but only FOLLOWED BY themselves, it only returns 1 result from my Follow table. As I was cycling through the primary keys for the handful of users in my seed, only the following value in my results change, such that I'm only ever returning a users' followers, not the other users that user is following.
Is there a way to specify in an include which specific column I'm trying to join on, when multiple columns match the Sequelize object to which I'm joining?
I understand that worst case scenario I can always skip the User step and go straight to
Follow.findAll({
where: {
follower: id
}
})
But that restricts my immediate access to the User object and I'd have to write an additional query, which seems cumbersome considering a self-associated many-to-many capability exists.
If you wish to get a user with followers or with following users you don't need to indicate Follow table. All you need is to indicate an alias of a required association:
User.findByPk(id, {
include: {
model: User,
as: 'Followers'
}
});
User.findByPk(id, {
include: {
model: User,
as: 'Following'
}
});
In case you need to include Follow directly you need to add an associations like this:
User.hasMany(models.Follow, { as: 'FollowerLinks', foreignKey: 'follower'});
User.hasMany(models.Follow, { as: 'FollowingLinks', foreignKey: 'following'});
And you also should indicate an alias so that way Sequelize will know what association to use:
User.findByPk(id, {
include: {
model: Follow,
as: 'FollowerLinks'
}
});

Retrive some columns of relations in typeorm

I need to retrieve just some columns of relations in typeorm query.
I have an entity Environment that has an relation with Document, I want select environment with just url of document, how to do this in typeorm findOne/findAndCount methods?
To do that you have to use a querybuilder, here's an example:
return this.createQueryBuilder('environment') // use this if the query used inside of your entity's repository or getRepository(Environment)...
.select(["environment.id","environment.xx","environment.xx","document.url"])
.leftJoin("environment.document", "document")
.where("environment.id = :id ", { id: id })
.getOne();
Sorry I can't add comment to post above. If you by not parsed data mean something like "environment.id" instead of "id"
try this:
return this.createQueryBuilder("environment")
.getRepository(Environment)
.select([
"environment.id AS id",
"environment.xx AS xx",
"document.url AS url",
])
.leftJoin("environment.document", "document")
.where("environment.id = :id ", { id: id })
.getRawOne();
Here is the code that works for me, and it doesn't require using the QueryBuilder. I'm using the EntityManager approach, so assuming you have one of those from an existing DataSource, try this:
const environment = await this.entityManager.findOne(Environment, {
select: {
document: {
url: true,
}
},
relations: {
document: true
},
where: {
id: environmentId
},
});
Even though the Environment attributes are not specified in the select clause, my experience is that they are all returned in the results, along with document.url.
In one of the applications that I'm working on, I have the need to bring back attributes from doubled-nested relationships, and I've gotten that to work in a similar way, shown below.
Assuming an object model where an Episode has many CareTeamMembers, and each CareTeamMember has a User, something like the code below will fetch all episodes (all attributes) along with the first and last name of the associated Users:
const episodes = await this.entityManager.find(Episode, {
select: {
careTeamMembers: {
id: true, // Required for this to work
user: {
id: true,
firstName: true,
lastName: true,
},
}
},
relations: {
careTeamMembers: {
user: true,
}
},
where: {
deleted: false,
},
});
For some reason, I have to include at least one attribute from the CareTeamMembers entity itself (I'm using the id) for this approach to work.

How to disambiguate between multiple associations between the same models in Sequelize

I have three models ā€” Book, User and Institution ā€” which are associated to one another as follows:
Books are associated to Institutions via a Book_Institution join table (many to many relationship)
Book.belongsToMany(models.Institution, { through: 'Book_Institution' })
and
Institution.belongsToMany(models.Book, { through: 'Book_Institution' })
Users can be associated to Institutions in two ways: as reader or author. This is done via two join tables: Author_Institution and Reader_Institution:
Institution.belongsToMany(models.User, { through: 'Author_Institution' })
Institution.belongsToMany(models.User, { through: 'Reader_Institution' })
and
User.belongsToMany(models.Institution, { through: 'Author_Institution' })
User.belongsToMany(models.Institution, { through: 'Reader_Institution' })
(Each time leaving out foreignKey for brevity.)
I want to query the Book model to find all books that belong to an author. Sequelize provides the include option to easily join two associated tables. The problem Iā€™m stuggling with is that using include as shown below defaults to the Reader_Institution association. How can I specify which association should be used?
getBooks: (obj, args, context) => {
const { user } = context
return Book.findAll({
attributes: ['id', 'path'],
include: [{
include: [{
attributes: ['id'],
model: User,
where: { id: user }
}],
model: Institution,
required: true // inner join
}]
})
}
Thanks in advance for your help.
I use as which allows you to reference the relationship through that alias.
Institution.belongsToMany(models.User, {
through: 'Author_Institution', // many-to-many relationship table name
as: 'AuthorInstitution' // alias
})
With your models set up this way, you can use as to to specify which relationship you want to include when querying.
getBooks: (obj, args, context) => {
const { user } = context
return Book.findAll({
attributes: ['id', 'path'],
include: [{
include: [{
attributes: ['id'],
model: User,
where: { id: user },
as: 'AuthorInstitution'
}],
model: Institution,
required: true // inner join
}]
})
}
Also, with this methodology, it allows you you to reference the relationship data via the as, so you can do book.AuthorInstitution and it will be the value of that object.

Express and sequelize: eager loading belongs to many associations hangs application

I'm attempting to eager load a belongs-to-many association where I am loading three nested associations. Here are the models, which result in three database tables programs, programDates and peopleProgramDates
program.js:
module.exports = function(sequelize, DataTypes) {
const Program = sequelize.define('program', {
name: DataTypes.STRING
});
Program.associate = ({programDate}) => {
Program.hasMany(programDate);
};
return Program;
};
program_date.js:
module.exports = function(sequelize, DataTypes) {
const ProgramDate = sequelize.define('programDate', {
date: DataTypes.DATEONLY,
volunteerLimit: DataTypes.INTEGER
}, {
indexes: [
{
unique: true,
fields: ['programId', 'date']
}
]
});
ProgramDate.associate = ({program, person}) => {
ProgramDate.belongsTo(program);
ProgramDate.belongsToMany(person, {through: 'peopleProgramDates'});
};
return ProgramDate;
};
In my controller, I want to return an object with all of the programs, programDates and peopleProgramDates:
const {bus, family, person, volunteerType, program, programDate} = require('../models');
exports.get = (request, response) => {
return Promise.all([
bus.findAll({ include: [{model: family, include: [person]}] })
.then(buses => buses.map(addBusCount)),
volunteerType.findAll({include: [person]})
.then(volunteerTypes => volunteerTypes.map(addVolunteerCount)),
// this query hangs the application
program.findAll( { include: [{ model: programDate, include: [{association: 'peopleProgramDates'}] }]} )
.then(programs => programs.map(processPrograms))
])
.then(([buses, volunteerTypes, programs]) =>
response.render('pages/register', {
buses,
volunteerTypes,
programs
})
);
};
At the moment, processPrograms() is a function that simply returns the same array of objects, and so should not be relevant here. addBusCount and addVolunteerCount should similarly not be relevant.
I think the issue may be that peopleProgram dates is not a real sequelize model, but the result of the the belongsToMany through: association on ProgramDate.
This post seems to suggest I can use the association: property in order to load the data from the through association, however the query hangs the application.
If I remove the join table from the query, then the data loads fine:
program.findAll( { include: [programDate] } )
Bonus points: Ultimately what I really need is simply a count of peopleProgramDates returned with the programDate objects. Perhaps I can simply define such on the programDates model, however perhaps we can address that in a separate question. Nevertheless, if there is a compelling reason to use this approach, such as performance, then maybe we should go that way after all.
The solution was to add an alias to the belongsToMany through association:
// program_date.js
ProgramDate.belongsToMany(person, {through: 'peopleProgramDates', as: 'peopleProgDates'});
And then reference the alias in the include property:
program.findAll( { include: [{ model: programDate, include: [{association: 'peopleProgDates'}] }]} )

How to load associations conditionaly in sequlize.js?

Post.findAndCountAll({
include: [{
model: models.like,
where: { userId: req.authSession.user.id }
]}
}).then( collection => {
onSucess(collection);
});
This will load posts if they have an associated like from the user. However what I want is loading all the user's post and loading the associated like if its exist. Can I archive this behaviour somehow with sequlize.js?
You just need to nest the where clause one level above.
Post.findAndCountAll({
where: { userId: req.authSession.user.id },
include: [{model: models.like}]
}).then( collection => {
onSucess(collection);
});
This will return all Posts that have userId === req.authSession.user.id and will include their likes ( if any ).
Edit according to the comment
If you want to have the Likes of the user you can just revert the query
Like.findAndCountAll({
where: { userId: req.authSession.user.id },
include: [ { model: models.post } ]
});

Categories