【问题标题】:Seed foreign key constrain in knexknex 中的种子外键约束
【发布时间】:2017-10-16 12:29:24
【问题描述】:

我正在尝试使用 knex 中的外键创建以下表格:

评论

+----+---------+-------------------+------------+------------+------------+-----------+
| id | post_id | comment           | is_deleted | createdAt  | updatedAt  | deletedAt |
+----+---------+-------------------+------------+------------+------------+-----------+
| 1  | 2       | This is a comment | false      | 16.10.2017 | 16.10.2017 |           |
+----+---------+-------------------+------------+------------+------------+-----------+

发帖

+----+-----------------+------------------+---------+------------+------------+-----------+
| id | titel           | description      | deleted | createdAt  | updatedAt  | deletedAt |
+----+-----------------+------------------+---------+------------+------------+-----------+
| 1  | This is a titel | Test Description | false   | 16.10.2017 | 16.10.2017 |           |
+----+-----------------+------------------+---------+------------+------------+-----------+
| 2  | Titel Test      | Test Description | false   | 16.10.2017 | 16.10.2017 |           |
+----+-----------------+------------------+---------+------------+------------+-----------+

我创建了以下迁移:

comments.js

exports.up = function (knex, Promise) {
  return knex.schema.createTable("comments", function (t) {
    t.increments("id").unsigned().primary().references('id').inTable('posts')
    t.text("comment").nullable()
    t.boolean("is_deleted").nullable()
    t.dateTime("createdAt").notNull()
    t.dateTime("updatedAt").nullable()
    t.dateTime("deletedAt").nullable()
  })
}

posts.js

exports.up = function (knex, Promise) {
    return knex.schema.createTable('posts', function (t) {
        t.increments('id').unsigned().primary();
        t.string('title').notNull();
        t.text('description').nullable();
        t.boolean('deleted').nullable();       
        t.dateTime('createdAt').notNull();
        t.dateTime('updatedAt').nullable();
        t.dateTime('deletedAt').nullable();
    });
};

最后,我尝试用假数据为表格播种:

const faker = require("faker")
const knex = require("../db/knexfile.js")
const _ = require("lodash")
const postNumber = 50
const commentNumber = 150


function getRandomPostId() {
  const numberOfPosts = knex("posts").count("title")
  return _.random(0, numberOfPosts)
}

exports.seed = function(knex, Promise) {
  return Promise.all([
    knex("posts").del()
    .then(function() {
      const posts = []
      for (let index = 0; index < postNumber; index++) {
        posts.push({
          titel: faker.lorem.sentence(),
          description: faker.lorem.sentence(),
          createdAt: faker.date.recent(),
          updatedAt: faker.date.recent(),
          deletedAt: faker.date.recent(),
          deleted: faker.random.boolean(),
          tags: faker.random.arrayElement(["tag1", "tag2", "tag3", "tag4", ]),
        })
      }
      return knex("posts").insert(posts)
    }),
    knex("comments").del()
    .then(function() {
      const comments = []
      for (let index = 0; index < commentNumber; index++) {
        comments.push({
          comment: faker.lorem.sentence(),
          createdAt: faker.date.recent(),
          deletedAt: faker.date.recent(),
          updatedAt: faker.date.recent(),
          is_deleted: faker.date.recent(),
        })
      }
      return knex("comments").insert(comments)
    })

  ])
}

但是,我收到以下错误:

Using environment: development
Error: ER_NO_REFERENCED_ROW_2: Cannot add or update a child row: a foreign key constraint fails (`c9`.`comments`, CONSTRAINT `comments_id_foreign` FOREIGN KEY (`id`) REFERENCES `posts` (`id`))
    at Query.Sequence._packetToError (/home/ubuntu/workspace/node_modules/mysql/lib/protocol/sequences/Sequence.js:52:14)
    at Query.ErrorPacket (/home/ubuntu/workspace/node_modules/mysql/lib/protocol/sequences/Query.js:77:18)
    at Protocol._parsePacket (/home/ubuntu/workspace/node_modules/mysql/lib/protocol/Protocol.js:279:23)
    at Parser.write (/home/ubuntu/workspace/node_modules/mysql/lib/protocol/Parser.js:76:12)
    at Protocol.write (/home/ubuntu/workspace/node_modules/mysql/lib/protocol/Protocol.js:39:16)
    at Socket.<anonymous> (/home/ubuntu/workspace/node_modules/mysql/lib/Connection.js:103:28)
    at emitOne (events.js:96:13)
    at Socket.emit (events.js:188:7)
    at readableAddChunk (_stream_readable.js:176:18)
    at Socket.Readable.push (_stream_readable.js:134:10)
    at TCP.onread (net.js:547:20)

我的外键约束错了吗?
有什么建议为什么我会收到此错误?

感谢您的回复!

【问题讨论】:

    标签: javascript node.js knex.js


    【解决方案1】:

    在迁移创建方面,尝试分离post_id 外键生成。看起来它正在尝试使用 comments.id 作为主键和外键,而不是引用 posts.id 的名为 post_id 的单独列

    迁移:

    exports.up = function (knex, Promise) {
        return knex.schema.createTable('posts', function (t) {
            t.increments().unsigned().primary();
            t.string('title').notNull();
            t.text('description').nullable();
            t.boolean('deleted').nullable();       
            t.dateTime('createdAt').notNull();
            t.dateTime('updatedAt').nullable();
            t.dateTime('deletedAt').nullable();
        });
    };
    
    exports.up = function (knex, Promise) {
      return knex.schema.createTable("comments", function (t) {
        t.increments().unsigned().primary();
        t.text("comment").nullable();
        t.boolean("is_deleted").nullable();
        t.dateTime("createdAt").notNull();
        t.dateTime("updatedAt").nullable();
        t.dateTime("deletedAt").nullable();
        // column with name post_id references posts.id
        t.foreign("post_id").references('id').inTable('posts');
        // or
        // t.foreign("post_id").references('posts.id');
      })
    };
    

    关于错误,看起来这主要是时间问题。 Promise.all() 不会按顺序运行/执行/启动承诺任务。这意味着当创建评论时,它可能没有有效的posts.id 关联。通过更新迁移,您最好等到所有帖子都创建完毕,获取现有的帖子 ID 值,然后使用这些值创建具有有效 post_id 约束的 cmets。 Knex 方法pluck() 在这里派上用场,因为您可以取回posts.id 值的数组。我还考虑将其分解为多个种子,因为我遇到了将超过 ~100 个作为批量插入给定表中的问题。您可以通过在每个循环上插入来解决此问题,这将花费更长的时间,但似乎不会遇到相同的限制。如果每条评论都需要与帖子相关联,而当前种子似乎没有发生这种情况,则看起来没有插入任何 post_id 或类似内容。下面的代码在每次迭代中抓取一个有效的随机posts.id 并将其分配给comments.post_id,满足约束。

    我所看到的播种顺序如下:

    1. 删除 cmets
    2. 删除帖子
    3. 创建帖子
    4. 使用有效/现有的帖子 ID 值创建 cmets

    种子:

    exports.seed = function(knex, Promise) {
        return knex("comments").del()
            .then(() => {
                return knex("posts").del();
            })
            .then(() => {
                const posts = [];
    
                for (let index = 0; index < postNumber; index++) {
                    posts.push({
                        title: faker.lorem.sentence(),
                        description: faker.lorem.sentence(),
                        createdAt: faker.date.recent(),
                        updatedAt: faker.date.recent(),
                        deletedAt: faker.date.recent(),
                        deleted: faker.random.boolean(),
                        tags: faker.random.arrayElement(["tag1", "tag2", "tag3", "tag4", ]),
                    });
                }
    
                return knex("posts").insert(posts);
            })
            .then(() => {
                return knex('posts').pluck('id').then((postIds) => {
                    const comments = [];
    
                        for (let index = 0; index < commentNumber; index++) {
                            comments.push({
                                comment: faker.lorem.sentence(),
                                createdAt: faker.date.recent(),
                                deletedAt: faker.date.recent(),
                                updatedAt: faker.date.recent(),
                                is_deleted: faker.date.recent(),
                                post_id: faker.random.arrayElement(postIds)
                            })
                        }
    
                    return knex("comments").insert(comments);
                });                
            });
    };
    

    注意:在种子中,帖子创建中的title 拼写错误为titel。不确定您是否打算使用 titel 而不是 title,但它需要保持一致。

    希望这会有所帮助!

    【讨论】:

      【解决方案2】:

      看起来您需要更正 cmets 表的映射,如下所示,以在 post_id 列上添加约束。您的映射似乎错误地创建了外键(评论的表 id 指的是帖子的 id)。

      exports.up = function (knex, Promise) {
        return knex.schema.createTable("comments", function (t) {
          t.increments("id").unsigned().primary()
          t.integer("post_id").references('id').inTable('posts')
          t.text("comment").nullable()
          t.boolean("is_deleted").nullable()
          t.dateTime("createdAt").notNull()
          t.dateTime("updatedAt").nullable()
          t.dateTime("deletedAt").nullable()
        })
      }
      

      其次,您需要考虑迁移/种子文件是按照其名称的自然顺序选择的。在您的情况下,comments.js 文件比 posts.js 文件更早执行,导致插入失败,原因很明显。

      要解决此问题,您可以将 post.js 重命名为 20170812_10_posts.js 并将 comments.js 重命名为 20170812_20_comments.js(例如)。前缀&lt;date&gt;_&lt;seq&gt;_ 只是一个建议的约定。您可以在命名迁移/种子文件时遵循任何约定,只要它们的名称符合预期的自然顺序即可。

      相关问题: https://github.com/tgriesser/knex/issues/993

      希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 2016-07-28
        • 1970-01-01
        • 2020-04-16
        • 1970-01-01
        • 1970-01-01
        • 2020-11-08
        • 1970-01-01
        • 2021-07-02
        • 2011-08-30
        相关资源
        最近更新 更多