AVOID DUPLICATES in sequelize query - mysql

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

Related

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):

Sequelize relations foreign key constraint

I have a post route that is using the sequelize create method to add an event to the database. I have defined the tables for user and event as such:
Events
const Events = sequelize.define('Events', {
Event_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
month: {
type: DataTypes.STRING,
allowNull: false
},
day:{
type:DataTypes.INTEGER,
allowNull: false
},
year: {
type: DataTypes.INTEGER,
allowNull: false
},
important:{
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
description:{
type: DataTypes.STRING,
allowNull: true
}
});
return Events;
}
User
const User = sequelize.define('Users', {
user_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false
},
password: {
type: DataTypes.STRING,
allowNull: false
}
}, {});
User.associate = function(models){
User.hasMany(models.Events, {as: 'user'})
}
return User;
}
The post route seems to run correctly with the exception on sequelize not being able to attach the user Id to the event that is created do to
"Cannot add or update a child row: a foreign key constraint fails (planitdb.events, CONSTRAINT events_ibfk_1 FOREIGN KEY (UserUserId) REFERENCES users (user_id) ON DELETE SET NULL ON UPDATE CASCADE)"
I am not sure I understand exactly why this is happening. I am thinking I need to define the relation in the Events table as well? If that is the case, I am not sure I understand what type of relationship the Events table has with the User table. Or is the relationship a single event to a single user?
Any help would be greatly appreciated.
Your events object does not contain a mapping for user_id. This object needs to include a user_id field and this needs to be populated prior to attempting to insert a record to the events table.
EDIT - including sequelize relations solution:
Instead of this:
const Events = sequelize.define('Events', {...});
User.associate = function(models){
User.hasMany(models.Events, {as: 'user'})
}
Try:
const Events = sequelize.define('Events', {...});
const User = sequelize.define('User', {...});
User.hasMany(Events, {as: 'user'});
or:
const Events = sequelize.define('Events', {});
const User = sequelize.define('User', {...});
Events.belongsTo(User,{
foreignKey: 'user_id',
constraints: false,
as: 'user'
});

TypeError: s.replace is not a function when removeConstraint

I'm building an app with sequelize 5.5.0. I created some models; like courses and coursecategory are some tables in DB. I need add the foreing key courseCategory to course table...
(one category has many courses)
But when i create the migration and add Constraint with addConstraint() all's right but when revert this show the error
I understand that if i create the constraint in the beggin i could resolve that but in my case i'll migrate one proyect and there are a lot records in existent Database. So for example in future case if i wish to create a constraint and i create my migration i cannot revert that
Afther that i have the course model like..
const Course = sequelize.define('Course', {
//attributes
courseId:{
type: DataTypes.INTEGER.UNSIGNED,
allowNull : false,
autoIncrement: true,
primaryKey: true,
},
category:{
type: DataTypes.INTEGER.UNSIGNED,
allowNull:false,
defaultValue: 1
}
});
my coursecategory model
const Coursecategory = sequelize.define('Coursecategory', {
//atributes
coursecategoryId:{
type: DataTypes.INTEGER.UNSIGNED,
allowNull : false,
autoIncrement: true,
primaryKey: true,
},
name:{
type: DataTypes.STRING,
allowNull:true
},
order:{
type: DataTypes.SMALLINT.UNSIGNED,
allowNull:false,
defaultValue: 0
}
}, {});
and my constraint migration it's here...
up: (queryInterface, Sequelize) => {
return await queryInterface.addConstraint('courses', ['category'], {
type: 'foreign key',
name: 'courses_category_coursecategories_fk',
references: {
table: 'coursecategories',
field: 'coursecategoryId',
},
defaultValue: 0,
allowNull: true,
onDelete: 'no action',
onUpdate: 'no action',
})
},
down: async (queryInterface, Sequelize) => {
return await queryInterface.removeConstraint('courses', ['courses_category_coursecategories_fk'],{})
}
I expect the migration run fine. I review the INFORMATION_SCHEMA table and my Key (courses_category_coursecategories_fk) is there. But somethings happen and i not receive more logg that..
ERROR: s.replace is not a function
removeConstraint accepts a string as a second argument, not an array, see the docs. Should work like this:
return await queryInterface.removeConstraint('courses','courses_category_coursecategories_fk')

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)

Sequelizejs, how sort field values when creating an index

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"