The problem:
I can't retrieve an include inside include with Sequelize#5.21.2.
The first include its OK. But when i try to grab the other include, i only receive null.
The code:
let planCodes = ['monthly', 'annual'];
const plans = await Plan.findAll({
where: {
code: {
[Op.or]: planCodes
}
},
include: [{
model: PlanItem,
as: 'plan_items',
include: [{
model: Product,
as: 'product',
}]
}]
});
Model associations:
Plan
Plan.hasMany(PlanItem, { foreignKey: "plan_id" });
PlanItem
PlanItem.hasOne(Product, { foreignKey: "plan_item_id" });
The response:
[
{
"id": 175231,
"name": "mensal",
"interval": "months",
"interval_count": 1,
"code": "monthly",
"status": "active",
"metadata": {},
"created_at": "XXXX-XX-XX",
"updated_at": "XXXX-XX-XX",
"plan_items": [
{
"id": 190651,
"plan_id": 175231,
"product_id": 655939,
"created_at": "XXXX-XX-XX",
"updated_at": "XXXX-XX-XX",
"product": null
}
]
}
]
I already checked all details with the IDs at database and everything is OK.
I will admit that Sequelize likes to take all the spontaneity out of relationships, but you should use your alias in both the model definition AND the query. And they have to be the same. I'm sure there is some leeway, as you are, in fact, returning plan_items values. But whether "best practice" or "mandate", give this a go:
Plan.hasMany(PlanItem, { as: "plan_items", foreignKey: "plan_id" });
PlanItem.hasOne(Product, { as: "product", foreignKey: "plan_item_id" });
// You may also try defining the Product/PlanItem relationship the other way
Product.belongsTo(PlanItem, { as: "plan_item", foreignKey: "plan_item_id" });
Related
I'm trying to create custom controller for my Strapi (v3.6.10) model, but have a problem with populating one specific field. Here are my models:
Parent model:
{
"kind": "collectionType",
"collectionName": "clinic_pages",
"info": {
"name": "clinic-page",
},
"attributes": {
"clinic": {
"model": "clinic"
},
...otherParentModelFields,
}
}
Clinic:
{
"kind": "collectionType",
"collectionName": "clinics",
"attributes": {
...otherFields,
"schedule": {
"type": "component",
"repeatable": true,
"component": "clinic.schedule"
},
}
}
Schedule:
{
"collectionName": "components_clinic_schedules",
"info": {
"name": "schedule",
},
"attributes": {
"time": {...},
"day": {...}
}
}
I need to populate only few fields from parent model, including clinic. So I'm using this query:
await strapi.services[MODEL].findOne(ctx.query) and receiving full schedule data in response:
{
...otherFieldsOfModel,
clinic: {
id: 1,
...otherFields,
schedule: [
{ id: 1, time: '9:00', day: 'monday' },
{ id: 2, time: '10:00', day: 'tuesday' }
]
}
}
That's fine, I see clinic and schedule, and all other fields in parent model. But then I wand to add populate to reduce size of those other fields and what I got?. Every time I'm getting anything instead of populated data for clinic.schedule even if I'm adding it to populate list. In all this cases:
await strapi.services[MODEL].findOne(ctx.query, {});
await strapi.services[MODEL].findOne(ctx.query, {clinic: true});
await strapi.services[MODEL].findOne(ctx.query, ['clinic']);
await strapi.services[MODEL].findOne(ctx.query, ['clinic', 'clinic.schedule', 'clinic.schedule.day', 'clinic.schedule.time']);
and so on... I mean I tried any possible syntax variant and don't even got a schedule: null. This field not returning anymore in the response. How do I populate this collection?
P.S. Yes, I know that Strapi is v4 already, unfortunately I can't update it for now.
I have 3 collections in mongoDB with the following structure
Users:
"name":""
"email":""
"phone":""
"city":""
"customerID":""
Ships:
"address":""
"city":""
"pincode":""
"porderID":""
"customerID":""
Orders:
"productName":""
"quantity":""
"pricing":""
"mrp":""
"porderID":""
"customerID":""
How do I write a get request or an aggregate function that returns all customerIDs in a json like this?
[{
customerID:"",
BuyOrder:[{
porderID:"",
productName:"",
quantity:""
ShipmentDetail:[
address:""
city:""
pincode:""
]
}],
customerID:"",
BuyOrder:[{
porderID:"",
productName:"",
quantity:""
ShipmentDetail:[
address:""
city:""
pincode:""
]
}],
}]
Any suggestions would be helpful.
Is not quite clear what do you want... without an input/output example, without a valid JSON... is not easy but I think you are looking for something like this:
This query is a nested $lookup where for each user join the collection with orders using the customerID, and also, orders is joined with ships using customerID too.
db.users.aggregate([
{
"$lookup": {
"from": "orders",
"localField": "customerID",
"foreignField": "customerID",
"as": "BuyOrder",
"pipeline": [
{
"$lookup": {
"from": "ships",
"localField": "customerID",
"foreignField": "customerID",
"as": "ShipmentDetail"
}
}
]
}
},
])
Example here
You also can add a $project stage to output only fields you want, like this example where the result is:
[
{
"BuyOrder": [
{
"ShipmentDetail": [
{
"address": "",
"city": "",
"pincode": ""
}
],
"porderID": "",
"pricing": "",
"productName": "",
"quantity": ""
}
],
"customerID": ""
}
]
I have two different collections for two different type of products. Now, I want to fetch all documents from both collections for a particular user.
I know I can do that with 2 queries for each collection, merging them on the server side and sending the result to the user. Downside of this is that I have to fetch all documents for a user from both collections, which is not good for pagination. That is why I want to do it in one query, so I can leave a pagination logic to MongoDB as well.
Here is the example of collections and expected result:
Products_type_1
[
{
"name": "product_1",
"user": "user_1",
...
},
{
"name": "product_2",
"user": "user_2",
...
}
]
Products_type_2
[
{
"name": "product_3",
"user": "user_1",
...
},
{
"name": "product_4",
"user": "user_2",
...
}
]
The expected result:
[
{
"type": "Products_type_1",
"name": "product_1",
"user": "user_1",
...
},
{
"type": "Products_type_2",
"name": "product_3",
"user": "user_1",
...
}
]
You can use aggregation framework with $unionWith stage:
db.Products_type_1.aggregate([
{
"$match": {
"user": "user_1"
}
},
{
$unionWith: {
coll: "Products_type_2",
pipeline: [
{
"$match": {
"user": "user_1"
}
}
]
}
}
])
Playground: https://mongoplayground.net/p/v0dKCwiKsZU
If you want to use pagination you will need to add sort stage to ensure consistent order of the documents in the result.
Firstly I would query the logic of having a different collection for the different 'product_type_x'. If you had a single collection with an added field...
{ "productType" : 1,
...
},
That way that issue has just been resolved, everything to do with Procts is now accessible in a single collection. Aggregation of your data now becomes simple (by comparison)
I am building a project using sequelize.js that includes a Tags table and a Stories table. They have a many to many relationship, which I created in sequelize with a through table of StoryTag. This all works perfectly so far, but I want to get a list of most popluar tags, as in how many stories they are associated with in the StoryTag table, and order them by the number of stories that use this tag.
This is the MySQL syntax of what I am trying to do. This works perfectly in MySQL Workbench:
SELECT tagName, COUNT(StoryTag.TagId)
FROM Tags
LEFT JOIN StoryTag on Tags.id = StoryTag.TagId
GROUP BY Tags.tagName ORDER BY COUNT(StoryTag.TagId) DESC;
This is what works in sequelize.js. It's a raw query, which is not ideal, but since this doesn't handle any sensitive information, it's not a huge worry, just very inelegant.
//DIRECT QUERY METHOD (TEST)
app.get("/api/directags", function (req, res) {
db.sequelize.query("select tags.id, tags.TagName, COUNT(stories.id) as num_stories
from tags left join storytag on storytag.TagId = tags.id
left join stories on storytag.StoryId = stories.id
group by tags.id order by num_stories desc;", {
type: db.Sequelize.QueryTypes.SELECT
}).then(function(result) {
res.send(result);
});
});
This outputs
[
{
"id": 3,
"TagName": "fiction",
"num_stories": 3
},
{
"id": 5,
"TagName": "Nursery Rhyme",
"num_stories": 2
},
...
{
"id": 4,
"TagName": "nonfiction",
"num_stories": 0
}
]
As it should. What doesn't quite work is:
//Sequelize count tags
//Known issues: will not order by the count
//Includes a random 'storytag' many-to-many table row for some reason
app.get("/api/sequelizetags", function (req, res) {
db.Tag.findAll({
attributes: ["id","TagName"],
include: [{
model: db.Story,
attributes: [[db.sequelize.fn("COUNT", "stories.id"), "Count_Of_Stories"]],
duplicating: false
}],
group: ["id"]
}).then(function (dbExamples) {
res.send(dbExamples);
});
});
Which outputs:
[
{
"id": 1,
"TagName": "horror",
"Stories": [
{
"Count_Of_Stories": 1,
"StoryTag": {
"createdAt": "2018-11-29T21:09:46.000Z",
"updatedAt": "2018-11-29T21:09:46.000Z",
"StoryId": 1,
"TagId": 1
}
}
]
},
{
"id": 2,
"TagName": "comedy",
"Stories": []
},
{
"id": 3,
"TagName": "fiction",
"Stories": [
{
"Count_Of_Stories": 3,
"StoryTag": {
"createdAt": "2018-11-29T21:10:04.000Z",
"updatedAt": "2018-11-29T21:10:04.000Z",
"StoryId": 1,
"TagId": 3
}
}
]
},
{
"id": 4,
"TagName": "nonfiction",
"Stories": []
},
...
{
"id": 8,
"TagName": "Drama",
"Stories": [
{
"Count_Of_Stories": 1,
"StoryTag": {
"createdAt": "2018-11-30T01:13:56.000Z",
"updatedAt": "2018-11-30T01:13:56.000Z",
"StoryId": 3,
"TagId": 8
}
}
]
},
{
"id": 9,
"TagName": "Tragedy",
"Stories": []
}
]
This is not in order, and the count of stories is buried. This seems like the sort of thing that would be a common and frequent request from a database, but I am at a loss of how to do this correctly with sequelize.js.
Resources that have failed me:
Sequelize where on many-to-many join
Sequelize Many to Many Query Issue
How to query many-to-many relationship data in Sequelize
Select from many-to-many relationship sequelize
The official documentation for sequelize: http://docs.sequelizejs.com/manual/tutorial/
Some less official and more readable documentation for sequelize: https://sequelize.readthedocs.io/en/v3/docs/querying/
Here's what finally worked, in case anyone else has this question. We also added a where to the include Story, but that's optional.
This resource is easier to understand than the official sequelize docs: https://sequelize-guides.netlify.com/querying/
I also learned that being familiar with promises is really helpful when working with sequelize.
db.Tag.findAll({
group: ["Tag.id"],
includeIgnoreAttributes:false,
include: [{
model: db.Story,
where: {
isPublic: true
}
}],
attributes: [
"id",
"TagName",
[db.sequelize.fn("COUNT", db.sequelize.col("stories.id")), "num_stories"],
],
order: [[db.sequelize.fn("COUNT", db.sequelize.col("stories.id")), "DESC"]]
}).then(function(result){
return result;
});
Please, use the same name if you mean the same thing (num_stories - Count_Of_Stories, etc.).
For ordering use order option.
Include count in top level attributes for get it on top level of instance.
I can't find include[].duplicating option in doc.
Your case:
db.Tag.findAll({
attributes: [
"id",
"TagName",
[db.sequelize.fn("COUNT", "stories.id"), "Count_Of_Stories"]
],
include: [{
model: db.Story,
attributes: [],
duplicating: false
}],
group: ["id"],
order: [
[db.sequelize.literal("`Count_Of_Stories`"), "DESC"]
]
});
Use through: {attributes: []} in options
Does anyone know if it's possible to populate a list of IDs for another model using waterline associations? I was trying to get the many-to-many association working but I don't think it applies here since one side of the relationship doesn't know about the other. Meaning, a user can be a part of many groups but groups don't know which users belong to them. For example, I'm currently working with a model with data in mongodb that looks like:
// Group
{
_id: group01,
var: 'somedata',
},
{
_id: group02,
var: 'somedata',
},
{
_id: group03,
var: 'somedata',
}
// User
{
_id: 1234,
name: 'Jim',
groups: ['group01', 'group03']
}
And I'm trying to figure out if it's possible to setup the models with an association in such a way that the following is returned when querying the user:
// Req: /api/users/1234
// Desired result
{
id: 1234,
name: 'Jim',
groups: [
{
_id: group01,
var: 'somedata',
},
{
_id: group03,
var: 'somedata',
}
]
}
Yes, associations are supported in sails 0.10.x onwards. Here is how you can setup the models
Here is how your user model will look like:
// User.js
module.exports = {
tableName: "users",
attributes: {
name: {
type: "string",
required: true
},
groups: {
collection: "group",
via: "id"
}
}
};
Here is how your group model will look like:
// Group.js
module.exports = {
tableName: "groups",
attributes: {
name: {
type: "string",
required: "true"
}
}
};
Setting up models like this will create three tables in your DB:
users,
groups and
group_id__user_group
The last table is created by waterline to save the associations. Now go on and create groups. Once groups are created, go ahead and create user.
Here is a sample POST request for creation a new user
{
"name": "user1",
"groups": ["547d84f691bff6663ad08147", "547d850c91bff6663ad08148"]
}
This will insert data into the group_id__user_group in the following manner
{
"_id" : ObjectId("547d854591bff6663ad0814a"),
"group_id" : ObjectId("547d84f691bff6663ad08147"),
"user_groups" : ObjectId("547d854591bff6663ad08149")
}
/* 1 */
{
"_id" : ObjectId("547d854591bff6663ad0814b"),
"group_id" : ObjectId("547d850c91bff6663ad08148"),
"user_groups" : ObjectId("547d854591bff6663ad08149")
}
The column user_groups is the user id. And group_id is the group id. Now if you fetch the user using GET request, your response will look like this:
{
"groups": [
{
"name": "group1",
"createdAt": "2014-12-02T09:23:02.510Z",
"updatedAt": "2014-12-02T09:23:02.510Z",
"id": "547d84f691bff6663ad08147"
},
{
"name": "group2",
"createdAt": "2014-12-02T09:23:24.851Z",
"updatedAt": "2014-12-02T09:23:24.851Z",
"id": "547d850c91bff6663ad08148"
}
],
"name": "user1",
"createdAt": "2014-12-02T09:24:21.182Z",
"updatedAt": "2014-12-02T09:24:21.188Z",
"id": "547d854591bff6663ad08149"
}
Please note that groups are not embedded in the user collection. Waterline does the fetch from groups, users and group_id__user_group to show this result to you.
Also, if you want to do this in your controller, you will need to execute like this
User.findOne({'id': "547d854591bff6663ad08149"})
.populate('groups')
.exec(function (err, user){
// handle error and results in this callback
});
Without populate('groups'), you won't get the groups array. Hope this serves your purpose