Sequelize Associations Multiple Foreign Keys from Same Table - javascript

Goal
Using Sequelize and Postgres, I am trying to create a Readings table which contains values and their units. The units are foreign keys to another table, Units. I am confused with the associations.
Assumptions
Each Reading must have a sun value/unit and a moon value/unit.
The reading units can be different
Example Database
Readings
id | sun_temp | sun_temp_unit_id | moon_temp | moon_temp_unit_id
1 5 1 2 2
2 10 1 4 2
Units
id | Name
1 brapple
2 schmeckle
Readings Table Definition
const Reading = sequelize.define("Reading", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
sun_temp: DataTypes.INTEGER,
moon_temp: DataTypes.INTEGER
}, {
timestamps: false,
tableName: "readings"
});
Reading.associate = (models) => {
Reading.belongsTo(models.Unit, {foreignKey: {name: 'sun_temp_unit_id'})
Reading.belongsTo(models.Unit, {foreignKey: {name: 'moon_temp_unit_id'})
}
Units Table Definition
const Unit = sequelize.define("Unit", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: DataTypes.STRING
}, {
timestamps: false,
tableName: "units"
});
Unit.associate = (models) => {
??
}
Questions:
What type of an associate is this?
How do I set up the Reading model?
How do I set up the Unit model?
What query is used to get the Reading Model and the Units models? (Here I would like to see/access each Units name for reading value/unit pair)
If this is a poor way to set up the database, what are other suggestions?

Related

How to include a specific association in sequelize findAll?

I've created a database with two tables, Users and Points. A user can have many points and a point stores the ID of the user who sent it and the user who received it. I am trying to query for a table grouped by user showing the sum of the amounts of all their points, which is working querying raw in postgresql but not in sequelize.
Working in postgresql:
Creating the models with sequelize:
User.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
telegram_id: {
type: DataTypes.INTEGER,
allowNull: false,
unique: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
tableName: "users",
sequelize: sequelize, // this bit is important
}
);
Point.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
amount: {
type: DataTypes.INTEGER,
allowNull: false,
},
to_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
from_id: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{
tableName: "points",
sequelize: sequelize, // this bit is important
}
);
User.hasMany(Point, {
sourceKey: "telegram_id",
foreignKey: "to_id",
as: "recievedPoints", // this determines the name in `associations`!
});
User.hasMany(Point, {
sourceKey: "telegram_id",
foreignKey: "from_id",
as: "sentPoints", // this determines the name in `associations`!
});
Point.belongsTo(User, {
foreignKey: "to_id",
targetKey: "telegram_id",
as: "toUser",
});
Point.belongsTo(User, {
foreignKey: "from_id",
targetKey: "telegram_id",
as: "fromUser",
});
Attempting to make same query with sequelize:
const points = await Point.findAll({
attributes: [
"users.name",
"points.to_id",
[Sequelize.fn("SUM", Sequelize.col("points.amount")), "points.amount"],
],
include: ["toUser"],
group: ["users.name", "points.to_id"],
});
Resulting error:
SequelizeDatabaseError: invalid reference to FROM-clause entry for table "users"
SQL generated by sequelize:
SELECT "users"."name", "points"."to_id", SUM("points"."amount") AS "points.amount", "toUser"."id" AS "toUser.id", "toUser"."telegram_id" AS "toUser.telegram_id", "toUser"."name" AS "toUser.name", "toUser"."createdAt" AS "toUser.createdAt", "toUser"."updatedAt" AS "toUser.updatedAt" FROM "points" AS "Point"
LEFT OUTER JOIN "users" AS "toUser" ON "Point"."to_id" = "toUser"."telegram_id" GROUP BY "users"."name", "points"."to_id";
RAW QUERY :
SELECT "users"."name", "points"."to_id", SUM("points"."amount") AS "points.amount", "toUser"."id" AS "toUser.id", "toUser"."telegram_id" AS "toUser.telegram_id", "toUser"."name" AS "toUser.name", "toUser"."createdAt" AS "toUser.createdAt", "toUser"."updatedAt" AS "toUser.updatedAt"
FROM "points" AS "Point"
LEFT OUTER JOIN "users" AS "toUser" ON "Point"."to_id" = "toUser"."telegram_id" GROUP BY "users"."name", "points"."to_id";
As per your raw query :
Change "users" to "toUser" every where
Change "points" to "Point" every where , like this :
const points = await Point.findAll({
attributes: [
"toUser.name",
"Point.to_id",
[Sequelize.fn("SUM", Sequelize.col("Point.amount")), "Point.amount"],
],
include: ["toUser"],
group: ["toUser.name", "Point.to_id"],
});

Mongoose/Mongodb basic trello like scheme problem with rendering in vue

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.

Sequelize Many to Many Query Issue

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,
});

How to insert all values from another table using sequelize

//table main_lookup
const main_lookup=DB.connection.define('main_lookup',{
main_lookup_name: {
type :Sequelize.STRING,
primaryKey: true,
allowNull: false
},
value:{
type:Sequelize.JSON,
allowNull:false,
}
});
//table 2
const school_lookup= DB.connection.define('school_lookup',{
school_id : {
type :Sequelize.STRING,
allowNull: false,
references: {
model: schools,
key: 'school_id'
}
},
lookup_name: {
type :Sequelize.STRING,
unique: true,
allowNull: false
},
value: {
type: Sequelize.JSON,
allowNull: false,
}
});
i need to send data from main_lookup table to school lookup data
school_id // that is given by me
lookup_name // that is copy from main_lookup
value // that is copy from main_look_up
example
main lookup // table1
main_lookup_name value
---------------- -----
language ['english','tamil']
subject ['social','maths']
the solution is similar to the content following this
school_lookup // table2 //needed
school_id lookup value
--------- ------ -----
cit language ['english','tamil']
cit subject ['social','maths']
i need help in sequelize with simple ways
You can use raw query to do the same.
var Sequelize = require('sequelize');
var sequelize = new Sequelize('database', 'username', 'password');
sequelize.query("insert into <table1> select * from <table2>", {
type:Sequelize.QueryTypes.SELECT
}).then(function(results) {
console.log(results) // or do whatever you want
})
I think you are using sequelize like how people use microsoft excel or microsoft access. You can simply make relation in sequelize.
here is the code
//school-model
const School=Sequelize.define('school',{
name: {
type :Sequelize.STRING,
allowNull: false
}
});
//subject
const Subject= Sequelize.define('subject',{
type: {
type :Sequelize.STRING,
unique: true,
allowNull: false
},
value: {
type: Sequelize.ARRAY,
allowNull: false,
}
});
//database
const School = require('./path_to_schoolmodel');
const Subject = require('./path_to_subjectmodel');
Subject.belongsTo(School);
School.hasMany(Subject);
School model
id name
-- ---------------------
1 'fullstack academy'
2 'app academy'
Subject model
id type value schoolID
-- --------- -------------------- ------------------
1 language ['english','tamil'] 2
2 subject ['social','maths'] 1
if you setup model relationship you should be able to see you data like this.
Then when you use Sequelize model querying on your server side.
const School = require('./path_to_schoolmodel');
const Subject = require('./path_to_subjectmodel');
School.findAll({
include:[ Subject ]
})
.then(console.log)
.catch(console.error)
your console.log should return something like
[
{
id: 1,
name: 'fullstack academy'
subjects: [{
id: 2,
type: 'subject',
value: [
'socials', 'maths'
]
}]
},
{
id: 2,
name: 'app academy'
subjects: [{
id: 1,
type: 'language',
value: [
'english', 'tamil'
]
}]
},
]

Query junction table without getting both associations in Sequelize

Consider the following models:
var User = sequelize.define('User', {
_id:{
type: Datatypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: Datatypes.STRING,
email:{
type: Datatypes.STRING,
unique: {
msg: 'Email Taken'
},
validate: {
isEmail: true
}
}
});
var Location= sequelize.define('Location', {
_id:{
type: Datatypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: Datatypes.STRING,
address: type: Datatypes.STRING
});
Location.belongsToMany(User, {through: 'UserLocation'});
User.belongsToMany(Location, {through: 'UserLocation'});
Is there a way to query the UserLocation table for a specific UserId and get the corresponding Locations. Something like:
SELECT * FROM Locations AS l INNER JOIN UserLocation AS ul ON ul.LocationId = l._id WHERE ul.UserId = 8
From what I can find you can do something similar to:
Location.findAll({
include: [{
model: User,
where: {
_id: req.user._id
}
}]
}).then( loc => {
console.log(loc);
});
However, this returns the Locations, UserLocation junctions, and User which it is joining the User table when I do not need any user information and I just need the Locations for that user. What I have done is working, however, the query against the junction table is prefered instead of the lookup on the User table.
I hope this is clear. Thanks in advance.
Edit
I actually ended up implementing this in a different way. However, I am still going to leave this as a question because this should be possible.
declaring junction table as separate class, something like this
var UserLocation = sequelize.define('UserLocation', {
//you can define additional junction props here
});
User.belongsToMany(Location, {through: 'UserLocation', foreignKey: 'user_id'});
Location.belongsToMany(User, {through: 'UserLocation', foreignKey: 'location_id'});
then you can query junction table same as any other model.

Categories