bulkUpdate in sequelize orm
Minimal bulkCreate
+ updateOnDuplicate
example
Just to clarify what was mentioned at https://stackoverflow.com/a/54900639/895245
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.' + path.basename(__filename) + '.sqlite',
});
(async () => {
const Integer = sequelize.define('Integer',
{
value: {
type: DataTypes.INTEGER,
unique: true, // mandatory
primaryKey: true,
},
name: {
type: DataTypes.STRING,
},
inverse: {
type: DataTypes.INTEGER,
},
},
{
timestamps: false,
}
);
await Integer.sync({force: true})
await Integer.create({value: 2, inverse: -2, name: 'two'});
await Integer.create({value: 3, inverse: -3, name: 'three'});
await Integer.create({value: 5, inverse: -5, name: 'five'});
// Initial state.
assert.strictEqual((await Integer.findOne({ where: { value: 2 } })).name, 'two');
assert.strictEqual((await Integer.findOne({ where: { value: 3 } })).name, 'three');
assert.strictEqual((await Integer.findOne({ where: { value: 5 } })).name, 'five');
assert.strictEqual((await Integer.findOne({ where: { value: 2 } })).inverse, -2);
assert.strictEqual((await Integer.findOne({ where: { value: 3 } })).inverse, -3);
assert.strictEqual((await Integer.findOne({ where: { value: 5 } })).inverse, -5);
assert.strictEqual(await Integer.count(), 3);
// Update.
await Integer.bulkCreate(
[
{value: 2, name: 'TWO'},
{value: 3, name: 'THREE'},
{value: 7, name: 'SEVEN'},
],
{ updateOnDuplicate: ["name"] }
);
// Final state.
assert.strictEqual((await Integer.findOne({ where: { value: 2 } })).name, 'TWO');
assert.strictEqual((await Integer.findOne({ where: { value: 3 } })).name, 'THREE');
assert.strictEqual((await Integer.findOne({ where: { value: 5 } })).name, 'five');
assert.strictEqual((await Integer.findOne({ where: { value: 7 } })).name, 'SEVEN');
assert.strictEqual((await Integer.findOne({ where: { value: 2 } })).inverse, -2);
assert.strictEqual((await Integer.findOne({ where: { value: 3 } })).inverse, -3);
assert.strictEqual((await Integer.findOne({ where: { value: 5 } })).inverse, -5);
assert.strictEqual(await Integer.count(), 4);
await sequelize.close();
})();
Generated SQLite update query:
INSERT INTO `IntegerNames` (`value`,`name`) VALUES (2,'TWO'),(3,'THREE'),(7,'SEVEN')
ON CONFLICT (`value`) DO UPDATE SET `name`=EXCLUDED.`name`;
Minimal update
example
To complement what was mentioned at https://stackoverflow.com/a/63727529/895245 you don't need to use fixed values for update, you can also use other columns and SQL functions, e.g.:
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes, Op } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.' + path.basename(__filename) + '.sqlite',
});
(async () => {
const Inverses = sequelize.define('Inverses',
{
value: {
type: DataTypes.INTEGER,
primaryKey: true,
},
inverse: {
type: DataTypes.INTEGER,
},
name: {
type: DataTypes.STRING,
},
},
{ timestamps: false }
);
await Inverses.sync({force: true})
await Inverses.create({value: 2, inverse: -2, name: 'two'});
await Inverses.create({value: 3, inverse: -3, name: 'three'});
await Inverses.create({value: 5, inverse: -5, name: 'five'});
// Initial state.
assert.strictEqual((await Inverses.findOne({ where: { value: 2 } })).inverse, -2);
assert.strictEqual((await Inverses.findOne({ where: { value: 3 } })).inverse, -3);
assert.strictEqual((await Inverses.findOne({ where: { value: 5 } })).inverse, -5);
assert.strictEqual(await Inverses.count(), 3);
// Update to fixed value.
await Inverses.update(
{ inverse: 0, },
{ where: { value: { [Op.gt]: 2 } } },
);
assert.strictEqual((await Inverses.findOne({ where: { value: 2 } })).inverse, -2);
assert.strictEqual((await Inverses.findOne({ where: { value: 3 } })).inverse, 0);
assert.strictEqual((await Inverses.findOne({ where: { value: 5 } })).inverse, 0);
assert.strictEqual(await Inverses.count(), 3);
// Update to match another column.
await Inverses.update(
{ inverse: sequelize.col('value'), },
{ where: { value: { [Op.gt]: 2 } } },
);
assert.strictEqual((await Inverses.findOne({ where: { value: 2 } })).inverse, -2);
assert.strictEqual((await Inverses.findOne({ where: { value: 3 } })).inverse, 3);
assert.strictEqual((await Inverses.findOne({ where: { value: 5 } })).inverse, 5);
assert.strictEqual(await Inverses.count(), 3);
// Update to match another column with modification.
await Inverses.update(
{ inverse: sequelize.fn('1 + ', sequelize.col('value')), },
{ where: { value: { [Op.gt]: 2 } } },
);
assert.strictEqual((await Inverses.findOne({ where: { value: 2 } })).inverse, -2);
assert.strictEqual((await Inverses.findOne({ where: { value: 3 } })).inverse, 4);
assert.strictEqual((await Inverses.findOne({ where: { value: 5 } })).inverse, 6);
assert.strictEqual(await Inverses.count(), 3);
// A string function test.
await Inverses.update(
{ name: sequelize.fn('upper', sequelize.col('name')), },
{ where: { value: { [Op.gt]: 2 } } },
);
assert.strictEqual((await Inverses.findOne({ where: { value: 2 } })).name, 'two');
assert.strictEqual((await Inverses.findOne({ where: { value: 3 } })).name, 'THREE');
assert.strictEqual((await Inverses.findOne({ where: { value: 5 } })).name, 'FIVE');
assert.strictEqual(await Inverses.count(), 3);
await sequelize.close();
})();
Generated SQLite update queries:
UPDATE `Inverses` SET `inverse`=$1 WHERE `value` > 2
UPDATE `Inverses` SET `inverse`=`value` WHERE `value` > 2
UPDATE `Inverses` SET `inverse`=1 + (`value`)
UPDATE `Inverses` SET `name`=upper(`name`) WHERE `value` > 2
QueryInterface.bulkUpdate
minimal example for a migration
What was confusing me is that there is a bulkUpdate
for QueryInterface
, but there isn't one for Model
.
Part of the reason seems to be that there is no way to make non-raw queries in migrations as mentioned at: https://github.com/sequelize/cli/issues/862 With the non-raw queries, we can just use .update
as shown above. It appears that this bulkUpdate
is a more raw version that is available to migrations.
A full example is shown at: https://github.com/cirosantilli/cirodown/blob/bulk-update-col/web/migrations/20210903000000-user-add-display-name-column.js In this example, I was adding a new displayName
column to the database, and I wanted it to be based on the existing username
column.
Here's an example of how to do it to make displayName
be the same as username
. Note how it is not actually using an Array input like Model.bulkCreate
, it just runs an SQL UPDATE
query instead, so the naming is a bit confusing:
module.exports = {
up: async (queryInterface, Sequelize) => queryInterface.sequelize.transaction(async transaction => {
await queryInterface.addColumn('User', 'displayName',
{
type: Sequelize.STRING(256),
allowNull: false,
defaultValue: '',
},
{transaction},
)
await queryInterface.bulkUpdate('User',
{displayName: queryInterface.sequelize.col('username')},
{}, // optional where clause to select which rows to update
// If empty like this it updates every single row.
{transaction},
)
}),
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('User', 'displayName')
}
};
Related example for creation: Add data in Sequelize migration script?
TODO QueryInterface.bulkInsert
+ updateOnDuplicate
minimal example for a migration
OK, so now I want to actually insert an array during migration, since my modification is just too complex, and cannot be done inside SQL with bulkUpdate
: I have to bring data into Node and then push it back.
I would expect the following would work, but it didn't, and given the complete lack of backtraces... I'm not sure why:
module.exports = {
up: async (queryInterface, Sequelize) => queryInterface.sequelize.transaction(async transaction => {
await queryInterface.addColumn('User', 'displayName',
{
type: Sequelize.STRING(256),
allowNull: false,
defaultValue: '',
},
{transaction},
)
const [users] = await queryInterface.sequelize.query('SELECT * FROM "User";', { transaction });
const newUsers = users.map(user =>
{ return { id: user.id, displayName: user.username } }
)
await queryInterface.bulkInsert('User',
newUsers,
{
updateOnDuplicate: ['displayName'],
transaction,
}
)
}),
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('User', 'displayName')
}
};
fails with:
ERROR: Cannot read property 'map' of undefined
Tested on sequelize 6.5.1, sqlite3 5.0.2, node v14.17.0, Ubuntu 21.04.
Use the bulkCreate to bulkUpdate method.
bulkCreate([...], { updateOnDuplicate: ["name"] })
updateOnDuplicate
is an array of fields that will be updated when the primary key (or may be unique key) match the row. Make sure you have at least one unique field (let say id) in your model and in the dataArray
both for upsert.
For reference refer here
You can, if you want to update a lot of records with the same values! example: I want to update field "activationStatus" for 10 users at 1 time, 1 user = 1 record in DB and I have Array of user IDs then:
User.update({ activationStatus: 'active'}, {
where: {
id: [1,2,3,4,5,6,7,8,9,10]
}
});
it will be analogue of SQL query:
UPDATE User SET activationStatus = 'active' WHERE id IN(1,2,3,4,5,6,7,8,9,10);
you can find more info about Sequelize Operator Aliases HERE