Sequelize findOne-includes: conjunction table id is missing - many-to-many

I want to query a Subject with Student with the join table included.
The relationship is many to many. I want to have the join table id (StudentSubject) but the returning result is empty
Subject.findOne({
where:{id},
include: [
{ model: Student, attribute: ['id', 'name']},
{ model: StudentSubject, attribute: ['id', 'subject', 'student', 'score'], as: "studentSubject"}
]
})
The relationship is as follow
student
Student.belongsToMany(models.Subject, { through: models.StudentSubject, foreignKey: 'student'})
Student.belongsTo(models.StudentSubject, {foreignKey: 'id', targetKey: 'student', as: 'studentSubject'})
subject
Subject.belongsToMany(models.Student, { through: models.StudentSubject, foreignKey: 'subject'})
Subject.belongsTo(models.StudentSubject, { foreignKey: 'id', targetKey: 'subject', as: 'studentSubject'})
the join table models
// it has id by default in the migration file
const StudentSubject = sequelize.define('StudentSubject', {
student: DataTypes.INTEGER,
subject: DataTypes.INTEGER,
score: DataTypes.INTEGER
}
the result is missing the conjuntion table id, what do I miss here?
additional experiment
tried also with nested include in Student, doesn't work. it doesn't have the id too
Subject.findOne({
where:{id},
include: [
{ model: Student, attribute: ['id', 'name'], include: [
{ model: StudentSubject, attribute: ['id', 'subject', 'student', 'score'], as: "studentSubject"}
]},
]
})

turns out I have to define the id in the conjunction table models. I thought otherwise, so here it is
sequelize.define('StudentSubject', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
student: DataTypes.INTEGER,
subject: DataTypes.INTEGER,
score: DataTypes.INTEGER
}
and I incorrectly define the associations, so I came to this
Student.hasMany(models.StudentSubject, {foreignKey: 'student'})
Subject.hasMany(models.StudentSubject, { foreignKey: 'subject'})
StudentSubject.belongsTo(models.Student, {foreignKey: 'student', sourceKey: 'id'})
StudentSubject.belongsTo(models.Subject, {foreignKey: 'subject', sourceKey: 'id'})
and now when querying with
Subject.findOne({
where:{id},
include: [
{ model: Student, attribute: ['id', 'name'], include: [
{ model: StudentSubject, attribute: ['id', 'subject', 'student', 'score'], as: "studentSubject"}
]},
]
})
it now works

I haven't tested this out but you may need to include the id of the Subject table in your first code block. The below may get you what you are looking for.
Subject.findOne({
where:{id},
include: [
{ model: Subject, attribute: ['id']},
{ model: Student, attribute: ['id', 'name']},
{ model: StudentSubject, attribute: ['id', 'subject', 'student', 'score'], as: "studentSubject"}
]
})

Related

Nodejs Sequelize

I have these 2 models:
Orders Models
Solutions model
Orders Model
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Orders extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
Orders.hasMany(models.Payments, {
foreignKey: {
name: 'order',
allowNull: false,
},
constraints: false,
onDelete: 'cascade',
});
Orders.hasOne(models.Solutions, {
foreignKey: {
name: 'order',
allowNull: false,
},
constraints: false,
onDelete: 'cascade',
as: "solution"
});
}
}
Orders.init(
{
order_no: {
defaultValue: DataTypes.UUIDV4,
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
unique: true,
},
order_date: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: 'Orders',
tableName: 'Orders',
}
);
return Orders;
};
#2. Solutions table
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Solutions extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
Solutions.belongsTo(models.Orders, {
foreignKey: 'order',
onDelete: 'cascade',
constraints: false,
as: "solution"
});
}
}
Solutions.init(
{
solutionId: {
defaultValue: DataTypes.UUIDV4,
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
unique: true,
},
content: {
type: DataTypes.TEXT,
allowNull: false,
},
additional_instruction: {
type: DataTypes.TEXT,
allowNull: true,
},
date_submited: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
},
{
sequelize,
modelName: 'Solutions',
}
);
return Solutions;
};
I am trying to get all orders where it's solution has not been submitted to the solutions table, i.e order field(Foreign key in solution table) is null.
I have tried this
Orders.findAndCountAll({
include: [
{
model: Users,
attributes: ['username', 'email', 'uid'],
},
{
model: Solutions,
as: "solution",
where: {
solutionId: {
[Op.notIn]: Solutions.findAll({
attributes: ['solutionId']
})
}
}
}
],
offset: page,
limit,
})
I was expecting to get a list of all orders where the solutions in the solution table has not been added. Am a bit new to sequelize.
You can try to filter after left join, Sequelize can apply where clause directly on the join or after join.
Orders.findAndCountAll({
where: {
'$orders.solution$': null,
},
include: [
{
model: Solutions,
as: "solution",
required: false
},
],
})
In SQL it's like :
SELECT COUNT(*)
FROM orders o
LEFT JOIN solutions s ON o.id = s.order AND s.order IS NULL
VS
SELECT COUNT(*)
FROM orders o
LEFT JOIN solutions s ON o.id = s.order
WHERE s IS NULL
You can perform a left join with a filter which excludes records from Solutions table if the order does not exit.
Orders.findAndCountAll({
include: [
{
model: Users,
attributes: ['username', 'email', 'uid'],
},
{
model: Solutions,
as: "solution",
required: false,
},
],
where: {
'$solution.order$': null
},
offset: page,
limit,
})
For those coming later to this question, I have come to the conclusion that a LEFT OUTER JOIN between the two tables performs the exact same thing I was looking for. I want to give credit back to #Shengda Liu and #8bitIcon for the solution given.
In sequelize the solution would involve just adding the required field in the include statement on the target model to enforce the rule(i.e) find all rows that have an associations in the target associated table. For my case, the solution is as follows.
Orders.findAndCountAll({
include: [
{
model: Users,
attributes: ['username', 'email', 'uid'],
},
{
model: Solutions,
as: "solution",
required: true, // this is the only thing I have added
/// and removed the where clause in the include field.
},
],
offset: page,
limit,
})

Sequelize 'where' on parent and child when parent hasMany childs

I have 2 models:
class User extends Model {
static associate(models) {
User.hasMany(models.Role, {
foreignKey: 'userId'
});
}
};
User.init({
firstname: {
type: DataTypes.STRING,
},
lastname: {
type: DataTypes.STRING,
},
allowedApps: {
type: DataTypes.ENUM({
values: Object.keys(PORTALS)
}),
allowNull: false
}
}, {
sequelize,
paranoid: true,
modelName: 'User',
});
class Role extends Model {
static associate(models) {
Role.BelongsTo(models.User, {
foreignKey: 'userId'
});
}
};
Role.init({
type: {
type: DataTypes.STRING,
unique: true
},
name: DataTypes.STRING,
userId: {
type: DataTypes.INTEGER,
allowNull: false,
}
}, {
sequelize,
paranoid: true,
modelName: 'Role',
});
I would like to get all users where the firstname OR the role type matches a certain condition. Something like:
User
.findAndCountAll({
where: {
$or: [
{
firstname: "John Doe"
},
{
"$Role.type$": "Admin"
}
]
},
include: [{
model: Role,
}],
}).limit=10,offset=0
.then(users => res.status(200).send(users))
.catch(error => {
return res.sendStatus(500);
});
above query giving me error: "SequelizeDatabaseError: Unknown column 'Role.type' in 'field list'"
I want to search through child model when it has one to many relationship having my limit and offset intact.Same query would give me success if user would have HasOne relation with role.
This is just an example code of what I try to achieve so please ignore any typos and silly mistakes.
After some digging:
await User.findAll({
where: {
$or: [
{
firstname: "John Doe"
},
{
"$Role.type$": "Admin"
}
]
},
include: {
model: Role,
as: 'Role',
required: false
}
});
However, it doesn't make logical sense to select Users that have no associated Role (required: false), while querying such Users with a property that exists on Role ($or: $Role.type$). If we set Required = true, then we violate your initial condition
firstname OR the role type matches a certain condition.
The following addresses this problem:
await User.findAll({
include: {
model: Role,
required: false
}
})
.then(
users => users
.filter(user => user?.firstName === "John Doe" || user.role?.type ===
"Admin");
);

Sequelize - Include all children if any one matches

I have two entities, Post and Tag, I am trying to query for all posts that have any one tag passed to the where clause. In addition, I want to include ALL the tags for the final set of Posts.
The association is defined as so
Post.belongsToMany(
models.tag,
{
through: 'post_tag'
}
);
My query is like so
models.post.findAll({
limit: 20,
offset: 0,
attributes: [
'id',
'name'
],
include: [{
model: models.tag,
attributes: ['name'],
where: {
name: {
[Op.in]: ['tagNameHere']
}
}
}],
where: [{
active: {
[Op.not]: 'False'
}
}],
order: [ ['name', 'ASC'] ]
})
It does work, but the included tags array is ONLY that one specified within the Op.in. I want ALL the tags to be included
Any better way of going about it?
One approach is to make two passes: 1) find posts that have particular tag, 2) find all tags for those posts. You need a third association to make this happen:
models.post.belongsToMany(models.tag, {through: models.postTag, foreignKey: 'post_id'} );
models.tag.belongsToMany (models.post,{through: models.postTag, foreignKey: 'tag_id' });
models.post.hasOne(Post, {
foreignKey: {name: 'id'},
as: 'selfJoin'
});
Now, identify posts that have particular tag (or tags)
models.post.addScope('hasParticularTag',
{
attributes: ['id'],
include: [
{
model: models.tag,
through: models.postTag,
attributes: [],
where: {name: 'TAG-YOU-WANT'} // your parameter here...
}]
});
Finally, list selected posts and all their tasks...
models.post.findAll({
attributes: ['id','name'],
include: [
{ // ALL tags
model: models.tag,
through: models.postTag,
attributes: ['name']
},
{ // SELECTED posts
model: models.post.scope('hasParticularTag'),
required: true,
as: 'selfJoin', // prevents error "post isn't related to post"
attributes: []
}]
})
HTH....

Sequelize Many-to-many table with identical foreign key is selecting just one value

I have the following structure:
var User = sequelize.define('user', {
name: DataTypes.STRING
});
var Post = sequelize.define('post', {
text: DataTypes.STRING
});
var PostComment = sequelize.define('postComment ', {
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true
},
comment: DataTypes.TEXT
});
Post.belongsToMany(User, {as: 'postUserComment', through: {model: models.PostComment, unique: false}, foreignKey: 'idPost'});
User.belongsToMany(Post, {through: {model: models.PostComment, unique: false}, foreignKey: 'idUserComment'});
I am able to create multiples comments to the same post with an user.
But if i have more then one comment to the same post with the same user and try to select them by doing:
Post.findAll({
include: [{model: models.User, as: 'postUserComment', attributes:['name'], through: {attributes: ['comment']}},
limit: 10,
offset: 0,
order: "id DESC"
...
it just return 1 comment for each user in a post. What do i have to do to select them all?
Dialect: mysql,
Sequelize version: ~3.27.0
Having association with BelongsToMany and the same ids is somehow tricky in Sequelize.
As you have already noticed in GitHub #6906 and other related issues there, the best way to do this is to mitigate it with different relations.
For example you can add :
Post.hasMany( models.PostComment, { foreignKey: 'idPost' } );
And then to your query
Post.findAll({
include: [
{model: models.User, as: 'postUserComment', attributes:['name'], through: {attributes: ['comment']}},
{model : models.PostComment}
],
limit: 10,
offset: 0,
order: "id DESC"
..
This will not change your database structure and will have the effect that you want.

Sequelize Query - Finding records based on many-to-many table and parent table

Given the following sequelize models:
var User = db.define('user', {
name: Sequelize.STRING
});
var Group = db.define('group', {
name: Sequelize.STRING,
public : { type: Sequelize.BOOLEAN, defaultValue: true }
});
Group.belongsToMany(User, { as: 'specialUsers', through: 'user_groups', foreignKey: 'group_id' });
User.belongsToMany(Group, { through: 'user_groups', foreignKey: 'user_id' });
How would I go about finding the Groups for a through the Groups model where the Groups returned should be those where the user has a record in the many to many table -- or -- the group is a public group?
I've tried something like this:
return Group.findAll({
attributes: ['name', 'public'],
include: [{
model: User,
as: 'specialUsers',
where: {
$or : [
{name: 'Neill'},
Sequelize.literal('"group"."public" = true')
]
}
}]
});
return Group.findAll({
attributes: ['name', 'public'],
include: [{
model: User,
as: 'specialUsers',
}],
where: {
$or : {
'$users.name$": 'Neill',
public: true
}
}
});
Should work if you are on a fairly recent version. Note that I moved the where out of the include