Sequelizejs, how sort field values when creating an index - mysql

I have some 'invite' model:
'use strict';
export default function (sequelize, DataTypes) {
return sequelize.define('Invite', {
userFromId: {
type: DataTypes.INTEGER,
modal: 'User',
key: '_id',
onDelete: 'cascade'
},
userToId: {
type: DataTypes.INTEGER,
modal: 'User',
key: '_id',
onDelete: 'cascade'
},
accept1: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
accept2: {
type: DataTypes.BOOLEAN,
defaultValue: false
}
},{
indexes: [
{
name:'id',
unique: true,
fields: ['userFromId','userToId'],
}]
}
);
}
I need that when i to create a new 'Invite', a combination of 'userFromId' and 'userToId' created one index regardless of who gets invited. For example invite {userFromId:1,userToId:2} and {userFromId:2,userToId:1} must have the same index.
I use sequelize: 3.29.0 and mysql.

This is not possible using a database index because [1, 2] != [2, 1]. If you want to enforce this type of uniqueness one option is to create a new field with a unique index and set it's value to be lower + : + higher so that in the example above you always end up with 1:2.
// Create a field called 'uniqueKey' set to the sorted IDs
export default function (sequelize, DataTypes) {
return sequelize.define('Invite', {
// ... other fields
uniqueKey: {
type: DataTypes.String,
allowNull: false,
defaultValue: function() {
// create an array of the IDs, sort, then join with :
return [
this.getDataValue('userFromId'),
this.getDataValue('userToId'),
].sort().join(':');
},
},
// ... other fields
},{
indexes: [
{
name:'uniqueKey',
unique: true,
fields: ['uniqueKey'],
}]
}
);
}
You can see how the defualtValue is calculated here, the key/result is the same regardless of the order of IDs:
[1,2].sort().join(':');
"1:2"
[2,1].sort().join(':');
"1:2"

Related

Nodejs Sequelize

I have these 2 models:
Orders Models
Solutions model
Orders Model
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Orders extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
Orders.hasMany(models.Payments, {
foreignKey: {
name: 'order',
allowNull: false,
},
constraints: false,
onDelete: 'cascade',
});
Orders.hasOne(models.Solutions, {
foreignKey: {
name: 'order',
allowNull: false,
},
constraints: false,
onDelete: 'cascade',
as: "solution"
});
}
}
Orders.init(
{
order_no: {
defaultValue: DataTypes.UUIDV4,
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
unique: true,
},
order_date: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
},
{
sequelize,
modelName: 'Orders',
tableName: 'Orders',
}
);
return Orders;
};
#2. Solutions table
'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Solutions extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
Solutions.belongsTo(models.Orders, {
foreignKey: 'order',
onDelete: 'cascade',
constraints: false,
as: "solution"
});
}
}
Solutions.init(
{
solutionId: {
defaultValue: DataTypes.UUIDV4,
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
unique: true,
},
content: {
type: DataTypes.TEXT,
allowNull: false,
},
additional_instruction: {
type: DataTypes.TEXT,
allowNull: true,
},
date_submited: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
},
{
sequelize,
modelName: 'Solutions',
}
);
return Solutions;
};
I am trying to get all orders where it's solution has not been submitted to the solutions table, i.e order field(Foreign key in solution table) is null.
I have tried this
Orders.findAndCountAll({
include: [
{
model: Users,
attributes: ['username', 'email', 'uid'],
},
{
model: Solutions,
as: "solution",
where: {
solutionId: {
[Op.notIn]: Solutions.findAll({
attributes: ['solutionId']
})
}
}
}
],
offset: page,
limit,
})
I was expecting to get a list of all orders where the solutions in the solution table has not been added. Am a bit new to sequelize.
You can try to filter after left join, Sequelize can apply where clause directly on the join or after join.
Orders.findAndCountAll({
where: {
'$orders.solution$': null,
},
include: [
{
model: Solutions,
as: "solution",
required: false
},
],
})
In SQL it's like :
SELECT COUNT(*)
FROM orders o
LEFT JOIN solutions s ON o.id = s.order AND s.order IS NULL
VS
SELECT COUNT(*)
FROM orders o
LEFT JOIN solutions s ON o.id = s.order
WHERE s IS NULL
You can perform a left join with a filter which excludes records from Solutions table if the order does not exit.
Orders.findAndCountAll({
include: [
{
model: Users,
attributes: ['username', 'email', 'uid'],
},
{
model: Solutions,
as: "solution",
required: false,
},
],
where: {
'$solution.order$': null
},
offset: page,
limit,
})
For those coming later to this question, I have come to the conclusion that a LEFT OUTER JOIN between the two tables performs the exact same thing I was looking for. I want to give credit back to #Shengda Liu and #8bitIcon for the solution given.
In sequelize the solution would involve just adding the required field in the include statement on the target model to enforce the rule(i.e) find all rows that have an associations in the target associated table. For my case, the solution is as follows.
Orders.findAndCountAll({
include: [
{
model: Users,
attributes: ['username', 'email', 'uid'],
},
{
model: Solutions,
as: "solution",
required: true, // this is the only thing I have added
/// and removed the where clause in the include field.
},
],
offset: page,
limit,
})

Sequelize Mysql - Unable to Insert model with one to many relationship

Design Overview: I've an application with Invoice creation and Inventory management features in it. Let's first understand the database design with 2 entities that we have as below:
Invoices
Items
Now, here I've a M:N relationship between these 2 entities because one invoice can contain multiple items and one item can be included in many such invoices.
So, I've created a 3rd table which we call joining table to associate these entities as shown in the image below,
Problem Statemet: I'm unable to insert model in the child table(invoice_items) using include attribute. Look at the code below to understand what's wrong happening here?
3 Model Classes as below:
1. Invoice:
Note: Providing with fewer attributes to keep it short.
module.exports = (sequelize, DataTypes) => {
const Invoice = sequelize.define('Invoice', {
invoiceId: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
invoiceNumber: {
type: DataTypes.INTEGER(6).UNSIGNED.ZEROFILL,
allowNull: false,
unique: true
},
invoiceTotal: {
type: DataTypes.DECIMAL(9,2),
allowNull: false,
defaultValue: 0.00
},
paymentTotal: {
type: DataTypes.DECIMAL(9,2),
allowNull: false,
defaultValue: 0.00
},
invoiceDate: {
type: DataTypes.DATEONLY,
defaultValue: DataTypes.NOW,
allowNull: false
}
}, {
underscored: true
});
Invoice.associate = function (model) {
Invoice.belongsTo(model.Customer, {
as: 'customer',
foreignKey: {
name: "cust_id",
allowNull: false
}
});
// association with 3rd table
Invoice.hasMany(model.InvoiceItem, {
as: 'invoice_item',
constraints: true,
onDelete: 'NO ACTION',
foreignKey: {
name: "invoice_id",
allowNull: false
}
});
};
return Invoice;
}
2. Item:
Note: Providing with fewer attributes to keep it short.
module.exports = (sequelize, DataTypes) => {
const Item = sequelize.define('Item', {
itemId: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
itemName: {
type: DataTypes.TEXT,
allowNull: false,
defaultValue: ''
},
// this is a opening stock
quantityInStock: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
},
unitPrice: {
type: DataTypes.DECIMAL(9,2),
allowNull: false,
defaultValue: 0.00
}
}, {
underscored: true
});
Item.associate = function (model) {
// association with 3rd table
Item.hasMany(model.InvoiceItem, {
as: 'invoice_item', // alias name of a model
constraints: true,
onDelete: 'NO ACTION',
foreignKey: {
name: "item_id",
allowNull: false
}
});
};
return Item;
}
3. Invoice_Item:
Note: Providing with fewer attributes to keep it short.
module.exports = (sequelize, DataTypes) => {
const InvoiceItem = sequelize.define('InvoiceItem', {
invoiceItemId: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
quantity: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 0,
},
rate: {
type: DataTypes.DECIMAL(9,2),
allowNull: false,
defaultValue: 0.00
}
}, {
underscored: true
});
InvoiceItem.associate = function(model) {
InvoiceItem.belongsTo(model.Invoice, {
as: 'invoice',
foreignKey: {
name: "invoice_id",
allowNull: false
}
});
InvoiceItem.belongsTo(model.Item, {
as: 'item',
foreignKey: {
name: "item_id",
allowNull: false
}
});
}
return InvoiceItem;
}
Now, I'm using below code to create an invoice with the list of items in it. But, this is not inserting the child records in the joining table(invoice_items). What's wrong here in the code below?
invoice = await Invoice.create({
"invoiceNumber": req.body.invoiceNumber,
"invoiceDate": req.body.invoiceDate,
"invoiceTotal": req.body.invoiceTotal,
"paymentTotal": req.body.paymentTotal,
"cust_id": req.body.customer.custId,
invoice_items: [{
item_id: 1,
quantity: 2,
rate: 300
}]
}, {
include: [{
association: InvoiceItem,
as: 'invoice_item'
}]
});
After trying so many variations, I understand that there was a problem in the association of my model classes. And, below is the way of associating both Invoice and Item model classes for M:N(Many to Many) relationships. I can now update the join table(invoice_items) by inserting the record in it for each invoice we create in the system with the items in it.
Invoice.associate = function (model) {
// association in Invoice model class
Invoice.belongsToMany(model.Item, {
through: 'InvoiceItem',
constraints: true,
onDelete: 'NO ACTION',
foreignKey: {
name: "invoice_id", // foreign key column name in a table invoice_items table
allowNull: false
}
});
};
Item.associate = function (model) {
// association in Item model class
Item.belongsToMany(model.Invoice, {
through: 'InvoiceItem',
constraints: true,
onDelete: 'NO ACTION',
foreignKey: {
name: "item_id", // foreign key column name in a table invoice_items
allowNull: false
}
});
};
Create Invoice with Items in it:
Note: Passing itemId (1) as a parameter in the addItems() method. If you have multiple items in an invoice then you can add forEach loop here to iterate over each item and individually pass the itemId, quantity and rate for an item sold to the customer.
// first create the invoice
invoice = await Invoice.create(invoice);
// Next, add record in the join table
await invoice.addItems([1], {
through: {
quantity: item.quantity,
rate: item.rate
}
});
Database Tables with one Test Result:
1. Invoice Table:
2. Invoice_items Table(Join Table):

Understanding Associations in Sequelize

I'm trying to understand associations in Sequelize. I'm starting from existing database tables so some of the fields may not match up to the defaults in Sequelize. I've used Sequelizer to generate my models directly from the database.
I'm accustomed to writing queries but now I'm trying to learn how an ORM like Sequelize works.
Here's my models.
models/user.js
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"User",
{
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
field: "id"
},
username: {
type: DataTypes.STRING(20),
allowNull: false,
field: "username"
},
fullname: {
type: DataTypes.STRING(60),
allowNull: false,
field: "fullname"
},
createdat: {
type: DataTypes.DATE,
allowNull: false,
field: "createdat"
},
updateat: {
type: DataTypes.DATE,
allowNull: true,
field: "updateat"
},
deletedat: {
type: DataTypes.DATE,
allowNull: true,
field: "deletedat"
}
},
{
tableName: "users",
timestamps: false
}
);
User.associate = function(models) {
models.User.hasMany(models.Ticket),
{ as: "createdbyname", foreignKey: "createdby" };
};
return User;
};
models/ticket.js
module.exports = (sequelize, DataTypes) => {
const Ticket = sequelize.define(
"Ticket",
{
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
field: "id"
},
details: {
type: DataTypes.STRING(45),
allowNull: true,
field: "details"
},
assignedto: {
type: DataTypes.INTEGER(11),
allowNull: true,
field: "assignedto"
},
createdby: {
type: DataTypes.INTEGER(11),
allowNull: true,
field: "createdby"
},
createdat: {
type: DataTypes.DATE,
allowNull: false,
field: "createdat"
},
updatedat: {
type: DataTypes.DATE,
allowNull: true,
field: "updatedat"
},
deletedat: {
type: DataTypes.DATE,
allowNull: true,
field: "deletedat"
}
},
{
tableName: "tickets",
timestamps: false
}
);
Ticket.associate = function(models) {
models.Ticket.belongsTo(models.User,
{ foreignKey: "createdby" });
};
return Ticket;
};
In my route handler, I'm calling User.findAll as follows:
models.User.findAll({
include: [models.Ticket]
})
The result I expect to see is a query that looks like this:
SELECT
`User`.`id`,
`User`.`username`,
`User`.`fullname`,
`User`.`createdat`,
`User`.`updateat`,
`User`.`deletedat`,
`Tickets`.`id` AS `Tickets.id`,
`Tickets`.`details` AS `Tickets.details`,
`Tickets`.`assignedto` AS `Tickets.assignedto`,
`Tickets`.`createdby` AS `Tickets.createdby`,
`Tickets`.`createdat` AS `Tickets.createdat`,
`Tickets`.`updatedat` AS `Tickets.updatedat`,
`Tickets`.`deletedat` AS `Tickets.deletedat`
FROM
`users` AS `User`
LEFT OUTER JOIN
`tickets` AS `Tickets` ON `User`.`id` = `Tickets`.`createdby`
The query I see running in the console is:
SELECT
`User`.`id`,
`User`.`username`,
`User`.`fullname`,
`User`.`createdat`,
`User`.`updateat`,
`User`.`deletedat`,
`Tickets`.`id` AS `Tickets.id`,
`Tickets`.`details` AS `Tickets.details`,
`Tickets`.`assignedto` AS `Tickets.assignedto`,
`Tickets`.`createdby` AS `Tickets.createdby`,
`Tickets`.`createdat` AS `Tickets.createdat`,
`Tickets`.`updatedat` AS `Tickets.updatedat`,
`Tickets`.`deletedat` AS `Tickets.deletedat`,
`Tickets`.`UserId` AS `Tickets.UserId`
FROM
`users` AS `User`
LEFT OUTER JOIN
`tickets` AS `Tickets` ON `User`.`id` = `Tickets`.`UserId`;
Note difference in LEFT OUTER JOIN clause. This is throwing an error as follows:
Unhandled rejection SequelizeDatabaseError: Unknown column 'Tickets.UserId' in 'field list'
I need some help figuring out where I've gone wrong here.
When defining associations, like belongsTo, you can specify a foreignKey and a targetKey. The foreignKey corresponds to the field in the source table (remember, the syntax is sourceModel.belongsTo(targetModel, options)). The targetKey corresponds to the field in the target table.
In your case, you made a mistake in the association in the models/ticket.js file, you used:
models.Ticket.belongsTo(models.User, { foreignKey: "createdby" });
Here, foreignKey references the source table, Ticket. Therefore, your are telling Sequelize to use the field createdBy for the Ticket table, and the default (the primary key) for the User table. As createdBy does not exists within Ticket, Sequelize falls back to the default case, where it uses Ticket.UserID.
To fix your association (and query), you need to update your belongsTo to the following:
models.Ticket.belongsTo(models.User, { targetKey: "createdby" });
Plant.findAll({
include: Farm,
order: [['name', 'ASC']]
})
.then(data=> {
res.send(data)
})
.catch(err => {
res.send(err)
console.log(err);
})
let arrQuery = [
queryInterface.addColumn('farms', 'dayPlayed' , Sequelize.STRING),
queryInterface.removeColumn('plants', 'dayPlayed' , Sequelize.STRING),
]
return Promise.all(arrQuery)

nodejs sequelize: I can't update the primary key field's value

I can update every field's value except the id (primary key). It's for a website. I want to be able to update the id via the website.
module.exports = {
schema: function (sequelize, DataTypes) {
return sequelize.define(
'T_Event',
{
id: {
type: DataTypes.STRING,
primaryKey: true
},
user_id: {
type: DataTypes.STRING
},
name: {
type: DataTypes.STRING
},
created_at: {
type: DataTypes.DATE
},
description: {
type: DataTypes.STRING
},
image: {
type: DataTypes.STRING
},
closed_at: {
type: DataTypes.DATE
}
},
{
timestamps: true,
paranoid: true,
underscored: true,
freezeTableName: true
}
)
}
I'm using sequelize 3.33.0 with node and a MySQL database.
All attributes have select,insert & update privileges.
Sequelize restricts this kind of operation for security reasons. Usually updating a primary key through runtime with user provided information is a bad idea. It can lead to all sort of issues, especially if the changed object is being used by other users at the moment.
I follow the rule of thumb: if the column value can be changed, it is not a good candidate for a primary key. If there is no good candidate among all columns, create one with this sole purpose.
schema: function (sequelize, DataTypes) {
return sequelize.define(
'T_Event',
{
uid: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true
},
id: {
type: DataTypes.STRING,
unique: true
},
// ...
)
}
That said, there are cases where this kind of solution isn't doable. In that case, you may use a workaround, making an update that changes the id:
T_Event.update(
{
id: new_value
}, {
where: {
uid: current_value
}
}
);

AVOID DUPLICATES in sequelize query

i'm implementing a like system for a project. And I need some help with a query.
Basically i have 2 buttons (upvote and downvote) that call my function and give the id of a thread, the username voting, and the vote ( 1 or -1).
addPositiveorNegativeLikes = function(thread_id, username, vote) {
sequelize.query('INSERT INTO Likes (thread_id, userId, vote, createdAt, updatedAt)
VALUES((?), (SELECT id FROM Users WHERE username=(?)), (?), (?), (?))
ON DUPLICATE KEY UPDATE thread_id=(?), userId=(SELECT id FROM Users WHERE username=(?))',{
replacements: [thread_id, username, vote, new Date(), new Date(), thread_id, username]
})
}
But now in my Likes table althought thread_id and userId ara both primary keys, inserts multiple repeated "Likes".
How I can modify my query so it deletes an existing vote and replaces it for a new one??
Here is my Like model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Like = sequelize.define('Like', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
userId: {
allowNull: false,
primaryKey: true,
type: DataTypes.INTEGER
},
thread_id: {
allowNull: false,
primaryKey: true,
type: DataTypes.INTEGER
},
createdAt: {
allowNull: false,
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
type: DataTypes.DATE
},
vote: {
type: DataTypes.INTEGER
}
}, {});
Like.associate = function(models) {
// associations can be defined here
};
return Like;
};
Here , what you can do is , create a composite key , with this
userId: {
allowNull: false,
unique:"vote_user" // <------ HERE
type: DataTypes.INTEGER
},
thread_id: {
allowNull: false,
unique:"vote_user" // <------ HERE
type: DataTypes.INTEGER
},
NOTE :
// Creating two objects with the same value will throw an error. The unique property can be either a
// boolean, or a string. If you provide the same string for multiple columns, they will form a
// composite unique key.
uniqueOne: { type: Sequelize.STRING, unique: 'compositeIndex' },
uniqueTwo: { type: Sequelize.INTEGER, unique: 'compositeIndex' },
And then create it like :
Like.create({ userId : 1 , thread_id : 1 }).then(data => {
// success
}).catch(err => {
// error if same data exists
})
// <--- this will check that if there any entry with userId 1 and thread_id 1 ,
// if yes , then this will throw error
// if no then will create an entry for that
Note :
Never never run raw query , like you have did in your code sample , always use the model to perform CRUD , this way you can utilize all
the features of sequelizejs