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

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.

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,
})

Problem using both include and attributes in findAll() query in Sequelize

I searched on the internet a common issue but I did not find anything so I post the question here.
I am using Sequelize for Node and I am trying to query my PostgreSQL database to find all the objects in a specific table. However, I would like to be able to replace de foreign keys by data from their linked table (In other words, join tables and remove the ids). I managed to do this by using both the "include" option to join the table and the "attributes" option to unselect the Id and only keep the joined value, but since I have a hasMany relationship, I get an error when executing the query.
Here is the options I use :
options = {
include: [
{
model: models.ProviderType,
attributes: ['id', 'name'],
as: 'providerType',
},
{
model: models.SavingProduct,
attributes: ['id', 'name'],
as: 'savingProducts',
},
],
attributes: [
'id',
'name',
'siteUrl',
'logoPath',
'createdAt',
'updatedAt',
'deletedAt',
],
}
And it executed the following query :
SELECT "Provider".*, "providerType"."id" AS "providerType.id", "providerType"."name" AS "providerType.name", "savingProducts"."id" AS "savingProducts.id", "savingProducts"."name" AS "savingProducts.name"
FROM (
SELECT "Provider"."id", "Provider"."name", "Provider"."siteUrl", "Provider"."logoPath", "Provider"."createdAt", "Provider"."updatedAt", "Provider"."deletedAt"
FROM "Provider" AS "Provider"
WHERE "Provider"."deletedAt" IS NULL
LIMIT 20 OFFSET 0
) AS "Provider"
LEFT OUTER JOIN "ProviderType" AS "providerType" ON "Provider"."providerTypeId" = "providerType"."id"
LEFT OUTER JOIN "SavingProduct" AS "savingProducts" ON "Provider"."id" = "savingProducts"."providerId" AND ("savingProducts"."deletedAt" IS NULL);
The error thrown is : "column Provider.providerTypeId does not exist". It is logical because, when selecting the embedded SELECT, I do not select Provider.providerTypeId as I don't want this column to be returned, and because of that, when trying to JOIN the tables, SQL do not find this column and return an error.
In other words, Sequelize first select the wanted values and then join while I would like it to do the opposite, ie join the tables and then select only the asked columns.
So I understand the error but I don't find any way to replace the foreign keys by data from the linked tables. It is possible I did not take the right direction to do that so if you see something easier and cleaner, I would really appreciate!
Followed are the models definition :
For Provider
module.exports = (sequelize, DataTypes) => {
const Provider = sequelize.define('Provider', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
siteUrl: {
type: DataTypes.STRING,
allowNull: true,
},
logoPath: {
type: DataTypes.STRING,
allowNull: true,
},
}, {
freezeTableName: true,
paranoid: true,
});
Provider.associate = (models) => {
Provider.belongsTo(models.ProviderType, { as: 'providerType' });
Provider.hasMany(models.SavingProduct, { as: 'savingProducts', foreignKey: 'providerId' });
};
return Provider;
};
For SavingProduct
module.exports = (sequelize, DataTypes) => {
const SavingProduct = sequelize.define('SavingProduct', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
}, {
freezeTableName: true,
paranoid: true,
});
SavingProduct.associate = (models) => {
SavingProduct.belongsTo(models.Provider, { as: 'provider', foreignKey: 'providerId' });
};
return SavingProduct;
};
Please do not hesitate to tell me if I am not clear, I would be happy to provide you with more informations.
Thank you very much!

Sequelize findOne-includes: conjunction table id is missing

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"}
]
})

Is it possible for Sequelize to include a table from another database in the same query?

I've got a two MySQL Schemas both with a table called User as I want to have a SSO application with multiple microservices. I can't seem to get Sequelize to generate the correct SQL for my needs.
I've tried adding the 'schema' attribute to my model definitions in Sequelize, but it tries to use 'schema_name.schema_name.table_name' instead of 'schema_name.table_name'. I'm unsure whether the schema attribute works for MySQL.
SuperUser.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
'User',
{
id: {
primaryKey: true,
type: DataTypes.UUID,
allowNull: false,
},
username: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING,
first_name: DataTypes.STRING,
last_name: DataTypes.STRING,
company: DataTypes.STRING,
},
{
// tried to add schema: super_schema
underscored: true,
timestamps: true,
paranoid: true,
},
);
User.associate = function(models) {};
return User;
};
SubUser.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
'User',
{
id: {
primaryKey: true,
type: DataTypes.UUID,
allowNull: false,
},
role: {
type: VARCHAR(45),
allowNull: false,
},
},
{
underscored: true,
timestamps: true,
paranoid: true,
},
);
User.associate = function(models) {
const { super } = models;
User.belongsTo(super.models.User, { as: 'Super', foreignKey: 'id' });
};
return User;
};
My query is
const user = await db.sub.User.findOne({
include: [
{
model: db.super.User,
as: 'Super',
where: {
username: 'someUsername',
},
},
],
});
I believe the problem lies in how I'm creating the Sequelize instances, I'm creating an instance for each schema connection. So Sequelize doesn't know that db.super.User is different from db.sub.User when written in Javascript.
The problem lies in the INNER JOIN it generates.
It generates
INNER JOIN `users` AS `Super` ON `User`.`id` = `Super`.`id`
I'd like it to generate
INNER JOIN `Super`.`users` AS `Super` ON `User`.`id` = `Super`.`id`

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