So, I have an existing MySQL database that I'm trying to connect to with Sequelize in Node that has a products table, a categories table and a categories_products table. What I want to do is return products, with each product containing all of the categories it belongs to. Here's what I've got:
// Declare Product Model
const Product = sequelize.define('products', {
name: Sequelize.STRING,
description: Sequelize.STRING,
single_price: Sequelize.BOOLEAN,
oz_price: Sequelize.FLOAT,
half_price: Sequelize.FLOAT,
quarter_price: Sequelize.FLOAT,
eigth_price: Sequelize.FLOAT,
gram_price: Sequelize.FLOAT,
unit_price: Sequelize.FLOAT
},
{
underscored: true
});
// Declare Category Model
const Category = sequelize.define('categories', {
name: Sequelize.STRING,
parent_id: Sequelize.INTEGER,
picture_file_name: Sequelize.STRING
},
{
underscored: true
});
// Join Table
const ProductCategory = sequelize.define('categories_products', {
product_id: Sequelize.INTEGER,
category_id: Sequelize.INTEGER,
}, {
timestamps: false,
underscored: true
});
// Do this because there is no id column on ProductCategory table
ProductCategory.removeAttribute('id');
Category.hasMany(Category, { as: 'children', foreignKey: 'parent_id' });
ProductCategory.belongsTo(Product);
ProductCategory.belongsTo(Category);
Product.hasMany(ProductCategory);
Category.hasMany(ProductCategory);
Using this setup, I query as follows:
Product.findAll({
include: [{
model: ProductCategory,
include: [ Category ]
}],
where: { active: true },
limit: 10
}).then(prods => {
res.send(prods);
}).catch(err => {
res.status(500).send(err);
});
I get back my products and each one has an array of categories, BUT each product only shows a max of one category. I have products that should have many categories, but it only shows the first.
Am I missing something? Any help would be greatly appreciated.
I think you should use belongsToMany association here.
You can define association like this
Product.belongsToMany(Category, { through: ProductCategory, foreignKey: 'product_id' });
Category.belongsToMany(Product, { through: ProductCategory, foreignKey: 'category_id' });
and the query can be
Product.findAll({
include: [Category]
}).then((res) => {
console.log(res);
})
Though the questioner might have gotten the solution but I ran into this composite key table problem and this is the solution with code example. Notice the "through" keyword. That is what solves the association where you want to limit your findings to say a category as AbhinavD asked above. Your category id would go in the literal expression. Applies to findAll too.
const products = await Product.findAndCountAll({
include: [Category],
through: { where: { category_id: `${category_id}` } },
attributes: [
'product_id',
'name',
],
limit: limitPage,
offset: offsett,
});
Related
I'm creating a very basic functionality kanban board.
My board has 4 models so far:
User model
var userSchema = new Schema({
name: {
type: String,
required: true
}
})
module.exports = mongoose.model('User', userSchema)
Board model
var boardSchema = new Schema({
title: {
type: String,
required: true
},
lists: [ listSchema ]
members: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
]
});
module.exports = mongoose.model('Board', boardSchema)
List schema
let listSchema = new Schema({
title: {
type: String,
required: true
},
userCreated: {
type: Schema.Types.ObjectId,
required: true,
ref: 'user'
},
boardId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'board'
},
sort: {
type: Number,
decimal: true,
required: true
}
})
module.exports = mongoose.model('List', listSchema)
Card schema
var cardSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String
},
boardId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'board'
},
listId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'list'
},
members: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
],
sort: {
type: Number,
decimal: true,
required: true
}
})
module.exports = mongoose.model('Card', cardSchema)
What am I looking for?
My front-end is made with Vue.js and sortable.js drag and drop lib.
I want to find the best way to render board with lists (columns) and cards in them.
From what I understand, I should get my board first, by the users id in members array.
Then I have my board which has lists embedded already.
On second api request, I get all the cards by boardId.
My question is - how do I correctly put/render all the cards into each owns lists?
So in the end I want to have something like:
{
title: 'My board',
lists: [
{
title: 'My list',
_id: '35jj3j532jj'
cards: [
{
title: 'my card',
listId: '35jj3j532jj'
}
]
},
{
title: 'My list 2',
_id: '4gfdg5454dfg'
cards: [
{
title: 'my card 22',
listId: '4gfdg5454dfg'
},
{
title: 'my card 22',
listId: '4gfdg5454dfg'
}
]
}
]
members: [
'df76g7gh7gf86889gf989fdg'
]
}
What I've tried?
I've came up with only one thing so far, that is:
Two api calls in mounted hook - one to get the board with lists, second to get all cards.
Then I loop trough lists and loop trough cards and push each card into the list by id?
But this way it seems that my lists would need to have an empty array called cards: [] just for the front-end card-to-list sorting by id, seems somehow wrong.
Is this a good way? Or should I redesign my models and schemas and go with some other way? Any help would be appreciated!
The schema you've defined is pretty good, just one modifications though.
No need to have 'lists' in Board model, since it's already available in lists and also if you keep it in boards, then everytime a new list is added, you'll need to edit the board as well.
Here's how the flow would be.
Initially, when a user signs in, you'll need to show them the list of boards. This should be easy since you'll just do a find query with the user_id on the board collection.
Board.find({members: user_id}) // where user_id is the ID of the user
Now when a user clicks on a particular board, you can get the lists with the board_id, similar to the above query.
List.find({boardId: board_id}) // where board_id is the ID of the board
Similarly, you can get cards with the help of list_id and board_id.
Card.find({boardId: board_id, listId: list_id}) // where board_id is the ID of the board and listId is the Id of the list
Now, let's look at cases wherein you might need data from 2 or more collection at the same time. For example, when a user clicks on board, you not only need the lists in the board but also the cards in that board. In that case, you'll need to write an aggregation as such,
Board.aggregate([
// get boards which match a particular user_id
{
$match: {members: user_id}
},
// get lists that match the board_id
{
$lookup:
{
from: 'list',
localField: '_id',
foreignField: 'boardId',
as: 'lists'
}
}
])
This will return the boards, and in each board, there'll be an array of lists associated with that board. If a particular board doesn't have a list, then it'll have an empty array.
Similarly, if you want to add cards to the list and board, the aggregation query will be a bot more complex, as such,
Board.aggregate([
// get boards which match a particular user_id
{
$match: {members: user_id}
},
// get lists that match the board_id
{
$lookup:
{
from: 'list',
localField: '_id',
foreignField: 'boardId',
as: 'lists'
}
},
// get cards that match the board_id
{
$lookup:
{
from: 'card',
localField: '_id',
foreignField: 'boardId',
as: 'cards'
}
}
])
This will add an array of cards as well to the mix. Similarly, you can get cards of the lists as well.
A bit late to the answer but I think it'll help someone nevertheless. The problem you have could be solved using aggregation framework. While the other answer mentions a pretty good way, it still doesn't have the cards data embedded into it.
MongoDB docs show a way for nested aggregation queries. Nested Lookup
A similar approach could be used for your question.
Board.aggregate([
{
$match: { _id: mongoose.Types.ObjectId(boardId) },
},
{
$lookup: {
from: 'lists',
let: { boardId: '$_id' },
pipeline: [
{ $match: { $expr: { $eq: ['$boardId', '$$boardId'] } } },
{
$lookup: {
from: 'cards',
let: { listId: '$_id' },
pipeline: [{ $match: { $expr: { $eq: ['$listId', '$$listId'] } } }],
as: 'cards',
},
},
],
as: 'lists',
},
},
]);
This will include the cards as an array inside of every list.
I have model Arhive
const Archive = sequelize.define('Archive', {
date: DataTypes.STRING,
}, {});
Archive.associate = function(models) {
Archive.hasMany(models.Video, {
foreignKey: 'ArchiveId',
onDelete: 'CASCADE',
as: 'video'
});
Archive.hasMany(models.Photo, {
foreignKey: 'ArchiveId',
onDelete: 'CASCADE'
});
};
return Archive;
};
and model Video
const Video = sequelize.define('Video', {
link: DataTypes.STRING,
pick: DataTypes.STRING,
date: DataTypes.STRING,
}, {});
Video.associate = function(models) {
Video.belongsTo(models.Archive, {
foreignKey: 'ArchiveId',
onDelete: 'CASCADE',
});
// associations can be defined here
};
return Video;
I do a search
models.Archive.findAll({
raw:true,
attributes: ['id'],
include: [{// Notice `include` takes an ARRAY
model: models.Video,
as:'video',
required:true,
attributes: ['id'],
}]
})
.then(archive => console.log(archive))
.catch(console.error)
I get unexpected
[ { id: 1, 'video.id': 1 }, { id: 1, 'video.id': 2 } ]
How to get one parent object with a child nested array ?
An example of the object to get
[ { id: 1, video[id:1,id:2] } ]
Is it possible to get a similar result with Sequelize?
All you need to do is remove raw:true from your query.
models.Archive.findAll({
// raw: true, // <------- REMOVE THIS
attributes: ['id'],
include: [{ // Notice `include` takes an ARRAY
model: models.Video,
as: 'video',
required: true,
attributes: ['id'],
}]
})
.then(archive => console.log(archive))
.catch(console.error)
It generates this kind of output while using it for nested levels with raw:true.
I'm trying to get the value and returns postgres me that this column does not exist. I am not able to deal in any way.
"column Group.participant.id does not exist"
relationship:
User.belongsToMany(models.group, { as: 'Member', through: { model: models.participant, unique: false}, foreignKey: 'userId', constraints: false});
Group.belongsToMany(models.user, { as: 'Group', through: { model: models.participant, unique: false}, foreignKey: 'groupId', constraints: false });
Participant.belongsTo(models.user, {
foreignKey: "userId", as: "Member"
});
Participant.belongsTo(models.group, {
foreignKey: "groupId", as: "Group"
});
Query/Sequelizejs:
const User = app.db_connect.postgres.models.user;
Group.findAll({
include: [
{ model: User,
as: 'Group',
where: {
"participant.id": idUser
}
// attributes: ['name']
}
]})
.then(result => res.json(result))
.catch(error => {
res.status(412).json({msg: error.message});
});
});
Query generate:
Executing (default): SELECT "group"."id", "group"."name", "group"."isMain", "group"."privacy", "group"."description", "group"."img", "group"."createdAt", "group"."updatedAt", "group"."destroyTime", "group"."categoryId", "Group"."id" AS "Group.id", "Group"."name" AS "Group.name", "Group"."birthday" AS "Group.birthday", "Group"."cpf" AS "Group.cpf", "Group"."email" AS "Group.email", "Group"."img" AS "Group.img", "Group"."status" AS "Group.status", "Group"."phone" AS "Group.phone", "Group"."cellPhone" AS "Group.cellPhone", "Group"."nickName" AS "Group.nickName", "Group"."password" AS "Group.password", "Group"."found" AS "Group.found", "Group"."createdAt" AS "Group.createdAt", "Group"."updatedAt" AS "Group.updatedAt", "Group"."destroyTime" AS "Group.destroyTime", "Group.participant"."id" AS "Group.participant.id", "Group.participant"."function" AS "Group.participant.function", "Group.participant"."createdAt" AS "Group.participant.createdAt", "Group.participant"."updatedAt" AS "Group.participant.updatedAt", "Group.participant"."groupId" AS "Group.participant.groupId", "Group.participant"."userId" AS "Group.participant.userId" FROM "groups" AS "group" INNER JOIN ("participants" AS "Group.participant" INNER JOIN "users" AS "Group" ON "Group"."id" = "Group.participant"."userId") ON "group"."id" = "Group.participant"."groupId" AND ("Group"."destroyTime" IS NULL AND "Group"."participant.id" = 1) WHERE "group"."destroyTime" IS NULL;
Returns without the where: Pastebin
Use just id in where, because in include section you work with a columns of specific model
Group.findAll({
include: [{ model: User, as: 'Group',
where: {
"id": idUser
}
}]})
I'm building a shop, where in the database i have orders and items. Here's the code for the models:
Item:
var Sequelize = require('sequelize')
var sequelize = require('./sequelize')
var Item = sequelize.define('item', {
image: {
type: Sequelize.STRING,
allowNull: false
},
itemName: {
type: Sequelize.STRING,
allowNull: false,
field: 'item_name'
},
price: {
type: Sequelize.INTEGER,
allowNull: false
}
})
module.exports = Item
Order:
var Sequelize = require('sequelize')
var sequelize = require('./sequelize')
var Order = sequelize.define('order', {
orderNumber: {
primaryKey: true,
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV4
},
shop: {
type: Sequelize.INTEGER
},
location: {
type: Sequelize.STRING
}
})
module.exports = Order
They are related through belongs to many:
Item.belongsToMany(Order, {through: OrderItem})
Order.belongsToMany(Item, {through: OrderItem})
The OrderItem has an additional field, 'count', which i need to return:
var Sequelize = require('sequelize')
var sequelize = require('./sequelize')
var OrderItem = sequelize.define('OrderItem', {
count: Sequelize.INTEGER
})
module.exports = OrderItem
However, when i try to include the OrderItem model, it doesn't work. No errors, nothing. The query just doesn't return:
Order.findAll({
where: {
userId: userId
},
include: [{
model: Item,
include: [OrderItem]
}]
}).then(orders => {
console.log(orders)
res.status(200).json(orders)
})
How to get what i need from sequeilize?
You may try something like this:
Order.findAll({
where: {
userId: userId
},
include: [{ model: Item,
as:'item',
through:{attributes:['count']} // this may not be needed
}]
}).then(orders => {
console.log(orders)
res.status(200).json(orders)
})
Also, your models must have a right naming strategy. E.g. :
Item - must have itemId field instead of itemNumber,
Order - must have orderId as primary field
OrderItem's structure:
var OrderItem = sequelize.define('OrderItem', {
orderId: Sequelize.INTEGER,
itemId: Sequelize.INTEGER,
count: Sequelize.INTEGER
})
Another one is to use direct names of related fields and models when you use belongsToMany()
More here: http://docs.sequelizejs.com/en/latest/docs/associations/
Turns out that the OrderItem is already nested inside the Item object. However, this doesn't make a nice return format , so the question is still open.
I know this is year later, but yet..
You can use the property joinTableAttributes to get a junction table fields .
for example :
Order.findAll({
where: {
userId: userId
},
joinTableAttributes: ['count'],
include: [{
model: Item,
}]
}).then(orders => {
console.log(orders)
res.status(200).json(orders)
})
I am using Graphql and I had the same problem with the formatting. I did a small map after getting the result.
For example:
let orders = await Order.findAll({
where: {
userId: userId
},
include: [{ model: Item,
as:'item',
through:{attributes:['count']}
}]
});
let ordersWithCount = orders.map((o) => {
let orderWithCount = {};
orderWithCount = o;
orderWithCount.count = o.OrderItem.count;
return orderWithCount;
});
I have a many to many relations that looks like this:
var Genres = db.define('Movie', {
name: {
type: Sequelize.STRING(100),
allowNull: false
},
description: {
type:Sequelize.STRING(),
allowNull: true
},
thumbnail: {
type: Sequelize.BLOB(),
allowNull: true
},
urlposter: {
type: Sequelize.STRING(245),
allowNull: true
}
});
var Users = db.define('User', {
username: {
type: Sequelize.STRING(25),
allowNull: false
},
password: {
type: Sequelize.STRING(25),
allowNull: false
}
});
Movies.belongsToMany(Users, {through: UM, foreignKey: 'Id_Movies'});
Users.belongsToMany(Movies, {through: UM, foreignKey: 'Id_Users'});
what I will do is return all Movies that have no link to a specific user
this is my SQL query i want to achive:
SELECT m.*
FROM Movies m
WHERE NOT EXISTS(SELECT NULL
FROM Users_Movies sc
WHERE sc.Id_Movies = m.id AND sc.Id_Users = 1)
This is the closest I've got but this just return all movies that have a link u user with ID 1
Movies.findAll({
include: [
// {
// model:Genres,
// through:{attributes:[]}
// },
{
model:Users,
through:{attributes:[]},
where: {id: 1},
required: true,
}],
})
.then(function(movies){
done(null, movies);
})
.catch(function(error){
done(error, null);
});
But I want to invert it.
right now my only option is to make 2 queries one with all movies and one with all movies that have a link and loop through the list and remove them from the first list.. this is not pretty code.
I know that I'm late on the matter, but I think that using the literal 'users.id IS NULL' in the query's 'where' will do the trick:
Movie.findAll({
where: sequelize.literal("users.id IS NULL"),
include: [
{
model: Users,
through: { attributes: [] }
}],
})
.then(function (movies) {
done(null, movies);
})
.catch(function (error) {
done(error, null);
});
This solutions is based on the following Github issue: https://github.com/sequelize/sequelize/issues/4099
Use rawQuery
const projects = await sequelize.query('SELECT * FROM projects', {
model: Projects,
mapToModel: true // pass true here if you have any mapped fields
});