How to use an alias inside an include where condition using Sequelize - sql

A table contains 2 user id cols, userOneId and userTwoId. My user id could be in either col. I'd like to get all rows where my userId is in either col, and also include the associated object of the other user.
Here's the latest attempt. I query where my id is in userOneId or userTwoId, make an alias for the other user id, otherUserId, with a case/when. Then use alias otherUserId in an include, to return the other user associated object.
where: {
[Op.or]: [{
userOneId: userId,
}, {
userTwoId: userId,
}]
},
attributes: [
'id',
[sequelize.literal("CASE WHEN \"userOneId\" = " + userId + " THEN \"userTwoId\" ELSE \"userOneId\" END"), 'otherUserId']
],
include: {
where: {
id: '$otherUserId$'
},
model: sequelize.models.User,
as: 'otherUser',
attributes: [ 'id', 'name', ...
Model associations are:
MyModel.belongsTo(models.User, {
as: 'userOne',
foreignKey: 'userOneId'
})
MyModel.belongsTo(models.User, {
as: 'userTwo',
foreignKey: 'userTwoId'
})
User.hasMany(models.MyModel, {
as: 'userOne',
foreignKey: {
name: 'userOneId',
allowNull: false
}
})
User.hasMany(models.MyModel, {
as: 'userTwo',
foreignKey: {
name: 'userTwoId',
allowNull: false
}
})
However, it errors with
User is associated to MyModel multiple times. To identify the correct association, you must use the 'as' keyword to specify the alias of the association you want to include.
Any pointers on if or how to get this to work?

Related

How to search for/select by included entity but include all related entities into result set

In my application, I am using sequelize ORM. There are several entities: A Tool can have Tags and Categories.
Now I want to search for all Tools, that have a specific Tag, but I want to include all relating Tags of that tool (not just the specific one). If I now place a where statement into the include, only specified Tags are included into the result set (see [2]). I tried to limit the Tags in the outer where statement (see [1]), but this does not help either.
Example
Tool A has Tags t1, t2 and t3. Now I want to search all Tools that have the Tag t3, but the result set shall contain all three tags.
Expected result:
Tool A
\
- Tag t1
- Tag t2
- Tag t3
db.Tool.scope('published').findAll({
where: { '$tool.tag.name$': filter.tag }, // [1] Does not work
include: [
{
model: db.User,
attributes: ['id', 'username']
},
{
model: db.Tag,
attributes: ['name'],
through: { attributes: [] },
// [2] Would limit the result specified tag
// where: {
// name: {
// [Op.and]: filter.tag
// }
// }
},
{
model: db.Category,
attributes: ['id', 'name', 'views'],
through: { attributes: ['relevance'] },
where: {
id: {
[Op.and]: filter.category
}
}
}
],
where: {
title: {
[Op.like]: `%${filter.term}%`,
}
},
attributes: ['id', 'title', 'description', 'slug', 'docLink', 'vendor', 'vendorLink', 'views', 'status', 'createdAt'],
order: [['title', 'ASC'], [db.Tag, 'name', 'ASC']]
})
I know I could perform this by performing a select via the Tag in the first place (db.Tag.findAll() instead of db.Tool.findAll(); I've already done this elsewhere in my project), but at the same time I also want to be able to filter by another entity (Category) the same way. So the Tool.findAll() should be the starting point.
Any help appreciated!
First off, you have two where clauses in your top-level query:
where: { '$tool.tag.name$': filter.tag }, // [1] Does not work
// ...
where: {
title: {
[Op.like]: `%${filter.term}%`,
}
},
I think your best approach is going to be with a literal subquery in the WHERE clause. Basically we want to find the ids of all of the tools that have the right tag and that contain the filter.term.
The subquery part for the WHERE looks something like...
SELECT ToolId FROM ToolTags WHERE TagId='t2';
Inspired by the subquery solution from this post Sequelize - subquery in where clause
// assuming your join table is named 'ToolTags' in the database--we need the real table name not the model name
const tempSQL = sequelize.dialect.QueryGenerator.selectQuery('ToolTags',{
attributes: ['ToolId'],
where: {
TagId: filter.tag
}})
.slice(0,-1); // to remove the ';' from the end of the SQL
db.Tool.scope('published').findAll({
where: {
title: {
[Op.like]: `%${filter.term}%`,
},
id: {
[Op.In]: sequelize.literal(`(${tempSQL})`)
}
},
include: [
{
model: db.User,
attributes: ['id', 'username']
},
{
model: db.Tag,
attributes: ['name'],
through: { attributes: [] },
},
// {
// model: db.Category,
// attributes: ['id', 'name', 'views'],
// through: { attributes: ['relevance'] },
// where: {
// id: {
// [Op.and]: filter.category
// }
// }
// }
],
attributes: ['id', 'title', 'description', 'slug', 'docLink', 'vendor', 'vendorLink', 'views', 'status', 'createdAt'],
order: [['title', 'ASC'], [db.Tag, 'name', 'ASC']]
})
I commented out your category join for now. I think you should try to isolate the solution for the tags before adding more onto the query.

Sequelize define association on Array

I have two models in Sequelize as below:
export const User = db.define('user', {
id: {
type: Sequelize.UUID,
primaryKey: true,
},
});
export const Class = db.define('class', {
id: {
type: Sequelize.UUID,
primaryKey: true,
},
students: Sequelize.ARRAY({
type: Sequelize.UUID,
references: { model: 'users', key: 'id' },
})
});
How can I define an association between my Class model and the user model?
I have tried the below but it gives me an error.
Class.hasMany(User, { foreignKey: 'students' });
User.belongsTo(Class);
DatabaseError [SequelizeDatabaseError]: column "class_id" does not exist
I think you are missing the syntax
students: Sequelize.ARRAY({
type: Sequelize.UUID,
references: { model: 'users', key: 'id' }, // this has no effect
})
Should be
students: {
type: DataTypes.ARRAY({ type: DataTypes.UUID }),
references: { model: 'users', key: 'id' }
}
This won't work either, because the data type of students (ARRAY) and id (UUID) of User does not match.
Also, with these associations, you are adding two columns on User referencing id of Class but you only need one
Class.hasMany(User, { foreignKey: 'students' }); //will add students attribute to User
User.belongsTo(Class); //will add classId attribute to User
if you want to name the foreign key column passe the same name to both associations, by default Sequelize will add classId, however if you configured underscored: true on the models it will be class_id
Here is a working solution
const User = sequelize.define('user', {
id: { type: DataTypes.UUID, primaryKey: true },
});
const Class = sequelize.define('class', {
id: { type: DataTypes.UUID, primaryKey: true },
students: DataTypes.ARRAY({ type: DataTypes.UUID }),
});
Class.hasMany(User, { foreignKey: 'class_id' });
User.belongsTo(Class, { foreignKey: 'class_id' });

How to generate query on join table using Sequelize?

Let us say there are two tables namely User and User Role.
The relationship between user and user role is one to many.
Sequelize model for the user is as following -
const user = sequelize.define(
'user', {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id'
},
userName: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'username'
},
password: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'password'
}
}, {
tableName: 'user'
}
);
Sequelize model for user role is as follwing -
const userRole = sequelize.define(
'userRole', {
id: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'id'
},
userId: {
type: DataTypes.BIGINT,
allowNull: false,
primaryKey: true,
autoIncrement: true,
field: 'user_id'
},
password: {
type: DataTypes.STRING(200),
allowNull: false,
field: 'password'
}
}, {
tableName: 'userRole'
}
);
Sequelize association is defined as follows -
user.hasMany(models.userRole, { foreignKey: 'user_id', as: 'roles' });
userRole.belongsTo(models.user, { foreignKey: 'user_id', as: 'user' });
I want to generate the following query using
Sequelize -
SELECT *
FROM USER
INNER JOIN (SELECT user_role.user_id,
role
FROM user_role
INNER JOIN USER tu
ON tu.id = user_role.user_id
GROUP BY user_id
ORDER BY role) AS roles
ON USER.id = roles.user_id;
I am developing an API which will be consumed by the front end grid for showing user info. There is search functionality on role attribute of user role table. If any of role of a specific user is matched then I expect a user record with all the roles which are associated with that user.
To get all roles that are associated with the user even if one of them matches with a query you need to define another association so that you can include role table twice in sequelize statement.
User.hasMany(models.UserRole, { foreignKey: 'user_id', as: 'roles2' });
Sequelize statement -
const userInfo = await User.findAndCountAll({
include: [
{
model: UserRole,
attributes: ['id', 'role'],
as: 'roles',
where: { [Op.or]: [{ role: { [Op.like]: '%MANAGER%' } },
required: true
},
{
model: UserRole,
attributes: ['id', 'role'],
as: 'roles2',
required: true
}
],
where: whereStatement,
});
With the help of first include (join), you can filter user records based on user role and with the help of the second include(join), you can get all the roles of a user whose one of the roles is matched.
You have to use include (regarding the doc : https://sequelize.org/master/manual/models-usage.html#-code-findandcountall--code----search-for-multiple-elements-in-the-database--returns-both-data-and-total-count)
Exemple :
Models.User.findAll({
where :{
id: userId
},
group: ['roles.user_id'],
order: [['roles.role', 'ASC']] //or DESC, as you want
include: {
model: Models.UserRole,
as: 'roles',
attributes: ['user_id', 'role'],
required: true
},
})
Hope it helps you

Sequelize doesn't use field alias from model

I have model called proxyPool with next fields:
poolId: {
type: DataTypes.INTEGER,
references: {
model: 'pool',
key: 'id',
},
field: 'pool_id',
},
proxyId: {
type: DataTypes.INTEGER,
references: {
model: 'proxy',
key: 'id',
},
field: 'proxy_id',
},
It's a N:M table for two tables which have next associations:
proxy.associate = (models) => {
proxy.belongsToMany(models.pool, {
through: models.proxyPool,
foreignKey: 'proxy_id',
});
};
and
pool.associate = (models) => {
pool.belongsToMany(models.proxy, {
through: models.proxyPool,
foreignKey: 'pool_id',
});
};
When I call proxyPool.findOrCreate({where: {proxyId, poolId}}) it says that column proxyPool.proxyId does not exist, but in raw SQL I see:
SELECT "id", "pool_id" AS "poolId", "createdAt", "updatedAt", "pool_id", "proxy_id"
FROM "portnoi"."proxy_pool" AS "proxyPool"
WHERE "proxyPool"."proxyId" = '3' AND "proxyPool"."pool_id" = '1' LIMIT 1;
Why does it use alias for poolId = pool_id but not use alias described in model for proxyId = proxy_id?
Have you tried to make a query like that?
proxyPool.findOrCreate({
where: {
proxyId: {
[Op.eq]: proxyId,
},
...
}
})

Sequelize the nodejs throught model association

I have tree models User, Company and UserCompany.
I have associations
Company.belongsToMany(User, {
as: 'users',
through: {
model: UserCompany,
unique: false
},
onDelete: 'cascade'
});
User.belongsToMany(Company, {
as: 'companies',
through: {
model: UserCompany,
unique: false
},
onDelete: 'cascade'
});
I am trying to query companies buy runing this code
sql.Company.findAll({
where: query,
include: [{model:sql.User, as:'users', attributes: ['id', 'first_name', 'last_name', 'email']}],
order: sort,
limit: limit,
offset: offset
})
I have two elements of UserCompany with CompanyId=10 and UserId=50, but query returns only one of them.
It returns array
{id: 10,
...,
users: {
id: 50,
...,
UserCompany: {}
}
}
So UserCompany is not array, it is just one element. But I want to get them all.
How can I fix my association code?
Your associations are right. The UserCompany property isn't supposed to be showing all of each user's UserCompany relationships -- it's just showing the one that got the user included in that query's results.
If what you're looking for is a list of companies associated with each user in the returned array, you can add an include for that:
Company.findAll({
include: [{
model: User,
as: 'users',
attributes: ['id', 'first_name', 'last_name', 'email'],
// Add this
include: [{
model: Company,
as: 'companies',
attributes: ['id', 'name']
}],
}],
})
This gives you the following format in the returned array:
{
id: 1,
first_name: 'Nelson',
last_name: 'Bighetti',
email: 'bighead#hooli.com',
UserCompany: {...},
companies: [
{ id: 1, name: 'Hooli', UserCompany: {...} },
{ id: 2, name: 'Pied Piper', UserCompany: {...} }
]
}