Nodejs Sequelize - mysql

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

Related

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");
);

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!

Understanding Associations in Sequelize

I'm trying to understand associations in Sequelize. I'm starting from existing database tables so some of the fields may not match up to the defaults in Sequelize. I've used Sequelizer to generate my models directly from the database.
I'm accustomed to writing queries but now I'm trying to learn how an ORM like Sequelize works.
Here's my models.
models/user.js
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"User",
{
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
field: "id"
},
username: {
type: DataTypes.STRING(20),
allowNull: false,
field: "username"
},
fullname: {
type: DataTypes.STRING(60),
allowNull: false,
field: "fullname"
},
createdat: {
type: DataTypes.DATE,
allowNull: false,
field: "createdat"
},
updateat: {
type: DataTypes.DATE,
allowNull: true,
field: "updateat"
},
deletedat: {
type: DataTypes.DATE,
allowNull: true,
field: "deletedat"
}
},
{
tableName: "users",
timestamps: false
}
);
User.associate = function(models) {
models.User.hasMany(models.Ticket),
{ as: "createdbyname", foreignKey: "createdby" };
};
return User;
};
models/ticket.js
module.exports = (sequelize, DataTypes) => {
const Ticket = sequelize.define(
"Ticket",
{
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
field: "id"
},
details: {
type: DataTypes.STRING(45),
allowNull: true,
field: "details"
},
assignedto: {
type: DataTypes.INTEGER(11),
allowNull: true,
field: "assignedto"
},
createdby: {
type: DataTypes.INTEGER(11),
allowNull: true,
field: "createdby"
},
createdat: {
type: DataTypes.DATE,
allowNull: false,
field: "createdat"
},
updatedat: {
type: DataTypes.DATE,
allowNull: true,
field: "updatedat"
},
deletedat: {
type: DataTypes.DATE,
allowNull: true,
field: "deletedat"
}
},
{
tableName: "tickets",
timestamps: false
}
);
Ticket.associate = function(models) {
models.Ticket.belongsTo(models.User,
{ foreignKey: "createdby" });
};
return Ticket;
};
In my route handler, I'm calling User.findAll as follows:
models.User.findAll({
include: [models.Ticket]
})
The result I expect to see is a query that looks like this:
SELECT
`User`.`id`,
`User`.`username`,
`User`.`fullname`,
`User`.`createdat`,
`User`.`updateat`,
`User`.`deletedat`,
`Tickets`.`id` AS `Tickets.id`,
`Tickets`.`details` AS `Tickets.details`,
`Tickets`.`assignedto` AS `Tickets.assignedto`,
`Tickets`.`createdby` AS `Tickets.createdby`,
`Tickets`.`createdat` AS `Tickets.createdat`,
`Tickets`.`updatedat` AS `Tickets.updatedat`,
`Tickets`.`deletedat` AS `Tickets.deletedat`
FROM
`users` AS `User`
LEFT OUTER JOIN
`tickets` AS `Tickets` ON `User`.`id` = `Tickets`.`createdby`
The query I see running in the console is:
SELECT
`User`.`id`,
`User`.`username`,
`User`.`fullname`,
`User`.`createdat`,
`User`.`updateat`,
`User`.`deletedat`,
`Tickets`.`id` AS `Tickets.id`,
`Tickets`.`details` AS `Tickets.details`,
`Tickets`.`assignedto` AS `Tickets.assignedto`,
`Tickets`.`createdby` AS `Tickets.createdby`,
`Tickets`.`createdat` AS `Tickets.createdat`,
`Tickets`.`updatedat` AS `Tickets.updatedat`,
`Tickets`.`deletedat` AS `Tickets.deletedat`,
`Tickets`.`UserId` AS `Tickets.UserId`
FROM
`users` AS `User`
LEFT OUTER JOIN
`tickets` AS `Tickets` ON `User`.`id` = `Tickets`.`UserId`;
Note difference in LEFT OUTER JOIN clause. This is throwing an error as follows:
Unhandled rejection SequelizeDatabaseError: Unknown column 'Tickets.UserId' in 'field list'
I need some help figuring out where I've gone wrong here.
When defining associations, like belongsTo, you can specify a foreignKey and a targetKey. The foreignKey corresponds to the field in the source table (remember, the syntax is sourceModel.belongsTo(targetModel, options)). The targetKey corresponds to the field in the target table.
In your case, you made a mistake in the association in the models/ticket.js file, you used:
models.Ticket.belongsTo(models.User, { foreignKey: "createdby" });
Here, foreignKey references the source table, Ticket. Therefore, your are telling Sequelize to use the field createdBy for the Ticket table, and the default (the primary key) for the User table. As createdBy does not exists within Ticket, Sequelize falls back to the default case, where it uses Ticket.UserID.
To fix your association (and query), you need to update your belongsTo to the following:
models.Ticket.belongsTo(models.User, { targetKey: "createdby" });
Plant.findAll({
include: Farm,
order: [['name', 'ASC']]
})
.then(data=> {
res.send(data)
})
.catch(err => {
res.send(err)
console.log(err);
})
let arrQuery = [
queryInterface.addColumn('farms', 'dayPlayed' , Sequelize.STRING),
queryInterface.removeColumn('plants', 'dayPlayed' , Sequelize.STRING),
]
return Promise.all(arrQuery)

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`

sequelizejs eager fetch many to many

I am trying to fetch eager on a join table in Nodejs with Sequelizejs v3 .
So, 1 Tag can belong to Many Images, and Many Images can have multiple tags.
Tag 1 - > M ImageTag M < - 1 Image
I am getting Unhandled rejection Error: Tag is not associated to ImageDetails when i tried to excute a query.
function getImagesFromAlbum(albumid, callback, errCallback){
ImageDetails.findAll({where: { AlbumId: albumid }, include: [{model: Tag}]}).then((data) => {
return callback(data)
}).catch((err) => {
return errCallback(err)
})
}
The expected return result should be the data according to the albumid, with the assiociate tags for that image
Here are the relationship joining
ImageDetails.belongsToMany(Tag, { as: { singular: "tag", plural: "tags" }, through: { model: ImageTag, unique: false }, foreignKey: "ImageId"})
Tag.belongsToMany(ImageDetails, { as: { singular: "image", plural: "images" }, through: { model: ImageTag, unique: false }, foreignKey: "TagId"})
Here are the model designs
Tag Model
const model = {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: Sequelize.STRING
}
}
const name = "Tag"
ImageTag model (Join Table)
const model = {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
}
}
const name = "ImageTag"
ImageDetails model
import { Sequelize, db } from "../config/MySqlConfiguration"
const model = {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
ImageLocation: {
type: Sequelize.STRING
},
originalName: {
type: Sequelize.STRING
}
}
const name = "ImageDetails"
*Note sequelize.define is purposely omitted.
When defining relation between models with use of alias (as) in the belongsToMany, you need to remember to include this alias when eager loading models, so your query would look as follows
ImageDetails.findAll({
where: { AlbumId: albumid },
include: [{ model: Tag, as: 'tags' }]
}).then((data) => {
return callback(data)
}).catch((err) => {
return errCallback(err)
});
And what is the AlbumId you want to perform query on? According to your models definition, the ImageDetails model does not have such a field.