knex js查询多对多

knex js query many to many

我在使用节点时遇到问题 & knex.js

我正在尝试构建一个带有 post 的迷你博客,并添加了向 post

添加多个标签的功能

我有一个 POST 模型 具有以下属性:

id SERIAL PRIMARY KEY NOT NULL,
name TEXT,

其次,我有 标签模型 用于存储标签:

id SERIAL PRIMARY KEY NOT NULL,
 name TEXT

我有很多 table:Post 标签 引用了 post 和标签:

 id SERIAL PRIMARY KEY NOT NULL,
 post_id INTEGER NOT NULL REFERENCES posts ON DELETE CASCADE,
 tag_id INTEGER NOT NULL REFERENCES tags ON DELETE CASCADE

我成功插入了标签,并创建了 post 标签, 但是当我想获取 Post 带有附加标签的数据时 post 我遇到了麻烦

这里有个问题:

 const data = await knex.select('posts.name as postName', 'tags.name as tagName'
            .from('posts')
            .leftJoin('post_tags', 'posts.id', 'post_tags.post_id')
            .leftJoin('tags', 'tags.id', 'post_tags.tag_id')
            .where('posts.id', id)

以下查询 returns 这个结果:

[
  {
    postName: 'Post 1',
    tagName: 'Youtube',
  },
  {
    postName: 'Post 1',
    tagName: 'Funny',
  }
]

但我希望结果的格式和返回方式如下:

  {
    postName: 'Post 1',
    tagName: ['Youtube', 'Funny'],
  }

这甚至可以通过查询实现,还是我必须手动格式化数据?

结果是正确的,因为这就是 SQL 的工作方式 - 它 returns 行数据。 SQL 除了 table(想想 CSV 数据或 Excel 电子表格)没有返回任何东西的概念。

您可以使用 SQL 做一些有趣的事情,这些事情可以将标签转换为您连接在一起的字符串,但这并不是您真正想要的。无论哪种方式,您都需要添加 post 处理步骤。

对于您当前的查询,您可以简单地执行如下操作:

function formatter (result) {
    let set = {};

    result.forEach(row => {
        if (set[row.postName] === undefined) {
            set[row.postName] = row;
            set[row.postName].tagName = [set[row.postName].tagName];
        }
        else {
            set[row.postName].tagName.push(row.tagName);
        }
    });

    return Object.values(set);
}

// ...

query.then(formatter);

这应该不会很慢,因为您只循环一次结果。

实现此目的的一种方法是使用某种聚合函数。如果您使用的是 PostgreSQL:

const data = await knex.select('posts.name as postName', knex.raw('ARRAY_AGG (tags.name) tags'))
    .from('posts')
    .innerJoin('post_tags', 'posts.id', 'post_tags.post_id')
    .innerJoin('tags', 'tags.id', 'post_tags.tag_id')
    .where('posts.id', id)
    .groupBy("postName")
    .orderBy("postName")
    .first();

->

{ postName: 'post1', tags: [ 'tag1', 'tag2', 'tag3' ] }

对于MySQL:

const data = await knex.select('posts.name as postName', knex.raw('GROUP_CONCAT (tags.name) as tags'))
    .from('posts')
    .innerJoin('post_tags', 'posts.id', 'post_tags.post_id')
    .innerJoin('tags', 'tags.id', 'post_tags.tag_id')
    .where('posts.id', id)
    .groupBy("postName")
    .orderBy("postName")
    .first()
    .then(res => Object.assign(res, { tags: res.tags.split(',')}))

MySQL中没有数组,GROUP_CONCAT只会将所有标签拼接成一个字符串,所以我们需要手动拆分。

->

RowDataPacket { postName: 'post1', tags: [ 'tag1', 'tag2', 'tag3' ] }