How to count association size with waterline/sails? - many-to-many

Using sails 0.10.5/waterline 0.10.15:
I cannot find an answer to a simple question: how to count the elements of an association without using populate() (which would load all data).
Let take a simple many2many relation with via:
User:
attributes: {
following: {
collection: 'user',
via: 'follower',
dominant: true
},
follower: {
collection: 'user',
via: 'following'
}
Now I need the size of the collections.
Currently I try
User.findById(1).populateAll().exec(function(err, user) {
// count of followings -> user.following.length;
// count of followers-> user.follower.length;
}
which leads to loading the collections.
I'm missing a count function at collection level to avoid population/loading of data.
Is there a possibility to access the (auto generated) join tables to run a count-query directly on the join?
Something like:
User.findById(1).count({'followings'}).exec(function(err, followings) {
...}
or
UserFollowingFollow_FollowFollowing.countByUserFollowingFollowId(1).
exec(function(err, followings) {
...}

Waterline does offer the count query method and it can be used like this to solve your problem:
User.count().where({follower: followerId})
.exec(function(err, numberOfFollowings) {
//numberOfFollowings will be the integer that you need
})
followerId is the id that you are passing to User.findOne() in your example.
You can also read the Waterline documentation about this.

Related

Do I need to use Prisma's connect & disconnect API? Or is it not safe to just update my Relations via IDs as strings?

I am using prisma + mysql (on planetscale). When I link two items that are in different tables, I normally use connect or disconnect:
const getUser = await prisma.user.update({
where: {
id: 9
},
data: {
posts: {
| connect: {
| id: 11
| },
create: {
title: "My new post title"
}
}
}
})
I am wondering whether that's necessary or why that's necessary?
I also noticed that I can just update records in my database by updating the id (as a plain string), and it will still work. e.g.:
// example for updating a one-to-many relationship:
const getUser = await prisma.user.update({
where: {
id: 9
},
data: {
postId: "123192312i39123123"
}
}
})
... or if it's an explicit many-to-many relation, I can just edit the row in the relation-table & update the id.
Is this a bad way of doing things? Am I going to break something later down the line in doing it this way?
Your cloud provider is not relevant in the context of the question. It will not affect how your framework(prisma) behaves in updates.
I am wondering whether that's necessary or why that's necessary?
You have a user with a one to many relation: user => n posts.
You have an existing post in the db, and you want to add that post to the posts collection of a user.
That posts relation can be either explicit or implicit. The connect clause handles the addition of relation:
{
posts: {
connect: { id: 11 }
}
}
Without using the connect you'd have to create a new post:
{
posts: {
create: {
title: "My new post title"
}
}
}
update records in my database by updating the id (as a plain string)
Not sure what you mean here, mind sharing the schema?
or if it's an explicit many-to-many relation, I can just edit the row in the relation-table & update the id
If it's explicit many-to-many then it's OK to manually edit the id fields. As long as the ids are found and the relation makes sense, there's no problem with manual updates.

How to use .findAll and find records from two different tables and a self reference?

I'm working with two tables in particular. Users and Friends. Users has a bunch of information that defines the User whereas Friends has two columns aside from id: user_id and friend_id where both of them are a reference to the User table.
I'm trying to find all of the users friends in as little calls to the db as possible and I currently have 2. One to retrieve the id of a user first from a request, then another to Friends where I compare the IDs from the first call and then a third call that passes the array of friends and find all of them in the Users table. This already feels like overkill and I think that with associations, there has to be a better way.
Modification of the tables unfortunately is not an option.
One thing that I saw from "http://docs.sequelizejs.com/manual/querying.html#relations---associations"
I tried but got an interesting error.. when trying to repurpose the code snippet in the link under Relations/Associations, I get "user is associated to friends multiple times. To identify the correct association, you must use the 'as' keyword to specify the alias of the association you want to include."
const userRecord = await User.findOne({
where: { id }
})
const friendsIDs = await Friends.findAll({
attributes: ["friend_id"],
where: {
user_id: userRecord.id
}
}).then(results => results.map(result => result.friend_id));
const Sequelize = require("sequelize");
const Op = Sequelize.Op;
return await User.findAll({
where: {
id: { [Op.in]: friendsIDs }
},
});
Above for my use case works. I'm just wondering if there are ways to cut down the number of calls to the db.
Turns out Sequelize handles this for you if you have the proper associations in place so yes, it was a one liner user.getFriends() for me.

Is it possible to order intermediate relation table using sequelize?

I have the following scenario, my application has two entities: box and items with N to N relationship. I am using sequelize with MySQL.
I am using pseudocode to represent the tables:
Box {
id: Integer primary key
name: String
}
Item {
id: Integer primary key
name: String
}
I have set up the schemas with relations hasMany in both directions using the following through relation:
Box.hasMany(Item, { through: Box_Item });
Item.hasMany(Box, { through: Box_Item });
Box_Item {
id_box: Integer,
id_item: Integer,
item_order: Integer
}
With primary_key(id_box, id_item).
I tested it and I can call myBox.getItems() on my instance object myBox and easily get all the items it has.
I can make calls as
BoxModel.findOne({
where: { id: 1 },
include: [{ model: ItemModel }]
});
And it automatically understands there is a relation between the models through Box_Item and get everything correctly, except that I'm not getting the results sorted by item_order field. This field is a number from 1 to N that represents the item order inside that box.
I tried
BoxModel.findOne({
where: { id: 1 },
include: [
{
model: ItemModel,
order: 'item_order'
}
]
});
But it seems sequelizejs does not support order inside include yet (checked on their github repo).
I tried to force
BoxModel.findOne({
where: { id: 1 },
order: '`box_model`.`item_order`'
include: [ { model: ItemModel } ]
})
looking through the query sequelize creates but it just put the ORDER BY in two different places (inside INNER JOIN and at the end of the query, don't know why...) and I got an error.
So I searched for this on stackoverflow (1), found a few questions but I don't get a good way for doing that using the ORM.
How could I get the items sorted by item_order field when asking for specific box items?
After a few days trying to get it done I found an answer on stackoverflow that helped me.
After creating the relationships between Box and Item I can easily call on an instance:
myBox.getItems({
order: '`box_model`.`item_order`'
});
And then I get the result I'm expecting. But I had to look through the query sequelize is creating based on the models and get the correct field based on their renaming rules.
If you want you can pass the as parameter and rename your tables.

Sequelize include (how to structure query)?

I have a query I'm trying to perform based on a one to many relationship.
As an example there is a model called Users and one called Projects.
Users hasMany Projects
Projects have many types which are stored in a type (enum) column. There are 4 different types that potentially a user may have that I want to load. The catch is I want to include the most recent project record (createdAt column) for all networks that potentially will be there. I have not found a way to structure the query for it to work as an include. I have however found a way to do a raw query which does what I want.
I am looking for a way without having to do a raw query. By doing the raw query I have to map the returned results to users I've returned from the other method, or I have to do a simple include and then trim off all the results that are not the most recent. The latter is fine, but I see this getting slower as a user will have many projects and it will keep growing steadily.
This allow serialize a json for anywhere action about a model. Read it, very well
sequelize-virtual-fields
// define models
var Person = sequelize.define('Person', { name: Sequelize.STRING });
var Task = sequelize.define('Task', {
name: Sequelize.STRING,
nameWithPerson: {
type: Sequelize.VIRTUAL,
get: function() { return this.name + ' (' + this.Person.name + ')' }
attributes: [ 'name' ],
include: [ { model: Person, attributes: [ 'name' ] } ],
order: [ ['name'], [ Person, 'name' ] ]
}
});
// define associations
Task.belongsTo(Person);
Person.hasMany(Task);
// activate virtual fields functionality
sequelize.initVirtualFields();

Specifying specific fields with Sequelize (NodeJS) instead of *

Alright so I have a project in NodeJS where I'm utilizing Sequelize for a MySQL ORM. The thing works fantastically however I'm trying to figure out if there is a way to specify what fields are being returned on a query basis or if there's even a way just to do a .query() somewhere.
For example in our user database there can be ridiculous amounts of records and columns. In this case I need to return three columns only so it would be faster to get just those columns. However, Sequelize just queries the table for everything "*" to fulfill the full object model as much as possible. This is the functionality I'd like to bypass in this particular area of the application.
You have to specify the attributes as a property in the object that you pass to findAll():
Project.findAll({attributes: ['name', 'age']}).on('success', function (projects) {
console.log(projects);
});
How I found this:
The query is first called here: https://github.com/sdepold/sequelize/blob/master/lib/model-definition.js#L131
Then gets constructed here: https://github.com/sdepold/sequelize/blob/master/lib/connectors/mysql/query-generator.js#L56-59
Try this in new version
template.findAll({
where: {
user_id: req.params.user_id
},
attributes: ['id', 'template_name'],
}).then(function (list) {
res.status(200).json(list);
})
Use the arrays in the attribute key. You can do nested arrays for aliases.
Project.findAll({
attributes: ['id', ['name', 'project_name']],
where: {id: req.params.id}
})
.then(function(projects) {
res.json(projects);
})
Will yield:
SELECT id, name AS project_name FROM projects WHERE id = ...;
All Answers are correct but we can also use include and exclude as well
Model.findAll({
attributes: { include: ['id'] }
});
Model.findAll({
attributes: { exclude: ['createdAt'] }
});
Source