sequalize join in where clause to relate two tables - javascript

I have two table t1 and t2 related as one to many, t1 has id primary key and t2 has id as foreign key
How can I query in sequalize to find records from t2 which have t1.id ='value' using joins
it should give res as following sql query gives
SELECT[t2].[id], [t2].[c1], [t2].[c2], [t2].[c3], [t2].[c4], [t1].[id] AS[t1.id], [t1].[cID] AS[t1.cID] FROM[t2] AS[t2] INNER JOIN[t1] AS[t1] ON[t2].[id] = [t1].[id] AND[t1].[cID] = 'value' WHERE[t2].[sF] = 'value';

1st we have to associate the two table t1 and t2,
as par given info in question this association should as
t1.hasMany(t2, { foreignKey: 'id' });
t2.belongsTo(t1, { foreignKey: 'id' })
then
t2.findAll(
{
attributes: ['id','c1','c2','c3','c4'
],
required: true,
where: {
sF: 'value',
},
include: [
{
model: t1,
attributes: ['cID'],
required: true,
where: {
cID:'value',
}
}
]
} ).then((result)=>{console.log(result)})
assuming t1 and t2 are model declared in sequalize

Related

Sequelize Op.notIn with Sequelize Model

Hello i have a mysql query which is working fine in sequelize.query and the query is
select list_name from lists l where l.list_id not in
(SELECT sub.list_id from list_sub_activities sub left join.
Activities a on a.list_act_id = sub.list_act_id where a.agency_id = 2)
and i want to do the same using the sequelize model, i have tried but i think i am missing something.
List of Package ---> lists
List_of_Packages.findAll({
attributes: ['list_name'],
where: {
list_id: {
[Op.notIn]: [List_sub_Activities.findAll({
attributes: ['list_id'],
include: {
model: Activities,
required: false,
where: {
agency_id: 2
}
}
})
]
}
}
}).then((response) => {
console.log(response);
})
I appreciate that if you help me.
Thank you !!!
The findAll() (and other query methods) are asynchronous so you will need to resolve the promise (or use a callback) to resolve the value before you can pass the list_ids to Op.notIn. It will also return an array of objects with a property of list_id, so you will need to map this to an array of integers before you can use it. You can also pass in raw: true so that it will not generate Sequelize Instances from your results and will instead return plain javascript objects - this is more efficient than creating objects just to fetch a single property.
By setting required: false on the Activities include you will be returning all List_sub_Activities and not filtering on them (some will be null in your results). This is likely not what you intended.
This example uses async/await for clarity instead of thenables. Note that this is not the most efficient as it requires multiple database queries, the ideal solution would be to use a LEFT JOIN and then remove items where the package.list_id IS NULL (see second example).
// get an array of Activities with the list_id set
const activities = await List_sub_Activities.findAll({
attributes: ['list_id'],
include: {
model: Activities,
// don't use required: false to only return results where List_sub_Activities.Activities is not null
// required: false,
where: {
agency_id: 2,
},
},
raw: true,
});
// map the property to an array of just the IDs
const activityIds = activities.map((activity) => activity.list_id);
// now you can pass the activityIds to Op.notIn
const packages = await List_of_Packages.findAll({
attributes: ['list_name'],
where: {
list_id: {
[Op.notIn]: activityIds,
},
},
});
With thenables.
List_sub_Activities.findAll(...)
.then((activities) => activities.map((activity) => activity.list_id))
.then((activityIds) => List_of_Packages.findAll(...))
.then((packages) => {
console.log(packages);
});
This example LEFT JOINs List_of_Packages to List_sub_Activities which is JOINed to Activities with a WHERE setting the agency_id to 2, then only returns results from List_of_Packages where the List_sub_Activities.list_id is NULL (nothing was matched on the LEFT JOIN). This should return the same results as above in a single query.
// Get List_of_Packages where there is no match in List_sub_Activities after
// it is joined to Activities with the agency_id set.
const agencyId = 2;
const packages = await List_of_Packages.findAll({
attributes: ['list_name'],
include: {
model: List_sub_Activities,
// we don't need to actually fetch the list_id
attributes: [],
include: {
model: Activities,
where: {
agency_id: agencyId,
},
},
// uses a LEFT JOIN
required: false,
},
// only return results where the List_sub_Activities.list_id is null
where: sequelize.where(sequelize.col('List_sub_Activities.list_id'), 'IS', null),
});

Stuck in generating the right LEFT join queries with count using the Sequelize models in the nodejs app

I'm using Sequelize in my NodeJS app with Postgres as database. Following is my correct SQL query which gives me right set of data while running them on database server :-
SELECT
"expertises"."uuid" AS "id",
"expertises"."display_name" AS "name",
"expertises"."modified_by" AS "modifiedBy",
(
SELECT COUNT(expertise_uuid)
FROM expertise_endorsements
WHERE expertise_uuid = "expertises"."uuid"
) AS "users"
FROM "expertises" AS "expertises"
LEFT JOIN "expertise_endorsements" AS "expertise_endorsements"
ON "expertise_endorsements"."expertise_uuid" = "expertises"."uuid"
LIMIT '16' OFFSET '1';
However when I run the app, the ORM is generating the wrong SQL query without any count (as shown below):-
SELECT
"expertises".*,
"expertise_endorsements"."id" AS "expertise_endorsements.id",
"expertise_endorsements"."expertise_uuid" AS "expertise_endorsements.expertise_uuid"
FROM
(
SELECT
"expertises"."id",
"expertises"."uuid" AS "id",
"expertises"."display_name" AS "name",
"expertises"."modified_by" AS "modifiedBy",
"expertises"."uuid"
FROM
"expertises" AS "expertises"
ORDER BY
"expertises"."name" DESC LIMIT '16' OFFSET '1'
)
AS "expertises"
LEFT OUTER JOIN
"expertise_endorsements" AS "expertise_endorsements"
ON "expertises"."uuid" = "expertise_endorsements"."expertise_uuid"
ORDER BY
"expertises"."name" DESC;
This is my model association :-
const consts = require("../services/static/constants");
const Expertises = require("./models/expertises.model");
const ExpertisesEndorsees = require("./models/expertises_endorsees.model");
const ExpertisesEndorsements = require("./models/expertise_endorsements.model");
exports.setAssociations = (db, Sequelize, type) => {
const expertisesModel = Expertises(db, Sequelize);
const expertisesEndorseesModel = ExpertisesEndorsees(db, Sequelize);
const expertisesEndorsementsModel = ExpertisesEndorsements(db, Sequelize);
expertisesModel.hasMany(expertisesEndorsementsModel, { sourceKey: consts.uuidField, foreignKey: consts.expertiseUuid, as: consts.modelAliases.EXPERTISE_ENDORSEMENTS });
expertisesEndorsementsModel.belongsTo(expertisesModel, { foreignKey: consts.uuidField, as: consts.modelAliases.EXPERTISE_ENDORSEMENTS });
return { expertisesModel, expertisesEndorseesModel, expertisesEndorsementsModel };
};
My logic is below. I'm stuck in understanding and applying the count properly using the Sequelize:-
const models = dbUtils.setAssociations(db, consts.getExpertiseFlow);
const includesData = [
{
model: models.expertisesEndorsementsModel,
attributes: [consts.expertiseUuid],
// attributes: [
// sequelize.fn("COUNT", sequelize.col(`${consts.modelAliases.EXPERTISE_ENDORSEMENTS}.${consts.get_endorsee_uuid}`)), "users"
// ],
as: consts.modelAliases.EXPERTISE_ENDORSEMENTS,
required: false
}
];
let {count: expertiseCount, rows: expertises} = JSON.parse(JSON.stringify(
await manageExpertise.getSortedExpertises(models, consts.get_expertises, pageOffset, pageLimit, orderBy, includesData, false)));
Method:-
exports.getSortedExpertises = (models, attributes, offset, limit, orderBy, includes, raw=true) => {
return models.expertisesModel
.findAndCountAll({
attributes,
include: includes,
offset: offset,
limit: limit,
//order: orderBy,
raw
}).then(data => {
return data;
})
.catch(err => {
throw err;
});
};
Would really appreciate if you can assist me in resolving this issue and let me know the root cause. Thank you

Joining to the same table multiple times with Sequelize

I am trying to write a query with that would roughly do:
SELECT Challenge.id, Challenge.name, count(AcceptedChallenge.userId) AS attempts, count(a.met) AS met, count(b.active) AS active, count(c.id) AS WHOLE
FROM Challange
LEFT JOIN AcceptedChallenge ON Challenge.id = AcceptedChallenge.challengeId,
LEFT JOIN AcceptedChallenge AS a ON Challenge.id = a.challengeId
LEFT JOIN AcceptedChallenge AS b ON Challenge.id = b.challengeId
LEFT JOIN AcceptedChallenge AS c ON Challenge.id = c.challengeId
WHERE a.met = true
AND b.userId = id and b.active = true
AND c.userId = id;
Tried multiple versions, including the below:
const Sequelize = require('sequelize');
const ChallengeController = async ({ user: { id } }) => {
const challenges = await Challenge
.findAll({
attributes: {
include: [[Sequelize.fn('count', Sequelize.col('AcceptedChallenges.userId')), 'attempts'],
[Sequelize.fn('count', Sequelize.col('a.met')), 'met']]
},
include: [{
model: AcceptedChallenge, attributes: [],
required: false,
}, {
model: AcceptedChallenge, attributes: [],
as: 'a',
where: { userId: id, met: true },
required: false,
}],
group: ['Challenge.id']
})
.catch((e) => {
console.log("error:", e);
throw new HTTP404Error('Challenges not found');
});
return challenges;
};
It is not recognizing my associations. Please advise. The version here results in: SequelizeEagerLoadingError: AcceptedChallenge is associated to Challenge using an alias. You've included an alias (a), but it does not match the alias(es) defined in your association (AcceptedChallenges).
When including the AcceptedChallenge model just once, it calculates attempts just fine. I am perplexed as to how I could do the include/JOIN multiple times to get the result, which I need from a single SQL request.
This works for me when the association is repeated, once for each alias needed in your query (example below, but obviously your association may be different).
ChallengeController.hasMany(AcceptedChallenge, {as: 'a', foreignKey: 'challengeId'});
ChallengeController.hasMany(AcceptedChallenge, {as: 'b', foreignKey: 'challengeId'});
ChallengeController.hasMany(AcceptedChallenge, {as: 'c', foreignKey: 'challengeId'});
Are you doing something similar?

Set sort order for mongoose document

I have a mongoose schema:
models/profile.js
var mongoose = require("mongoose");
var passportLocalMongoose = require("passport-local-mongoose");
var profileSchema = new mongoose.Schema({
username: String,
complete: { type: Boolean, default: false },
email: { type: String, default: "" },
city: { type: String, default: "" }
}, { discriminatorKey: 'accountType' });
profileSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model('Profile', profileSchema);
That has two discriminators associated with it:
models/finder.js
var Profile = require('./profile');
var Finder = Profile.discriminator('finder', new mongoose.Schema({
position: { type: String, default: "" },
skills: Array
}));
module.exports = mongoose.model("Finder");
models/helper.js
var Profile = require('./profile');
var Helper = Profile.discriminator('helper', new mongoose.Schema({
jobTitle: { type: String, default: "" },
lastPosition: { type: String, default: "" }
}));
module.exports = mongoose.model("Helper");
I am using this within an express framework, and on one page - shown below - I want to iterate over the key/value pairs in Profile to build a table.
I would like to retain the order designated in the Schema, so that the table ordering is consistent between pages.
Is it possible to define a sort order on Schema creation?
Here's my profile.ejs file where I make the table:
<table class="table profile-display">
<tbody>
<% for(var key in profile.toObject({versionKey: false})) { %>
<% if (key != '_id') { %>
<% if (profile[key].length === 0){ %>
<tr class="incomplete-warning table-rows">
<% } else { %>
<tr class="table-rows">
<% } %>
<td class="key-text"><%=key.toUpperCase()%>:</td>
<td><%=profile[key]%></td>
</tr>
<% } %>
<% } %>
</tbody>
</table>
Please let me know if I can provide more information
You can use retainKeyOrder
By default, mongoose reverses key order in documents as a performance optimization. For example, new Model({ first: 1, second: 2 }); would actually be stored in MongoDB as { second: 2, first: 1 }. This behavior is considered deprecated because it has numerous unintended side effects, including making it difficult to manipulate documents whose _id field is an object.
Mongoose >= 4.6.4 has a retainKeyOrder option for schemas that ensures that mongoose will always keep the correct order for your object keys.
var testSchema = new Schema({ first: Number, second: Number }, { retainKeyOrder: true });
var Test = mongoose.model('Test', testSchema);
Test.create({ first: 1, second: 2 }); // Will be stored in mongodb as `{ first: 1, second: 2 }`
References:
https://github.com/Automattic/mongoose/issues/1514
https://mongoosejs.com/docs/4.x/docs/guide.html#retainKeyOrder
Every browser handles object sorts differently. I suggest returning a JSON object which maps your schema that has a sort value or such and iterates over your profile.ejs template. Then you can just map the value from mongoose output key whichever you like.
{
1 : 'username',
2 : 'type',
3 : 'complete',
4 : 'email',
5 : 'city',
6 : 'position',
7 : 'skills'
}
or an array
[
'username',
'type',
'complete',
'email',
'city',
'position',
'skills'
]
Then from that, you can just map your schema from the value of the object or from the array. I like using an array in this case as its easier to iterate over by just using the index key. It depends on your usage and purpose.
Hope it help.
UPDATE: To minimizing in hardcoding the schema twice you can create an object which has sorts with the schema value.
Code Example.
// Construct your object
var objSchema = {
obj1 : {
schema : { type: String },
order : 2
},
obj2 : {
schema : { type: String },
order : 3
},
obj3 : {
schema : { type: String },
order : 1
}
}
// Function to construct your schema object and sort array
function mapSortSchema(objs) {
var result = {
schema : {},
order : []
}
for (var obj in objs) {
result.schema[obj] = objs[obj].schema;
result.order.push({
'key': obj,
'order': objs[obj].order
});
}
result.order.sort(function(a, b) {
return a.order - b.order;
});
return result;
}
Now you have schema for mongoose and order for template.
var mapSchema = mapSortSchema(objSchema);
// You can now use this with your mongoose schema
var forMongooseSchema = mapSchema.schema;
// result {obj1 : { type: String }, obj2 : { type: String }, obj3 : { type: String }}
// You can now use this to loop through your template
var forTemplateLoop = mapSchema.order;
// result [{key: "obj3", order: 1}, {key: "obj1", order: 2}, {key: "obj2", order: 3}]
Haven't tested this in mongoose but it will give you the basic idea, you can improve the function base on your need. Hope it helps.

Sequelize How to customize your our ON condition in a join?

I have these three tables:
A: B: C:
AId BId CId
b_id b_id
I want to do this:
SELECT A.*, B.*, C.*
FROM A
JOIN B
ON A.b_id = B.BId
JOIN C
ON B.BId = C.b_id #1
I have tried to do this with sequelize like:
AModel.findOne({
attributes: [],
where: { Aid: idByParameter },
include: [{
model: BModel,
through: {
// as: 'B', Not necessary
attributes: []
},
include: [{
model: CModel,
// on: 'B.BId = C.b_id', #2
through: {
attributes: []
}
}]
}]
}).then((res) => {
return res;
})
The first join, AModel with BModel works correctly because I have set the relation between A and B.
The problem is when I try to make a join between BModel and CModel because the relation is defined in the other side.
Are there any possibility to implement this joint with sequelize??
Sequelize is trying to execute this query:
SELECT A.*, B.*, C.*
FROM A
JOIN B
ON A.b_id = B.BId
JOIN C
ON B.c_id = C.CId #3
But I want to change #3 to #1 with something similar to #2.
Is this possible??

Categories