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.
Related
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
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'}] }]} )
I have two models Brand and Campaign.
A Brand can have many Campaigns
export default(sequelize, DataTypes)=> {
const Brand = sequelize.define('Brand', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
})
Brand.associate = models=> {
Brand.belongsToMany(models.Campaign, {
through: models.CampaignBrand,
foreignKey: 'brand',
})
}
return Brand
}
A Campaign can also have many Brand
export default(sequelize, DataTypes)=> {
const Campaign = sequelize.define('Campaign', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
})
Campaign.associate = models=> {
Campaign.belongsToMany(models.Brand, {
through: models.CampaignBrand,
foreignKey: 'campaign',
})
}
return Campaign
}
And here is through model:
export default(sequelize, DataTypes)=> {
const CampaignBrand = sequelize.define('CampaignBrand', {
// see enums
status: {
type: DataTypes.INTEGER,
allowNull: false,
},
roleText: {
type: DataTypes.STRING,
},
})
CampaignBrand.associate = models=> {
CampaignBrand.belongsTo(models.Campaign, {
foreignKey: 'campaign',
targetKey: 'id',
onDelete: 'CASCADE',
})
}
return CampaignBrand
}
In case I want to get Campaigns by brand. What should I do?
I have tried query likes document mentioned but it does not work for me
With Belongs-To-Many you can query based on through relation and select specific attributes. For example using findAll with through
User.findAll({
include: [{
model: Project,
through: {
attributes: ['createdAt', 'startedAt', 'finishedAt'],
where: {completed: true}
}
}]
});
I have found some ways to work around, but it is not what I am looking for:
SOLUTION 1:
Update belongsToMany Brand to hasMany CampaignBrand and the query by CampaignBrand.brand
SOLUTION 2:
Get Campaign by querying Brand
Any other advices?
Dialect: postgres
Database version: 9.4
Sequelize version: 4.2.1
I think you don't need this association in the the through model:
CampaignBrand.associate = models=> {
CampaignBrand.belongsTo(models.Campaign, {
foreignKey: 'campaign',
targetKey: 'id',
onDelete: 'CASCADE',
})
}
You already have the belongsToMany association in the definitions of Brand and Campaign, so I think you just need to create the CampaignBrand model with your status and roleText attributes.
As I understand it, then you can query brands through campaigns and it should return each brand element and its associated campaigns,
Brand.findAll({
include: [{
model: Campaign
}]
});
This answer is kinda old, but with belongstomany associations, you can use mixins.
https://sequelize.org/api/v6/class/src/associations/belongs-to-many.js~belongstomany
const campaigns = await CampaignModel.findAll();
const campaignsBrandsModels = await campaigns.getBrandsModels({
limit:1,
through: {where:{pivot_field:'pivot_value'} } // << You want this, i think!
});
Now, remember that, the mixins (get, set, count...) set their name with inflection library, so, a way to set the names without error (or without spending a lot of time searching the name in in the console) is setting an 'as' alias in the Association
CampaignBrand.belongsToMany(models.Campaign, {
through: models.CampaignBrand,
foreignKey: 'brand',
as: 'BrandsModels' //<<<< This is Super important! at least for easy usage
})
You can achieve this with an include in the findAll method too, BUT!
the 'limit: #' part will not work! (Will give you the error of "This only works with hasMany because separate: true")
I really hope this can help anyone, seya!
I modelled a data structure where I have a n:m relation between "Rooms" and "Users".
Now I want to delete / destroy a room.
Therefore I want to check if the deleting user is in the room.
I have the username and the roomid.
How can I accomplisch that in one Query.
Basically my question is of it possible to do something like that in a query:
Room.destroy({
where: {
//username in users
users: {$contains: { username: "some username" }}
}
})
Here users is an "Association" to my users.
Considering your users model is defined as User,
this may helps you (or at least gives you a start point):
Room.destroy({
include: [{
model: User,
through: {
where: {username: "some username"}
}
}]
});
This is described in the n:m querying section in Sequelize documentation:
With Belongs-To-Many you can query based on through relation and
select specific attributes. For example using findAll with through
User.findAll({
include: [{
model: Project,
through: {
attributes: ['createdAt', 'startedAt', 'finishedAt']
where: {completed: true}
}
}]
});
What Type of Relationship is needed below, and How Can I Leverage SailsjS/Waterline to Simplify My Find and Update Queries?
In my app, I have Lists (List-Model), Items (Item-Model), and Provisions (Provision-Model). The app's intention is to manage Inventory. As so, Items are discrete -- meaning, there can only be one of the same Item in the Items-collection (primaryKey is set on item.name). A single List shares a One-To-Many relationship with Items -- vicariously through Provisions. A Provision is simply a [discrete] Item's details for a given List. In this case, a Provision is only accessible using both a List-Id and and Item-Id (list.id + item.id === provision.compositePrimaryKey).
My issue is the complexity in dealing with find and update operations. With an alternative approach, I had just a Many-To-Many relationship between Lists and Items (with Items dominant) -- and this generated an Item upon List-Updates and aggregated Items with List.find(...).populate('items'). This was nearly ideal but I needed Provisions in the mix. Now (with the Schema below), the same relationship exists between Lists and Provisions -- List updates and 'populates' generate and aggregate a Provision with the correct list.id, but I was expecting Sails/Waterline to generate an Item-Model-Instance because Provision contains a item: { model: 'item' } attribute. Here is my current approach:
Provisions:
//api/models/Provision.js
module.exports = {
schema: true,
attributes: {
...
list: { model: 'list' },
item: { model: 'item' },
quantity: { type: 'integer' },
...
}
};
Lists:
//api/models/List.js
module.exports = {
schema: true,
attributes: {
...
items: { collection: 'provision', via: 'list' },
...
}
};
Items:
//api/models/Item.js
module.exports = {
schema: true,
attributes: {
...
name: { type: 'string', primaryKey: true }
lists: { collection: 'provision', via: 'item' },
inStock: { type: 'integer' },
...
}
};
I would like to generate a Provision -- if only through updating a list -- and have a discrete Item found or created automatically -- and hydrate an Item in the Provision-instance when performing a find on a List, as each List's Provision will have a model link to Item.
How can I do this, or is there a better design which I should employ?
Currently primaryKey must be id, look after sails hooks blueprint at actionUtil that primary key must be id field yet. So your Item.js should be
module.exports = {
schema: true,
attributes: {
...
// change it's column name if necessary fo your DB scheme
id: { type: 'string', unique: true, primaryKey: true, columnName: 'name' }
lists: { collection: 'provision', via: 'item' },
inStock: { type: 'integer' },
...
}
};
I don't pretty understand what and how do you want to approach. But using Lifescycle callbacks may help you. For example in your statement I would like to generate a Provision -- if only through updating a list -- and have a discrete Item found or created automatically, so use beforeUpdate, beforeCreate, etc. that meets your need.