Related
The problem:
Whenever I fetch a user, I always have to declare/include the association on the query to get its role:
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: { email },
include: [ // EVERY QUERY, I HAVE TO INCLUDE THIS
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
}
]
});
Now there are instance where I forget to include this association so I get a undefined role.
My question is, is there a way where I only set this association once so that I don't have to include this later on my queries?
This the model for my AccessUser table
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
email: {
type: DataTypes.STRING(255),
allowNull: false
},
firstname: {
type: DataTypes.STRING(255),
allowNull: false
},
lastname: {
type: DataTypes.STRING(255),
allowNull: false
},
password: {
type: DataTypes.STRING(255),
allowNull: false
},
disable: {
type: DataTypes.TINYINT,
allowNull: false,
defaultValue: 0
},
role_id: {
type: DataTypes.SMALLINT,
allowNull: false,
defaultValue: 0
},
created_modified: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
}
}, {
tableName: 'access_user',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "user_id" },
]
},
]
});
AccessUserRoleLup table
const AccessUserRoleLup = <AccessUserRoleLupStatic>sequelize.define<AccessUserRoleLupInstance>(
'AccessUserRoleLup',
{
role_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
role_name: {
type: DataTypes.STRING(50),
allowNull: false
},
role_code: {
type: DataTypes.CHAR(50),
allowNull: false,
defaultValue: ""
}
}, {
tableName: 'access_user_role_lup',
timestamps: false,
indexes: [
{
name: "PRIMARY",
unique: true,
using: "BTREE",
fields: [
{ name: "role_id" },
]
},
]
});
Association:
db.models.AccessUser.hasOne(db.models.AccessUserRoleLup, {
foreignKey: 'role_id',
as: 'role'
});
Use defaultScope for AccessUser. defaultScope is defined in a model definition and it is always applied (unless you removed inline).
const AccessUser = <AccessUserStatic>sequelize.define<AccessUserInstance>(
'AccessUser',
{
user_id: {
autoIncrement: true,
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
...
}, {
tableName: 'access_user',
timestamps: false,
defaultScope: { // Add this
include: [{
model: AccessUserRoleLup,
as: 'role'
}]
},
...
});
With this model definition, all queries will include AccessUserRoleLup.
If you would like to remove for a certain query, use .unscoped().
// These will automatically add eager loading for role
DB.PORTALDB.models.AccessUser.findAll()
DB.PORTALDB.models.AccessUser.findOne()
// These won't fetch role
DB.PORTALDB.models.AccessUser.unscoped().findAll()
DB.PORTALDB.models.AccessUser.unscoped().findOne()
More detail about scope: https://sequelize.org/master/manual/scopes.html
My initial workaround was to create a utility function for querying the user like so:
export const getAccessUser = (where: WhereOptions, include?: IncludeOptions) => {
return new Promise(async (resolve, reject) => {
try {
const user = await DB.PORTALDB.models.AccessUser.findOne({
where: where,
include: [
{
model: DB.PORTALDB.models.AccessUserRoleLup,
as: 'role'
},
...[include]
]
});
resolve(user);
} catch (err) {
reject(err);
}
});
}
I wonder if my question above can be done in much simpler way.
I have a classical many-to-many relationship for users which own assets: assets can be transfered to other users during their life so a window time is recorded in the AssetUser "through table",
adding STARTDATE and ENDDATE attributes.
User Table
const User = sequelize.define('User', {
ID: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
FIRSTNAME: {
type: DataTypes.STRING,
allowNull: false
},
LASTNAME: {
type: DataTypes.STRING,
allowNull: false
}},{ timestamps: false }});
Asset Table
const Asset = sequelize.define('Asset', {
ID: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
DESCRIPTION: {
type: DataTypes.STRING,
allowNull: false
}},{ timestamps: false }});
AssetUser Join Table
const AssetUser = sequelize.define('AssetUser', {
id: {
type: DataTypes.INTEGER.UNSIGNED,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
UserID: {
type: DataTypes.INTEGER.UNSIGNED,
references: {
model: User,
key: 'ID'
}
},
AssetID: {
type: DataTypes.INTEGER.UNSIGNED,
references: {
model: Asset,
key: 'ID'
}
},
STARTDATE: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
ENDDATE: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: null
}},{ timestamps: false });
The models are created here:
User.belongsToMany(Asset, { through: { model: AssetUser, unique: false }, uniqueKey: 'id' });
Asset.belongsToMany(User, { through: { model: AssetUser, unique: false }, uniqueKey: 'id' });
My problem is that I want to query and find all the results where one asset, owned by one user, during a restricted period. I am not able to query the join-table but only User and Assets tables.
How can I add a "where" condition for the AssetUser table inside my query? How should I insert a STARTDATE and/or ENDDATE condition below?
Asset.findAll({
where: {
DESCRIPTION: 'Personal computer'
},
include: {
model: User,
where: {
FIRSTNAME: 'Marcello'
}
}});
Thanks for your help.
I found the solution
Asset.findAll({ where: { DESCRIPTION: 'Personal computer' }, include: { model: User, through: { where: { FIRSTNAME: 'Marcello' } } }});
I try to explain my case. I have two models: Film and Category. They are N:M associations.
migration file 20200123070411-createTables.js:
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Category', {
ID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
Name: {
type: Sequelize.STRING(20),
allowNull: false,
},
Last_Update: {
type: Sequelize.DATE,
allowNull: false,
},
});
await queryInterface.createTable('Language', {
ID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
Name: {
type: Sequelize.STRING(20),
allowNull: false,
},
Last_Update: {
type: Sequelize.DATE,
allowNull: false,
},
});
await queryInterface.createTable('Film', {
ID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
LanguageID: {
type: Sequelize.INTEGER,
references: {
model: 'Language',
key: 'ID',
},
onDelete: 'restrict',
allowNull: false,
},
Title: {
type: Sequelize.STRING,
allowNull: false,
},
Description: {
type: Sequelize.STRING,
allowNull: false,
},
Release_Year: {
type: Sequelize.INTEGER,
allowNull: false,
},
Rental_Duration: {
type: Sequelize.INTEGER,
allowNull: false,
},
Rental_Date: {
type: Sequelize.DECIMAL(19, 0),
allowNull: false,
},
Length: {
type: Sequelize.INTEGER,
allowNull: false,
},
Replacement_Cost: {
type: Sequelize.DECIMAL(19, 0),
allowNull: false,
},
Rating: {
type: Sequelize.INTEGER,
allowNull: false,
},
Last_Update: {
type: Sequelize.DATE,
allowNull: false,
},
Special_Features: {
type: Sequelize.STRING,
allowNull: false,
},
Fulltext: {
type: Sequelize.STRING,
allowNull: false,
},
});
await queryInterface.createTable(
'Film_Category',
{
FilmID: {
type: Sequelize.INTEGER,
// composite primary key
primaryKey: true,
references: {
model: 'Film',
key: 'ID',
},
onDelete: 'restrict',
},
CategoryID: {
type: Sequelize.INTEGER,
primaryKey: true,
references: {
model: 'Category',
key: 'ID',
},
onDelete: 'cascade',
},
Last_Update: {
type: Sequelize.DATE,
allowNull: false,
},
}
);
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Film_Category');
await queryInterface.dropTable('Film');
await queryInterface.dropTable('Category');
await queryInterface.dropTable('Language');
},
};
After executing the db migration, I define models below:
models/category.ts:
import { Model, DataTypes, BelongsToManyGetAssociationsMixin } from 'sequelize';
import { sequelize } from '../db';
import { Film } from './film_category';
class Category extends Model {
public ID!: number;
public Name!: string;
public Last_Update!: Date;
public getFilms!: BelongsToManyGetAssociationsMixin<Film>;
}
Category.init(
{
ID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
Name: {
type: DataTypes.STRING(20),
allowNull: false,
},
Last_Update: {
type: DataTypes.DATE,
allowNull: false,
},
},
{ sequelize, modelName: 'Category' },
);
export { Category };
models/film.ts:
import { Model, DataTypes, BelongsToManyGetAssociationsMixin } from 'sequelize';
import { sequelize } from '../db';
import { Category } from './film_category';
class Film extends Model {
public ID!: number;
public LanguageID!: number;
public Title!: string;
public Description!: string;
public Release_Year!: number;
public Rental_Duration!: number;
public Rental_Date!: number;
public Length!: number;
public Replacement_Cost!: number;
public Rating!: number;
public Last_Update!: Date;
public Special_Features!: string;
public Fulltext!: string;
public getCategories!: BelongsToManyGetAssociationsMixin<Category>;
}
Film.init(
{
ID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
LanguageID: {
type: DataTypes.INTEGER,
references: {
model: 'Language',
key: 'ID',
},
onDelete: 'restrict',
allowNull: false,
},
Title: {
type: DataTypes.STRING,
allowNull: false,
},
Description: {
type: DataTypes.STRING,
allowNull: false,
},
Release_Year: {
type: DataTypes.INTEGER,
allowNull: false,
},
Rental_Duration: {
type: DataTypes.INTEGER,
allowNull: false,
},
Rental_Date: {
type: DataTypes.DECIMAL(19, 0),
allowNull: false,
},
Length: {
type: DataTypes.INTEGER,
allowNull: false,
},
Replacement_Cost: {
type: DataTypes.DECIMAL(19, 0),
allowNull: false,
},
Rating: {
type: DataTypes.INTEGER,
allowNull: false,
},
Last_Update: {
type: DataTypes.DATE,
allowNull: false,
},
Special_Features: {
type: DataTypes.STRING,
allowNull: false,
},
Fulltext: {
type: DataTypes.STRING,
allowNull: false,
},
},
{ sequelize, modelName: 'Film' },
);
export { Film };
models/film_category.ts:
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../db';
import { Category } from './category';
import { Film } from './film';
class FilmCategory extends Model {
public FilmID!: number;
public CategoryID!: number;
public Last_Update!: Date;
}
FilmCategory.init(
{
FilmID: {
type: DataTypes.INTEGER,
primaryKey: true,
references: {
model: 'Film',
key: 'ID',
},
onDelete: 'restrict',
},
CategoryID: {
type: DataTypes.INTEGER,
primaryKey: true,
references: {
model: 'Category',
key: 'ID',
},
onDelete: 'cascade',
},
Last_Update: {
type: DataTypes.DATE,
allowNull: false,
},
},
{ sequelize, modelName: 'Film_Category' },
);
export { FilmCategory, Film, Category };
models/index.ts:
import { Category } from './category';
import { Film } from './film';
import { Language } from './language';
import { FilmCategory } from './film_category';
Category.belongsToMany(Film, { through: FilmCategory });
Film.belongsToMany(Category, { through: FilmCategory });
Language.hasMany(Film);
export { Category, Film, Language, FilmCategory };
When I try to call Film.findByPk(1), sequelize throw an error:
SequelizeDatabaseError: column "CategoryID" does not exist
The SQL query generated by sequelize of Film.findByPk(1) like this:
Executing (default): SELECT "ID", "LanguageID", "Title", "Description", "Release_Year", "Rental_Duration", "Rental_Date", "Length", "Replacement_Cost", "Rating", "Last_Update", "Special_Features", "Fulltext", "CategoryID" FROM "Film" AS "Film" WHERE "Film"."ID" = 1;
I know when I use Film.belongsToMany(Category, { through: FilmCategory });, sequelize will add CategoryID of target model Category to source model Film. I expect the film data find by primary key has the same properties as the model schema. This extra CategoryID column is the issue.
So I don't want this CategoryID column to be added on Film model and FilmID column to be added on Category model. Because Film table doesn't have CategoryID column and Category table doesn't have FilmID column in the database. They are connected by the join table Film_Category. Is there any way to do this? Or, am I missing something?
Created a minimal reproducible code repo: https://github.com/mrdulin/node-sequelize-examples/tree/master/src/db
Should work if you explicitly define the 'through' table in the association, like so:
Film.belongsToMany(Category,
{
through: FilmCategory,
foreignKey: 'FilmID',
otherKey: 'CategoryID'
});
It's possible the problems may occurs because you're using ID rather than Id, but that is just speculation...
HTH
I have the following problem:
I defined my tables (product and collection) like this:
module.exports = (sequelize, type) => {
return sequelize.define('product', {
id: {
type: type.UUID,
primaryKey: true,
defaultValue: type.UUIDV4,
},
title: {
type: type.STRING,
allowNull: false,
},
description: {
type: type.TEXT,
allowNull: false,
},
width: {
type: type.FLOAT,
allowNull: false,
},
height: {
type: type.FLOAT,
allowNull: false,
},
weight: {
type: type.FLOAT,
allowNull: false,
},
length: {
type: type.FLOAT,
allowNull: false,
},
vendor: {
type: type.STRING,
allowNull: false,
},
status: {
type: type.ENUM,
values: ['inactive', 'active'],
defaultValue: 'active',
allowNull: false,
},
})
}
module.exports = (sequelize, type) => {
return sequelize.define('collection', {
id: {
type: type.UUID,
primaryKey: true,
defaultValue: type.UUIDV4,
},
name: {
type: type.STRING,
allowNull: false,
},
image: {
type: type.STRING,
allowNull: true,
},
createdAt: {
type: type.DATE,
allowNull: false,
},
updatedAt: {
type: type.DATE,
allowNull: false,
},
status: {
type: type.ENUM,
values: ['inactive', 'active'],
defaultValue: 'active',
allowNull: false,
},
})
}
Then, I need associated the tables (product and collection) with belongsToMany association and i did it like this:
const ProductModel = require('../api/product/model')
const CategoryModel = require('../api/category/model')
const Product = ProductModel(sequelize, Sequelize)
const Collection = CollectionModel(sequelize, Sequelize)
Product.belongsToMany(Collection, {
through: ProductCollection,
foreignKey: 'productId',
otherKey: 'collectionId',
unique: false,
})
Collection.belongsToMany(Product, {
through: ProductCollection,
foreignKey: 'collectionId',
otherKey: 'productId',
unique: false,
})
Now, i want to get all the products of a collection given by the id sent from the body of the request, i have little time working with sequelize and i donĀ“t know how to do this kind of query.
Can you help me with that?
you can use something like this
let products = Collection.findAll({
where: {
id: collection.id,
},
attributes: ['id', 'name'],
include: [{
model: Product,
through: {
model: ProductCollection,
},
as: 'products',
attributes: ['id', 'title', 'description']
}],
});
return products
hope it helps
I am considering these 2 tables "exam_response" and "answer" for hasMany association.
Where both the tables contains "question_id". Using question_id I need the answers.
exam_response table
module.exports = (sequelize, DataTypes) => {
const exam_response = sequelize.define('exam_response', {
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
session_id: {
type: DataTypes.UUID,
allowNull: false
},
exam_id: {
type: DataTypes.UUID,
allowNull: false
},
user_id: {
type: DataTypes.UUID,
allowNull: false
},
question_id: {
type: DataTypes.UUID,
allowNull: false
},
answer_ids: {
type: DataTypes.ARRAY(DataTypes.UUID),
allowNull: false
},
is_correct: {
type: DataTypes.BOOLEAN,
allowNull: false
},
is_bookmarked: {
type: DataTypes.BOOLEAN,
allowNull: false
},
is_attempted: {
type: DataTypes.BOOLEAN,
allowNull: false
},
createdAt: {
type: DataTypes.DATE,
field: 'created_at'
},
updatedAt: {
type: DataTypes.DATE,
field: 'updated_at'
}
}, {});
exam_response.associate = function (models) {
// associations can be defined here
exam_response.hasMany(models.answer, {
foreignKey: 'question_id', sourceKey: 'question_id',as:'exam_answers'
});
};
answer table
'use strict';
module.exports = (sequelize, DataTypes) => {
const answer = sequelize.define('answer', {
//{
// "id":"",
// "question_id":"123",
// "position":0,
// "answer":"This is answer 1."
// }
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
},
question_id: {
allowNull: false,
type: DataTypes.UUID
},
position: {
allowNull: false,
type: DataTypes.INTEGER
},
answer: {
allowNull: false,
type: DataTypes.TEXT
},
publish_status: {
allowNull: false,
type: DataTypes.ENUM('published', 'unpublished', 'deleted')
},
language: {
type: DataTypes.ENUM('en', 'kn', 'hi')
},
createdAt: {
type: DataTypes.DATE,
field: 'created_at'
},
updatedAt: {
type: DataTypes.DATE,
field: 'updated_at'
}
}, {});
answer.associate = models => {
answer.belongsTo(models.question,{foreignKey:'question_id',as:'answers'});
answer.belongsTo(models.exam_response,{foreignKey:'question_id', targetKey: 'question_id',as:'exam_answers'});
};
return answer;
};
Query::
ExamResponse.findAll({
where: {
exam_id
},
include: [
{
model: Answer,as:'exam_answers'
}
],
}).then(resp => {
response.successGet(res, resp, 'Exam Response');
}).catch(next)
I am getting the output but associated part("exam_answers") is empty.
If I use raw query, i am able to get the output. But the Query is only fetching me the exam_response not the answer even though the value exists.