【问题标题】:How to ignore SequelizeUniqueConstraintError in Sequelize?如何在 Sequelize 中忽略 SequelizeUniqueConstraintError?
【发布时间】:2021-11-29 17:07:53
【问题描述】:

使用documentation 中的示例,仅在第一次插入时一切正常。在第二个和后续之后,就会出现错误。

const Product = this.sequelize.define('Product', {
  title: Sequelize.STRING
});
const Tag = this.sequelize.define('Tag', {
  name: Sequelize.STRING,
  unique: true
});
const ProductTag = this.sequelize.define('ProductTag', {
  product_id: Sequelize.INTEGER,
  tag_id: Sequelize.INTEGER
});

Product.belongsToMany(Tag, {through: 'ProductTag', as: 'tag'});
Tag.belongsToMany(Product, {through: 'ProductTag'});

第一个插入工作正常。

Product.create({
  title: 'Chair',
  tag: [
    { name: 'Alpha'},
    { name: 'Beta'}
  ]
}, {
  include: [{
    model: Tag,
    as: 'tag'
  }]
})

SQL 日志

INSERT INTO "Product" ("id","title","created_at","updated_at") VALUES (DEFAULT,'Chair','2019-02-25 12:51:50.802 +00:00','2019-02-25 12:51:50.802 +00:00') RETURNING *;
INSERT INTO "Tag" ("id","name","created_at","updated_at") VALUES (DEFAULT,'Alpha','2019-02-25 12:51:51.061 +00:00','2019-02-25 12:51:51.061 +00:00') RETURNING *;
INSERT INTO "Tag" ("id","name","created_at","updated_at") VALUES (DEFAULT,'Beta','2019-02-25 12:51:51.061 +00:00','2019-02-25 12:51:51.061 +00:00') RETURNING *;
INSERT INTO "ProductTag" ("product_id","tag_id","created_at","updated_at") VALUES (1,1,'2019-02-25 12:51:51.068 +00:00','2019-02-25 12:51:51.068 +00:00') RETURNING *;
INSERT INTO "ProductTag" ("product_id","tag_id","created_at","updated_at") VALUES (1,2,'2019-02-25 12:51:51.072 +00:00','2019-02-25 12:51:51.072 +00:00') RETURNING *;

后续插入 Product.create(...) 产生错误 SequelizeUniqueConstraintError

Key "(name)=(Alpha)" already exists.

如何做到,如果标签已经存在,则获取现有标签的ID并忽略错误?

【问题讨论】:

  • 您已将此问题标记为 [mysql] 和 [postgresql] - 您实际使用的是哪个 RDBMS?
  • 你可能想看看upsert。
  • 使用 [postgresql]
  • 感谢@TGrif 的回答,但使用upsert,只添加了产品。未添加标签。
  • 这是什么行为,因为在您的模型定义中,您的标签为unique: true。这意味着在您的所有记录中,每个值只能有一个。

标签: node.js postgresql sequelize.js


【解决方案1】:

我的解决方案。

添加方法updateOrCreate

Product.updateOrCreate = function (options) {
    return this.findOrCreate(options).then(res => {
        let [row, created] = res;
        if (created) return [row, created];
        return row.update(options.defaults, options.transaction)
            .then(updated => [updated, created]);
    })
};
Tag.updateOrCreate = function (options) {
    return this.findOrCreate(options).then(res => {
        let [row, created] = res;
        if (created) return [row, created];
        return row.update(options.defaults, options.transaction)
            .then(updated => [updated, created]);
    })
};

使用

let data = {
    title: 'Chair',
    tag: [
        {name: 'Alpha'},
        {name: 'Beta'}
    ]
};
return sequelize.transaction().then(t => {
    return Product.updateOrCreate({
        where: {title: data.title},
        defaults: data,
        transaction: t
    }).then(res => {
        let [product] = res;
        return Promise.all(data.tag.map(tag => {
            return Tag.updateOrCreate({
                where: {name: tag.name},
                defaults: tag,
                transaction: t
            }).then(res => {
                let [tag] = res;
                return Promise.resolve(tag);
            }).catch(err => Promise.reject(err.message));
        })).then(() => {
            t.commit();
            sequelize.close();
        }).catch(err => Promise.reject(err));
    }).catch(err => {
        t.rollback();
        sequelize.close();
    });
});

【讨论】:

    【解决方案2】:

    bulkCreate 与 ignoreDuplicates: true

    这可能很有趣。在 sequelize@6.5.1 sqlite3@5.0.2 上测试,这会产生一个 INSERT OR IGNORE INTO,而 PostgreSQL 会产生 ON CONFLIC DO NOTHING。

    请注意下面进一步讨论的缺少 ID 的问题。

    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',
      define: {
        timestamps: false
      },
    });
    (async () => {
    const Tag = sequelize.define('Tag', {
      name: {
        type: DataTypes.STRING,
        unique: true,
      },
    });
    await sequelize.sync({force: true})
    await Tag.create({name: 't0'})
    
    // Individual create does not have the option for some reason.
    // Apparently you're just supposed to catch.
    // https://github.com/sequelize/sequelize/issues/4513
    //await Tag.create({name: 't0', ignoreDuplicates: true})
    
    // SQLite: INSERT OR IGNORE INTO as desired.
    // IDs may be missing here.
    const tags = await Tag.bulkCreate(
      [
        {name: 't0'},
        {name: 't1'},
        {name: 't1'},
        {name: 't2'},
      ],
      {
        ignoreDuplicates: true,
      }
    )
    const tagsFound = await Tag.findAll({order: [['name', 'ASC']]})
    assert.strictEqual(tagsFound[0].name, 't0')
    assert.strictEqual(tagsFound[1].name, 't1')
    assert.strictEqual(tagsFound[2].name, 't2')
    assert.strictEqual(tagsFound.length, 3)
    
    await sequelize.close();
    })();
    

    缺少 ID 问题

    不幸的是,Tag.bulkCreate 的返回值不包含 SQLite 在INSERT INTO 期间生成的 ID,但正如在 https://github.com/sequelize/sequelize/issues/11223#issuecomment-864185973 中提到的那样:

    这可能是因为 SQLite 和 PostgreSQL 都没有返回底层查询中的行:

    这打破了 OP 提到的从退货立即为产品分配标签的用例,因为我们需要通过表的 ID。

    我不确定它与 OPs findOrCreate 选项相比如何,但我认为在我的文章上有我选择的标签模型“更新模型”控制器之后再进行第二次查找/选择会更快:

      await Promise.all([
        req.app.get('sequelize').models.Tag.bulkCreate(
          tagList.map((tag) => {return {name: tag}}),
          {ignoreDuplicates: true}
        ).then(tags => {
          // IDs may be missing from the above, so we have to do a find.
          req.app.get('sequelize').models.Tag.findAll({
            where: {name: tagList}
          }).then(tags => {
            return article.setTags(tags)
          })
        }),
        article.save()
      ])
    

    【讨论】:

    • 在大多数情况下这是一个很好的解决方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-14
    相关资源
    最近更新 更多