How to query sequelize model by association, but include all associated objects? - javascript

Trying to query by all association's attribute, but get all associations
# FAQs: { id: 1, name: 'How to do it?' }, { id: 2, name: 'How to FIX it?' }
# tags: { id: 1, slug: 'api' }, { id: 2, slug: 'beta' }
# taggings: { id: 1, faqId: 1, mainEntityId: 1, mainEntityType: 'faq' }, { id: 2, faqId: 1, mainEntityId: 2, mainEntityType: 'faq' }
const query = { slugs: ['api'] }
const foundFAQs = await this.models.FAQ.findAll({
where: {
'$taggings.tag.slug$': { $in: query.slugs },
},
include: [{
model: this.models.Tagging,
as: "taggings",
include: [{
model: this.models.Tag,
as: 'tag',
}],
}],
})
My model definition:
models.Tagging.belongsTo(models.Tag, { as: 'tag', onDelete: 'cascade' });
models.Tag.hasMany(models.Tagging, { as: 'taggings', onDelete: 'cascade' });
models.Tag.belongsToMany(models.FAQ, { through: models.Tagging, as: 'faqs' });
models.FAQ.hasMany(models.Tagging, { as: 'taggings', onDelete: 'cascade' });
models.FAQ.belongsToMany(models.Tag, { through: models.Tagging, as: 'tags' });
What do you expect to happen?
I want to get all FAQs what has associated TAG api and have all its tags.
Object:
{
id: 1,
name: 'How to do it?',
tags: [
{ id: 1, slug: 'api' },
{ id: 2, slug: 'beta' }
]
}
What is actually happening?
Query returns object:
{
id: 1,
name: 'How to do it?',
tags: [
{ id: 1, slug: 'api' }
]
}
Output
SELECT \"faq\".\"id\", \"faq\".\"name\", \"faq\".\"bankId\", \"faq\".\"priority\",
\"faq\".\"publishedLocales\", \"faq\".\"createdAt\", \"faq\".\"updatedAt\", \"taggings\".\"id\"
AS \"taggings.id\", \"taggings\".\"tagId\" AS \"taggings.tagId\", \"taggings\".\"locked\"
AS \"taggings.locked\", \"taggings\".\"sdkId\" AS \"taggings.sdkId\", \"taggings\".\"guideId\"
AS \"taggings.guideId\", \"taggings\".\"newsId\" AS \"taggings.newsId\", \"taggings\".\"faqId\"
AS \"taggings.faqId\", \"taggings\".\"apiId\" AS \"taggings.apiId\", \"taggings\".\"createdAt\"
AS \"taggings.createdAt\", \"taggings\".\"updatedAt\" AS \"taggings.updatedAt\", \"taggings->tag\".\"id\"
AS \"taggings.tag.id\", \"taggings->tag\".\"name\" AS \"taggings.tag.name\", \"taggings->tag\".\"slug\"
AS \"taggings.tag.slug\", \"taggings->tag\".\"tagType\" AS \"taggings.tag.tagType\", \"taggings->tag\".\"mainEntityId\"
AS \"taggings.tag.mainEntityId\", \"taggings->tag\".\"createdAt\"
AS \"taggings.tag.createdAt\", \"taggings->tag\".\"updatedAt\"
AS \"taggings.tag.updatedAt\" FROM \"faqs\" AS \"faq\" INNER JOIN \"taggings\"
AS \"taggings\" ON \"faq\".\"id\" = \"taggings\".\"faqId\" LEFT OUTER JOIN \"tags\"
AS \"taggings->tag\" ON \"taggings\".\"tagId\" = \"taggings->tag\".\"id\" WHERE \"faq\".\"bankId\" = 'bank.csas'
AND \"taggings->tag\".\"slug\" IN ('faq') ORDER BY \"faq\".\"priority\" DESC;
Dialect: postgres
Dialect version: pg#^6.1.0
Database version: PostgreSQL 10.1
Sequelize version: 4.23.2
Tested with the latest release: No (4.23.2)
Node: 8.6.0
I am not sure if it is a bug or I'm doing something wrong.
Thanks

You're looking to include all related data for each record, but filter those records on that related data. You're going to want to get Sequelize to generate SQL similar to:
SELECT "faq"."id", ....
FROM "faqs" AS "faq"
INNER JOIN "taggings" AS "taggings" ON "faq"."id" = "taggings"."faqId"
LEFT OUTER JOIN "tags" AS "taggings->tag" ON "taggings"."tagId" = "taggings->tag"."id"
LEFT OUTER JOIN "tags" AS "taggings->tagdata" ON "taggings"."tagId" = "taggings->tagdata"."id"
WHERE "faq"."bankId" = 'bank.csas' AND "taggings->tag"."slug" IN ('faq')
ORDER BY "faq"."priority" DESC;
Something like this might work:
const foundFAQs = await this.models.FAQ.findAll({
where: {
'$taggings.tag.slug$': { $in: query.slugs },
},
include: [{
model: this.models.Tagging,
as: "taggings",
include: [{
model: this.models.Tag,
as: 'tag',
},{
model: this.models.Tag,
as: 'tagdata',
}],
}],
})
The idea here is that you're performing a join on which you are filtering, and then another join to get the extra records for the filtered rows.

This worked for me today -- nested includes:
getReferralPlanForThisCode(parent, args, context) {
let {referralCode} = args;
return Promise.resolve()
.then(() => {
let referralPlan = connectors.ReferralPlans.findAll({
include: [{
model: connectors.ReferralCodes,
where: {unique_referral_code: referralCode},
include: [{
model: connectors.epUserData, as: 'referrer',
}],
}],
})
return referralPlan;
})
.then(referralPlan => {
return referralPlan;
})
.catch((err) => {
console.log(err);
});
}

Related

Request with 2 many to many with sequelize (merge result)

I would like to know how can I "merge" the result to get result from 2 tables.
Currently I have 3 tables :
posts [id, title...]
feeds [id, fk_people_id, fk_post_id]
posts_peoples [id, fk_people_id, fk_post_id]
I would like to return the posts where people is present in feeds table and posts_peoples table.
When I run this request, I have only the post where people is present in feeds table :
// Request
const resultRequest = await db.Post.findAll({
include: [
{
model: db.Feed,
as: "Feed",
where: {
fk_people_id: 2,
},
},
],
})
When I run this request, I have only the post where people is present in posts_peoples table :
// Request
const resultRequest = await db.Post.findAll({
include: [
{
model: db.PostPeople,
as: "PostPeople",
where: {
fk_people_id: 2,
},
},
],
})
When I add feeds and posts_peoples, it doesn't work.
// Request
const resultRequest = await db.Post.findAll({
include: [
{
model: db.Feed,
as: "Feed",
where: {
fk_people_id: 2,
},
},
{
model: db.PostPeople,
as: "PostPeople",
where: {
fk_people_id: 2,
},
},
],
})
The result is an empty array.
Add required: false to your includes to generate SQL with a LEFT JOIN to include results from both tables.
// Request
const resultRequest = await db.Post.findAll({
include: [{
model: db.Feed,
as: "Feed",
where: {
fk_people_id: 2,
},
required: false,
},
{
model: db.PostPeople,
as: "PostPeople",
where: {
fk_people_id: 2,
},
required: false,
}],
})

Sequelize - After findAll, delete with a single query

I have a table with an associated table. I am using beforeDestroy to remove any associated records up deletion.
Model.beforeDestroy(async (category: any) => {
const items = await Category.findAll({
where: {
category_id: category.id,
},
attributes: ['id'],
raw: true,
});
console.log(items); // [ { id: 2 }, { id: 3364 }, { id: 3365 } ]
items.map((item: any) => {
Category.destroy({ where: { id: item.id } });
});
});
}
I am trying to delete the matching items with a single destroy query rather than mapping through.
try:
Category.destroy({ where: { id:items.map(item=>item.id)} });

How I can include array of model?

var user = await db.user.findOne({
attributes: attributes,
include: ['charges',
'availability',
'practice',// 'services',
'education',// 'user_role',
'address',
'user_location',
// 'isAvailableStatus',
{ model: db.user_role, attributes: role_attributes },
{
model: db.user_service, as: 'services',
// ThIS IS MY QUESTION
include : [db.service_group, as : 'group']
//
},
],
where: {id: 0}
});
User -> user_role-> one of user_role # This is working
User -> user_service-> array of service_group # this is not working// my question
How I can include array of model?
You need to modify your code as follows. If you have defined associations properly then you shall get an intended result.
var user = await db.user.findOne({
attributes: {
include: [
'charges',
'availability',
'practice', // 'services',
'education', // 'user_role',
'address',
'user_location'
// 'isAvailableStatus',
]
},
include: [
{ model: db.user_role, attributes: role_attributes },
{
model: db.user_service,
as: 'services',
include: [{ model: db.service_group, as: 'group' }]
}
],
where: { id: 0 }
});
Hope it helps!

Sequelize relationship query returns duplicate data

I'm querying customer orders for a specified customer using Sequelize relationships.
index.js
var results2 = await customerService.getOrders(1);
console.log(results2);
service.js
exports.getOrders = function (id) {
return customerModel.findAll({
raw: true,
include: [{
model: orderModel,
where: { customer_idcustomer: id }
}],
}).then(r => r);
};
results
[ { idcustomer: 1,
customername: 'hello world',
'orders.idorder': 1,
'orders.orderdesc': 'order description 1',
'orders.customer_idcustomer': 1 },
{ idcustomer: 1,
customername: 'hello world',
'orders.idorder': 2,
'orders.orderdesc': 'Test 456',
'orders.customer_idcustomer': 1 },
{ idcustomer: 1,
customername: 'hello world',
'orders.idorder': 3,
'orders.orderdesc': 'Test 123',
'orders.customer_idcustomer': 1 } ]
expected
[ { idcustomer: 1,
customername: 'hello world',
'orders: [{
'orders.idorder': 1,
'orders.orderdesc': 'order description 1',
'orders.customer_idcustomer': 1 },
},
{
'orders.idorder': 2,
'orders.orderdesc': 'order description 2',
'orders.customer_idcustomer': 1 },
},
{
'orders.idorder': 3,
'orders.orderdesc': 'order description 3',
'orders.customer_idcustomer': 1 },
}]
]
All you need is to remove raw: true, from query ,
as it will return plain/flat object , and that will convert your object as it looks now.
exports.getOrders = function (id) {
return customerModel.findAll({
// raw: true, // <------ Just remove this line
include: [{
model: orderModel,
where: { customer_idcustomer: id }
}],
}).then(r => r);
};
Note : You should put the where condition in upper level as per your
logic
exports.getOrders = function (id) {
return customerModel.findAll({
where: { id: id } ,
// raw: true, // <------ Just remove this line
include: [{
model: orderModel
}]
}).then(r => r);
};
Try removing raw key value from your query.
Finder methods are intended to query data from the database. They do
not return plain objects but instead return model instances. Because
finder methods return model instances you can call any model instance
member on the result as described in the documentation for instances.
If you want to get the data without meta/model information then map your results using
{ plain: true }
Good sequelize examples in docs
Example:
const getPlainData = records => records.map(record =>
record.get({ plain: true }));
// Your code
return customerModel.findAll({
// raw: true, <= remove
include: [{
model: orderModel,
where: { customer_idcustomer: id }
}],
}).then(getPlainData);
In my case, having
raw: true
in the options didn't make any difference.
I added
distinct: true
and the issue disappeared.
I was using findAndCountAll, though.
Documentation: https://sequelize.org/master/class/lib/model.js~Model.html

Sequelize create include existing object

I'm having an issue with sequelize on create with associated model with an "unique" value. It works when I create a "book" with a new "category", but if the "category" already exists it throws an error because it tried to duplicate an "unique" value. Is there a way to make sequelize.create try to use findAndCreate on a associated model?
My setup
Book:
const Book = sequelize.define('book', {
title: Sequelize.STRING,
})
Category:
const Category = sequelize.define('category', {
id: Sequelize.BIGINT,
name: {
type: Sequelize.STRING,
unique: true,
},
})
and the relationship:
Book.belongsTo(Category, { foreignKey: 'category_id' })
So, when i create a book i can write:
Book.create(
{
title: 'Book',
category: {
name: 'Foo'
}
}, {
include: [
{
model: Category
}
]
}
)
if after that I create another book:
Book.create(
{
title: 'Another Book',
category: {
name: 'Foo'
}
}, {
include: [
{
model: Category
}
]
}
)
it throws the error saying category.name must be unique
For anyone with the same question, this was my final solution:
On my services.js I created the function create that receives a request(req) and all my models already initialized.
create: (req, models) => {
const { category, ...rest } = req.body
if (category && category.name && !rest.category_id) {
return models.Category
.findOrCreate({
where: {
name: category.name
}
})
.then(([returnedCategory]) => {
return models.Book.create(
{
...rest,
category_id: returnedCategory.id
},
{ ...defaultOptions(models) }
)
})
} else {
return models.Book.create(rest, { ...defaultOptions(models) })
}
},
defaultOptions looks like this:
const defaultOptions = models => {
return {
attributes: {
exclude: ['category_id', 'created_at', 'updated_at'],
},
include: [
{ model: models.Category, attributes: ['id', 'name'] },
],
}
}

Categories