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

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

Related

Sequelize – where with count

I'm having three models that extend Sequelize.Model and have migrations generated. The associations look like this:
Foo
Foo.belongsToMany(Bar, {
as: 'bars',
through: 'FooBar',
foreignKey: 'foo_id',
});
Bar
Bar.belongsToMany(Foo, {
as: 'bars',
through: 'FooBar',
foreignKey: 'bar_id',
});
FooBar
FooBar.belongsTo(Foo, {
as: 'foo',
foreignKey: 'foo_id',
});
FooBar.belongsTo(Bar, {
as: 'bar',
foreignKey: 'bar_id',
});
I'm trying to query Foo like this:
const foos = await Foo.findAll({
include: [{
model: Bar,
as: 'bars',
}],
});
The code works as expected and I get the array of bars for each of my foo.
How can I now query foos that only have bars count that is more than e.g. 2?
There is no direct way to do this in sequelize.
Two ways you can do it:
First way: (2 queries)
Find all the foo ids that have 2 bars in 1 query (probably raw query)
Add those foo ids as a filter in where of your findAll
Second way: (1 query)
Create a sequelize literal of a subquery that returns foo ids that have more than 2 bars and add that literal to where: { id: <subquery literal> }
Showing Second way:
subquery literal e.g.:
sequelize.literal(`(
SELECT
id
FROM foo
LEFT JOIN bar ON bar.foo_id = foo.id
WHERE COUNT(bar.id) > 2
GROUP BY foo.id
)`)
Final findAll:
const foos = await Foo.findAll({
where: {
id: sequelize.literal(`...`),
},
include: [{
model: Bar,
as: 'bars',
}],
});

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

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?

sequalize join in where clause to relate two tables

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

Find parent where child in array

I've got two tables,
Places
Products
What I am trying to achieve is to find Place By multiple products.
So basically.
find PLACE where product ids in [1,4,6]
The thing is that it looks for every place where product id = 1 OR id = 4. I want to find a place that contains all the products. So 3 conditionals must be achieved.
This is how it looks in sequelize
const eq = await Place.findAndCountAll({
include: [
{
model: PlaceProduct,
as: 'products',
where: {
productId: [1,2,5]
}
},
],
}
And it returns places which contain only of the required products.
I want all to be required.
Thanks!
You can check the count of records are exact of product_ids , if yes then thats the record you want , and if is less than that it will be ignored
const product_ids = [1, 2, 5];
const eq = await Place.findAll({
include: [{
model: PlaceProduct,
attributes : [],
as: 'products',
where: {
productId: product_ids // <----- product_ids
}
}, ],
group: ['place.id'], // <--- Not sure but , you might need to add place_product.id also here
having: {
$and: [
db.sequelize.where(db.sequelize.fn('count', db.sequelize.col('place.id')), product_ids.length), // <----- product_ids
]
}
}

Categories