I need to sort data from a MySQL database on related table row.
Suppose we have two models:
ModelOne:
module.exports = {
tableName: 'modelOne',
attributes: {
// some atributes.........
modelTwo{
model: 'ModelTwo',
columnName: 'model_two_id'
}
}
}
ModelTwo:
module.exports = {
tableName: 'modelTwo',
attributes: {
// some atributes.........
model_two_id: {
type: 'integer',
autoIncrement: true,
primaryKey: true
},
name: 'string'
}
}
I would like to do something like:
ModelOne
.find(find_criteria)
.populateAll()
.paginate({page: page, limit: limit})
.sort('modelTwo.name')
.then(...)
Is there possibility to do this or I need to write an SQL query without using Waterline functions?
This is how I do it...
ModelOne
.find(find_criteria)
.populate('modelTwo', {sort: 'name ASC'})
.paginate({page: page, limit: limit})
.then(...)
As you can see, you can pass {sort} object to populate method.
No. Deep-population-sorting is on future list https://github.com/balderdashy/waterline/issues/266
Related
I wrote a custom data type by extending Abstract, like in the example listed for the following link: https://sequelize.org/v5/manual/data-types.html. Everything appears to work until I try to save foreign keys, in this case it continues to generate a new UUID instead of getting the foreign key passed in the object to the create method.
How should I accommodate foreign keys in this case? Which method call should I overload to handle a getter situation where I need to pass in values?
The code below outlines the extended class I am using
class BinToUUID extends ABSTRACT {
toString(options) {
return this.toSql(options);
}
toSql() {
return 'BINARY(16)';
}
validate(value) {
console.log(value);
}
_stringify(value, options) {
return `UUID_TO_BIN(${options.escape(value)})`;
}
_bindParam(value, options) {
return `UUID_TO_BIN('${options.escape(value)}')`;
}
parse(value) {
return uuidv1.parse(value);
}
_sanitize(value) {
if (value instanceof Buffer) {
const str = value.toString('hex');
return [
str.slice(0, 8),
str.slice(8, 12),
str.slice(12, 16),
str.slice(16, 20),
str.slice(20, 32),
].join('-');
} else {
const uuid = uuidv1();
return uuid;
}
return value;
}
}
Here is a sample of the model I am using it in:
sequelize.define("room", {
roomId: {
primaryKey: true,
allowNull: false,
type: Sequelize.BinToUUID,
defaultValue: Sequelize.BinToUUID,
validate: {
notNull: true
}
},
name: {
type: DataTypes.STRING(10)
},
userId: {
type: Sequelize.BinToUUID,
references: {
model: 'user',
key: 'userId',
}
},
groupId: {
type: Sequelize.BinToUUID,
references: {
model: 'group',
key: 'groupId',
}
},
createdAt: {
type: DataTypes.DATE
},
updatedAt: {
type: DataTypes.DATE
}
}, {
freezeTableName: true
});
Here are the associations for the tables:
db.Room.User = db.Room.hasOne(db.User, {
foreignKey: ‘roomId'
});
db.Room.Group = db.Room.hasOne(db.Group, {
foreignKey: ‘groupId'
});
This is the create method begin called to save the room:
Room.create(room, {
include: [{
association: Room.User
}, {
association: Room.Group
}]
});
I am just trying to save the foreign keys to this table. When I look into the database tables for User and Group, the values do not match the uuid primary key values. It seems like the BinToUUID datatype is overwriting the UUID that is getting passed to create method.
For one-to-one (primary/foreign key relationship), check out belongsTo / HasOne associations
belongsTo
https://sequelize.org/v5/class/lib/model.js~Model.html#static-method-belongsTo
which has the following example:
Profile.belongsTo(User) // This will add userId to the profile table
HasOne
https://sequelize.org/v5/class/lib/associations/has-one.js~HasOne.html:
This is almost the same as belongsTo with one exception - The foreign key will be defined on the target model.
See this tutorial for examples: https://sequelize.org/v4/manual/tutorial/associations.html
This (unofficial?) article shows how foreign key is being handled: https://medium.com/#edtimmer/sequelize-associations-basics-bde90c0deeaa
I have 3 tables: Books Authors and AuthorBooks. The last one to solve many-to-many. So far i managed to get the tables to work and display as i expected to. Now i want to display a table that shows data from both tables: Books and Authors. BooksAuthors stores only the IDs of the other tables.
I want something like this to work with sequelize:
SELECT ab.idA, ab.idB, a.firstName, a.lastName, b.title, b.description
from authorbooks ab
left join authors a on ab.idA=a.idA
left join books b on ab.idB=b.idB;
This is how i tried to define the relationship. I think I'm missing something here.
db.models.Books = require("../models/books")(sequelize);
db.models.Authors = require("../models/authors")(sequelize);
db.models.AuthorBooks = require("../models/authorbooks")(sequelize);
db.models.Authors.belongsToMany(db.models.Books,{
through: db.models.AuthorBooks,
foreignKey:{ name: 'idA',
allowNull:true
}
})
db.models.Books.belongsToMany(db.models.Authors,{
through: db.models.AuthorBooks,
foreignKey:{ name: 'idB',
allowNull: true
}
});
module.exports = db;
This is how i defined the tables. BOOKS:
module.exports = (sequelize) => {
class Books extends Sequelize.Model {}
Books = sequelize.define('Books', {
idB:{
type: Sequelize.INTEGER,
allowNull: false,
primaryKey : true
},
title: {
type: Sequelize.STRING
},
description:{
type: Sequelize.STRING(6000)
},
});
return Books;
};
AUTHORS:
module.exports = (sequelize) => {
class Authors extends Sequelize.Model {}
Authors = sequelize.define('Authors', {
idA:{
type: Sequelize.INTEGER,
allowNull: false,
primaryKey : true
},
firstName: {
type: Sequelize.STRING
},
lastName:{
type: Sequelize.STRING
},
});
return Authors;
};
AUTHORBOOKS:
module.exports = (sequelize) => {
class AuthorBooks extends Sequelize.Model {}
AuthorBooks.init({
id: {
type: Sequelize.INTEGER,
primaryKey:true,
autoIncrement: true
}
},{sequelize});
return AuthorBooks;
}
I think you can use the "include" option to achieve this function.
For example, you can use the code like
Book.findAll({
include:[{model:Author}]
})
This option will add an "Author" field including the author information to the result.
What you need is a through options in the query, it combines all the authors linked to a particular book, empty attributes will avoid adding any attribute from mapping table which in your case is 'AuthorBooks'.
Book.findAll({
include:[{
model:Author,
through: {
attributes: []
}
}]
})
I have 4 tables, user, profile , speciality , user_specialities. I am using sequelize. My user model is-->
const User = sequelize.define('user', {
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
IsDeleted: {
type:Sequelize.BOOLEAN,
},
status: {
type:Sequelize.BOOLEAN,
},
});
My profiles model-->
const Profile= sequelize.define('profile', {
profile_id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
first_name: {
type:Sequelize.STRING,
},
last_name: {
type:Sequelize.STRING,
},
},
{
indexes: [
{
unique: true,
fields: ['user_id']
}
]
});
Chef_Info.belongsTo(User, {foreignKey: 'user_id'});
Speciality table-->
const Profile= sequelize.define('speciality', {
speciality_id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
title: {
type:Sequelize.STRING,
},
}
user_specialities hold the many to many relation between user and speciality. I have used belongsToMany association like this -
Speciality.belongsToMany(User, {through: 'user_speciality',foreignKey: 'speciality_id', otherKey: 'user_id'});
User.belongsToMany(Speciality, {through: 'user_speciality',foreignKey: 'user_id', otherKey: 'speciality_id'});
Now, I want to get data where I have user_id, first_name, last_name also the specialities. For example-->
first_name | last_name | specialities
Khabib | Nurmagamedov | Athlete, Wrestler
Tony | Ferguson | Athlete, Striker
I tried in MySql, where query should be-->
SELECT profiles.first_name,profiles.last_name,GROUP_CONCAT(specialities.title)
FROM (
users
LEFT JOIN profiles
ON users.user_id = profiles.user_id
LEFT JOIN user_specialities
ON users.user_id = user_specialities.user_id
LEFT JOIN specialities
ON chef_specialities.speciality_id = specialities.speciality_id
)
WHERE(
AND users.IsDeleted = false
And users.status = true
)
GROUP BY users.user_id;
But I am struggling to convert this to sequelize. I have come this far-->
UserSpecialities.findAll({
attributes:[sequelize.fn('GROUP_CONCAT', sequelize.col('title')), 'speciality_id'],
include: [
{
model: User,
attributes: [],
where:{
IsRemoved: false,
status: true,
},
},
{ model: Speciality,
attributes: [],
},
],
group:['user_id']
Its provides the specialities userwise-->
[
{
"speciality_id": "Athlete, Wrestler",
},
{
"speciality_id": "Athlete, Striker",
}
]
But I am failing to populate the profile data here.If I try to include profile, it shows no association error, or I try to add a hasManyin user model it throws error again. What should I do in this situation?
Thanks In Advance.
Because Profile is associated with Users but not UserSpecialties it would make more sense to accomplish this with two queries.
Grab your user specialties like you did above, then take those userId's and grab their associated profiles (should be something like User.getProfile depending on how your associations are set up).
Right now you have your UserSpecialties attributes set up to only return the specialty_id and title, if you add in userId to those attributes then you can use those userId's to query your profile table. A then promise works well for something like this so something like:
UserSpecialties.findAll({...}).then((userSpecialties) => {
User.getProfile({where: {userId: userSpecialties.userId}, attributes: {'first_name', 'last_name'})
});
Depending on what you are specifically trying to do with the information you will probably have to tweak it a bit, but the general idea of adding in userId as an attribute and then querying your User table for profiles using the userId from the UserSpecialties query result should work fine for the data you want.
Though looking at your models, it might make sense to just have profile and user be one table, where User has first_name, last_name, userId, and status. Since Profile doesn't have a lot of other stuff to it, and then if you need specific things for the userProfile you can use scopes on your user model.
I am creating an application, and I would like to have more information
about creating a join table.
I have a table media:
var Media = sequelize.define('Media', {
m_title: DataTypes.STRING(50),
m_duration: DataTypes.INTEGER,
m_year: DataTypes.DATEONLY,
m_country: DataTypes.STRING(50),
m_season: DataTypes.INTEGER,
m_order: DataTypes.INTEGER,
m_fk_id_type_media: DataTypes.INTEGER
},
And Celebrity:
var Celebrity = sequelize.define('Celebrity', {
c_lastname: DataTypes.STRING(50),
c_firstname: DataTypes.STRING(50),
c_birth_date: DataTypes.DATEONLY,
c_nationality: DataTypes.STRING(50)
},
Do I have to create the model myself (media_has_celebrities)? Because I tried to add
Media.associate = function (models) {
models.Media.belongsToMany(models.Celebrity, {through: 'MediaHasCelebrity'});
};
and
Celebrity.associate = function (models) {
models.Celebrity.belongsToMany(models.Media, {through: 'MediaHasCelebrity'});
};
but it does not create the model of join as indicated in the documentation?
Do you have to define foreign keys before?
The goal would be to do something like Media.addCelebrity ...
Sorry for my english !
Thank you
Every example code of one to many in sails/waterline documentation assumes the primary key is the association between two models (I think).
http://sailsjs.org/documentation/concepts/models-and-orm/associations/one-to-many
However i have models that have a referral column that references some other values similar to
User
{
id (primary): <int>
email: <string>
}
recordings
{
id (primary): <int>
email: <that email from user>
}
atm im trying
userModel.js
{
attributes: {
email: {
type: 'string',
size: 255,
unique: true
},
},
queries: {
columnName: 'email',
model: 'recordings'
}
.....
}
}
recordingsModel.js
{
attributes: {
email: {
type: 'string',
size: 255,
},
},
queries: {
model: 'user'
}
.....
}
}
and in the Controller
sails.models.user
.find()
.populate('queries')
.exec(function (err, results) {
});
But i get the error
: ER_BAD_FIELD_ERROR: Unknown column '__queries.queries' in 'field list'
Does anyone have a good tutorial for one to many relationships in waterline because the documentation on there site is pretty bad so i feel im just not understanding how to design the models.
From what I can gather, what you want is to be able to set up your userModel and recordingsModel models such that, by giving a recording a certain value for email, it will automatically be associated with any users that share that email. This is not something that the Waterline (the Sails ORM) supports.
Instead, your best option is to set the models up as indicated in the documentation:
// userModel.js
{
attributes: {
email: {
type: 'string',
size: 255,
unique: true
},
},
recordings: {
collection: 'recordingsModel',
via: 'user'
}
.....
}
}
// recordingsModel.js
{
attributes: {
email: {
type: 'string',
size: 255,
},
},
user: {
model: 'userModel'
}
}
}
My guess is that you're trying to avoid having to look up a user ID in order to associate a new recording with it. You can do this if you make email the primary key of the userModel; then when you create a new recording, you can set its user to an email address and voila: the two are linked.