续集 orm 中的批量更新

aks*_*ade 24 node.js express sequelize.js

我们如何在sequelize orm中实现像bulkCreate这样的bulkUpdate,我搜索了sequelize的整个文档但没有找到与bulkUpdate相关的任何内容,所以我尝试在for循环中循环更新,它可以工作,但是还有其他方法可以批量更新

Sak*_*vam 41

使用bulkCreate 到bulkUpdate 方法。

bulkCreate([...], { updateOnDuplicate: ["name"] })
Run Code Online (Sandbox Code Playgroud)

updateOnDuplicate是一个字段数组,当主键(或可能是唯一键)与行匹配时将更新这些字段。确保您的模型和dataArray两者中至少有一个唯一字段(比如 id)用于 upsert。

参考这里

  • 不适用于 MS SQL。sequelize 抛出错误:“mssql 不支持 updateOnDuplicate 选项。” (6认同)
  • “仅受 MySQL、MariaDB、SQLite >= 3.24.0 和 Postgres >= 9.5 支持” - 来自文档:https://sequelize.org/master/class/lib/model.js~Model.html#static-method-批量创建 (2认同)

Ole*_*rin 20

你可以,如果你想用相同的值更新很多记录!示例:我想一次更新 10 个用户的字段“activationStatus”,1 个用户 = 数据库中的 1 个记录,并且我有用户 ID 数组,然后:

User.update({ activationStatus: 'active'}, {
          where: {
              id: [1,2,3,4,5,6,7,8,9,10]
          }
      });
Run Code Online (Sandbox Code Playgroud)

它将类似于 SQL 查询:

UPDATE User SET activationStatus = 'active' WHERE id IN(1,2,3,4,5,6,7,8,9,10);
Run Code Online (Sandbox Code Playgroud)

您可以在此处找到有关 Sequelize 运算符别名的更多信息


Cir*_*四事件 5

最小bulkCreate+updateOnDuplicate示例

只是为了澄清/sf/answers/3843044761/中提到的内容

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();
})();
Run Code Online (Sandbox Code Playgroud)

生成的 SQLite 更新查询:

INSERT INTO `IntegerNames` (`value`,`name`) VALUES (2,'TWO'),(3,'THREE'),(7,'SEVEN')
  ON CONFLICT (`value`) DO UPDATE SET `name`=EXCLUDED.`name`;
Run Code Online (Sandbox Code Playgroud)

最小的update例子

为了补充/sf/answers/4460927061/中提到的内容,您不需要使用固定值进行更新,您还可以使用其他列和 SQL 函数,例如:

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();
})();
Run Code Online (Sandbox Code Playgroud)

生成的 SQLite 更新查询:

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
Run Code Online (Sandbox Code Playgroud)

QueryInterface.bulkUpdate迁移的最小示例

让我困惑的是,有一个bulkUpdateforQueryInterface,但没有forModel

部分原因似乎是无法在迁移中进行非原始查询,如以下所述: https: //github.com/sequelize/cli/issues/862对于非原始查询,我们可以使用.update如上图所示。看来这bulkUpdate是一个可用于迁移的更原始的版本。

完整的示例如下所示: https: //github.com/cirosantilli/cirodown/blob/bulk-update-col/web/migrations/20210903000000-user-add-display-name-column.js在这个示例中,我是向数据库添加一个新displayName列,我希望它基于现有username列。

下面是如何使displayName与 相同的示例username。请注意,它实际上并没有使用像 那样的数组输入Model.bulkCreate,它只是运行 SQLUPDATE查询,因此命名有点令人困惑:

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')
  }
};
Run Code Online (Sandbox Code Playgroud)

相关创建示例:在Sequelize迁移脚本中添加数据?

TODO QueryInterface.bulkInsert+updateOnDuplicate迁移的最小示例

好的,现在我想在迁移过程中实际插入一个数组,因为我的修改太复杂了,并且无法在 SQL 中完成bulkUpdate:我必须将数据引入 Node,然后将其推回。

我希望以下内容能够工作,但事实并非如此,并且考虑到完全缺乏回溯......我不知道为什么:

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')
  }
};
Run Code Online (Sandbox Code Playgroud)

失败并显示:

ERROR: Cannot read property 'map' of undefined
Run Code Online (Sandbox Code Playgroud)

在sequelize 6.5.1、sqlite3 5.0.2、node v14.17.0、Ubuntu 21.04 上测试。