如何在Sequelize中忽略SequelizeUniqueConstraintError?

时间:2019-02-25 13:08:08

标签: node.js postgresql sequelize.js

使用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并忽略该错误?

2 个答案:

答案 0 :(得分:0)

我的解决方案。

添加方法#include <QtWidgets> class LineEditStyle: public QProxyStyle { Q_OBJECT Q_PROPERTY(int cursorWidth READ cursorWidth WRITE setCursorWidth) public: using QProxyStyle::QProxyStyle; int cursorWidth() const{ if(m_cursor_width < 0) return baseStyle()->pixelMetric(PM_TextCursorWidth); return pixelMetric(PM_TextCursorWidth); } void setCursorWidth(int cursorWidth){ m_cursor_width = cursorWidth; } int pixelMetric(QStyle::PixelMetric metric, const QStyleOption *option = nullptr, const QWidget *widget = nullptr) const override { if(metric == PM_TextCursorWidth) if(m_cursor_width > 0) return m_cursor_width; return QProxyStyle::pixelMetric(metric, option, widget); } private: int m_cursor_width = -1; }; class LineEdit: public QLineEdit { Q_OBJECT public: LineEdit(QWidget *parent = nullptr): QLineEdit(parent) { LineEditStyle *new_style = new LineEditStyle(style()); new_style->setCursorWidth(10); setStyle(new_style); } }; int main(int argc, char *argv[]) { QApplication a(argc, argv); LineEdit w; w.show(); return a.exec(); } #include "main.moc"

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]);
    })
};

答案 1 :(得分:0)

bulkCreateignoreDuplicates: true

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

请注意下面进一步讨论的 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

这打破了 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()
  ])