Joining to the same table multiple times with Sequelize - javascript

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?

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

Node.js Sequelize join two table and create a third table with the data created and count the sum of the same number of the first column

I am trying to use Sequelize and my brain must have been burnt from the whole project and now I cannot even join tables lol so here we go:
the task is I have two model that creates two tables and another model empty (so an empty table) I would like to join the two table in the third one and after in another statement I would like to count how many users(example) has the first column if there are two user with the same name means that is only one so will count just as one.
module.exports = (sequelize, Sequelize) => {
const Gig = sequelize.define("gig", {
a: {
type: Sequelize.STRING,
},
b: {
type: Sequelize.STRING,
},
c: {
type: Sequelize.STRING,
}
});
return Gig;
};
module.exports = (sequelize, Sequelize) => {
const Gig1 = sequelize.define("gig1", {
a: {
type: Sequelize.STRING,
},
b: {
type: Sequelize.STRING,
},
c: {
type: Sequelize.STRING,
}
});
return Gig1;
};
module.exports = (sequelize, Sequelize) => {
const Gig2 = sequelize.define("gig2", {
a: {
type: Sequelize.STRING,
},
b: {
type: Sequelize.STRING,
},
c: {
type: Sequelize.STRING,
}
});
return Gig2;
};
this is in another file
router.get("/", (req, res) => {
db.Gig.findAll({here the magic should happen thanks guys})
.then((users) => {
console.log(users);
res.sendStatus(200);
})
.catch((err) => {
console.log(err);
});
});
so Gig+Gig1 = Gig2 then count the user in a (so give me the sum without clones)
I found this article written below that is similar to what I am looking for but adding the result to Gig2 that is empty and then do the calcs.
*
UNION ALL and an exclusion join
One way to make UNION include only the duplicates I want is to use an exclusion join to eliminate anything from the second result that is already included in the first, like this:
select * from apples as a
left outer join oranges as o on a.price = o.price
union all
select * from apples as a
right outer join oranges as o on a.price = o.price
where a.price is null;
This handles duplicate rows correctly and doesn’t include anything it shouldn’t. It’s necessary to use UNION ALL instead of plain UNION, which would eliminate the duplicates I want to keep. This may be significantly more efficient on large result sets, since there’s no need to sort and remove duplicates.

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

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??

How does group by works in sequelize?

I am looking for group by queries through Sequelize and cannot seem to find any documentation.
SELECT column, count(column)
FROM table
GROUP BY column
issue: https://github.com/sequelize/sequelize/issues/348
User.findAll({
group: ['field']
})
i use sequelize#2.0.0-dev9
I think you looking for something like this:
Table.findAll({
attributes: ['column1',
sequelize.fn('count', sequelize.col('column2'))],
group: ["Table.column1"]
}).success(function (result) { });
Update: Newer versions of Sequelize uses .then instead of .success.
Table.findAll({
attributes: ['column1',
sequelize.fn('count', sequelize.col('column2'))],
group: ["Table.column1"]
}).then(function (result) { });
Group by with count sequelize ORM
'field' - custom user input, you can rename this string, it's your field(column) in database
'count' - reserved sequelize keyword string need for get count in sequelize
'cnt' - custom user input, you can rename this string, it's your output count
Sequelize version 3.25.0
User.findAll({
attributes: ['field', [sequelize.fn('count', sequelize.col('field')), 'cnt']],
group: ['field'],
})
Try this -
Table.count(
{
attributes: ['column'],
group: 'column',
}
Example: how to add an convenient alias to the grouped function column.
I did this as a separate response mostly because it wouldn't format well in a comment... otherwise I would have just added it to Sampat's answer.
function getSumsBySomeId() {
const criteria = {
attributes: ['some_id', [sequelize.fn('sum', sequelize.col('some_count')), 'some_count_sum']],
group: ['some_id'],
raw: true
};
return Table.getAll(criteria);
}
YIELDS:
{ some_id: 42, some_count_sum: 100 },
{ some_id: 43, some_count_sum: 150 }
...
etc.
Your code should look something like these using ES6 standard.
Table.findAll({ attributes: ['column1', sequelize.fn('count', sequelize.col('column2'))], group: ["Table.column1"] }).then( (result) => { })
**Try this*
Table.findAll({
group: ['column']
})
You need to use row.get('count') to get the count, row.count won't work
The API mentioned at in this answer is correct, but there were a few tricks I was missing in order to actually get the count results out.
As mentioned at: How do I select a column using an alias you need to use .get() for attribute aliased columns for some reason.
And another thing: you need to use parseInt to get an integer out of PostgreSQL count: Postgres sequelize raw query to get count returns string value due to bigint shenanigans.
Minimal runnable example also demonstrating ORDER BY, WHERE and HAVING:
main.js
#!/usr/bin/env node
// https://cirosantilli.com/sequelize-example
const assert = require('assert')
const { DataTypes, Op } = require('sequelize')
const common = require('./common')
const sequelize = common.sequelize(__filename, process.argv[2])
;(async () => {
const UserLikesPost = sequelize.define('UserLikesPost', {
userId: {
type: DataTypes.INTEGER,
},
postId: {
type: DataTypes.INTEGER,
},
}, {})
await UserLikesPost.sync({force: true})
await UserLikesPost.create({userId: 1, postId: 1})
await UserLikesPost.create({userId: 1, postId: 2})
await UserLikesPost.create({userId: 1, postId: 3})
await UserLikesPost.create({userId: 2, postId: 1})
await UserLikesPost.create({userId: 2, postId: 2})
await UserLikesPost.create({userId: 3, postId: 1})
await UserLikesPost.create({userId: 4, postId: 1})
// Count likes on all posts but:
// - don't consider likes userId 4
// - only return posts that have at least 2 likes
// Order posts by those with most likes first.
const postLikeCounts = await UserLikesPost.findAll({
attributes: [
'postId',
[sequelize.fn('COUNT', '*'), 'count'],
],
group: ['postId'],
where: { userId: { [Op.ne]: 4 }},
order: [[sequelize.col('count'), 'DESC']],
having: sequelize.where(sequelize.fn('COUNT', '*'), Op.gte, 2)
})
assert.strictEqual(postLikeCounts[0].postId, 1)
assert.strictEqual(parseInt(postLikeCounts[0].get('count'), 10), 3)
assert.strictEqual(postLikeCounts[1].postId, 2)
assert.strictEqual(parseInt(postLikeCounts[1].get('count'), 10), 2)
assert.strictEqual(postLikeCounts.length, 2)
await sequelize.close()
})()
common.js
const path = require('path');
const { Sequelize } = require('sequelize');
function sequelize(filename, dialect, opts) {
if (dialect === undefined) {
dialect = 'l'
}
if (dialect === 'l') {
return new Sequelize(Object.assign({
dialect: 'sqlite',
storage: path.parse(filename).name + '.sqlite'
}, opts));
} else if (dialect === 'p') {
return new Sequelize('tmp', undefined, undefined, Object.assign({
dialect: 'postgres',
host: '/var/run/postgresql',
}, opts));
} else {
throw new Error('Unknown dialect')
}
}
exports.sequelize = sequelize
package.json:
{
"name": "tmp",
"private": true,
"version": "1.0.0",
"dependencies": {
"pg": "8.5.1",
"pg-hstore": "2.3.3",
"sequelize": "6.5.1",
"sqlite3": "5.0.2"
}
}
and PostgreSQL 13.4 on Ubuntu 21.10. GitHub upstream.
Generated PostgreSQL query:
SELECT
"postId",
COUNT('*') AS "count"
FROM
"UserLikesPosts" AS "UserLikesPost"
WHERE
"UserLikesPost"."userId" != 4
GROUP BY
"postId"
HAVING
COUNT('*') >= 2
ORDER BY
"count" DESC;
JOIN + GROUP BY + aggregate
See: Sequelize query with count in inner join
To create a query like
SELECT key, COUNT(ref) FROM MyModel GROUP BY key
You can do this as follows
const results = await MyModel.findAll({
attributes: ['key', [Sequelize.fn('COUNT', Sequelize.col('ref')), 'count']],
group: ['key']
});
Optionally you can cast the result to something like (MyModel & { count: number })[]
Finally, to extract the count for each row, you'll need to use the getDataValue function. e.g.
results.map(r => r.getDataValue('count'))

Categories