【问题标题】:get JOIN table as array of results with PostgreSQL/NodeJS使用 PostgreSQL/NodeJS 获取 JOIN 表作为结果数组
【发布时间】:2017-02-09 20:54:21
【问题描述】:

我正在创建一个应用程序,用户可以在其中提出问题,其他人可以投票/反对。

以下是我的 sql 架构的一部分:

CREATE TABLE "questions" (
  id            SERIAL,
  content       VARCHAR(511) NOT NULL,
  created_at    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  CONSTRAINT    pk_question PRIMARY KEY (id)
);

CREATE TABLE "votes" (
  id            SERIAL,
  value         INT,
  question_id   INT NOT NULL,
  CONSTRAINT    pk_vote PRIMARY KEY (id),
  CONSTRAINT    fk_question_votes FOREIGN KEY (question_id) REFERENCES questions (id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE CASCADE
);

我想要的是 Postgres 给我每个问题的投票,就像这样:

[{ // a question
  id: 1,
  content: 'huh?',
  votes: [{ // a vote
    id: 1,
    value: 1
  }, { // another vote
    id: 2,
    value: -1
  }]
}, { /*another question with votes*/ }]

我查看了聚合函数(如 array_agg()),但它只给了我值。 A JOIN 给了我一个投票加入的问题,并会迫使我进行服务器端操作,我不希望这样做。

有没有办法做到这一点?我对我想要获得什么的推理是错误的吗?

感谢您的宝贵时间。

【问题讨论】:

  • 我想你在找json_agg()row_to_json()
  • @a_horse_with_no_name 从 PostgreSQL 9.4 开始,json_agg() + json_build_object() 对提供了更好的解决方案。请参阅下面的答案。

标签: sql node.js postgresql


【解决方案1】:

pg-promise 很容易做到这一点:

function buildTree(t) {
    const v = q => t.any('SELECT id, value FROM votes WHERE question_id = $1', q.id)
        .then(votes => {
            q.votes = votes;
            return q;
        });

    return t.map('SELECT * FROM questions', undefined, v).then(a => t.batch(a));
}

db.task(buildTree)
    .then(data => {
        console.log(data); // your data tree
    })
    .catch(error => {
        console.log(error);
    });

同上,但使用 ES7 async/await 语法:

await db.task(async t => {
    const questions = await t.any('SELECT * FROM questions');
    for(const q of questions) {
        q.votes = await t.any('SELECT id, value FROM votes WHERE question_id = $1', [q.id]);
    }
    return questions;
});
// method "task" resolves with the correct data tree

API:mapanytaskbatch


相关问题:


如果您只想使用单个查询,那么使用 PostgreSQL 9.4 及更高版本的语法,您可以执行以下操作:

SELECT json_build_object('id', q.id, 'content', q.content, 'votes',
    (SELECT json_agg(json_build_object('id', v.id, 'value', v.value))
     FROM votes v WHERE q.id = v.question_id))
FROM questions q

然后您的pg-promise 示例将是:

const query =
    `SELECT json_build_object('id', q.id, 'content', q.content, 'votes',
        (SELECT json_agg(json_build_object('id', v.id, 'value', v.value))
         FROM votes v WHERE q.id = v.question_id)) json
    FROM questions q`;
    
const data = await db.map(query, [], a => a.json);

而且您肯定希望将这些复杂的查询保存在外部 SQL 文件中。见Query Files

结论

上述两种方法之间的选择应基于应用程序的性能要求:

  • 单查询方法更快,但有点难以阅读或扩展,相当冗长
  • 多查询方法更易于理解和扩展,但由于执行的查询数量是动态的,因此性能不佳。

UPDATE-1

以下相关答案通过连接子查询提供了更多选项,这将大大提高性能:Combine nested loop queries to parent result pg-promise

UPDATE-2

添加了另一个示例,使用 ES7 async/await 方法。

【讨论】:

  • 感谢您的回答,我不是多查询方法的真正粉丝,因为它不能很好地扩展,但另一个带有 json_build_object 的方法正是我想要的!
  • 从性能的角度来看,这两种解决方案不是都很糟糕吗? n + 1 个查询(使用 json_agg 您“只是”保存 io,但数据库仍然需要对每一行运行此查询,IIRC)
  • @Michael 根据我的测试,第二种方法,通过一个查询非常快。
  • 添加了 ES7 示例。
【解决方案2】:

请想个简单的方法,也许我是对的,我用的是 knex js

 let allpost = knex
        .select([
            'questions.id',
            'question.content',
            knex.raw('json_agg(v.*) as votes')
        ])
        .from('questions')
        .leftJoin('votes as v', 'questions.id', 'v.question_id')
        .groupBy('questions.id');

【讨论】:

    【解决方案3】:

    sql-toolkit 正是这样做的。它是为pg-promise 构建的节点库,允许您编写常规的本机 SQL 并接收正确结构化(嵌套)的纯业务对象,而无需拆分查询或使用 json_build_object 重写它。

    例如:

    class Article extends BaseDAO {
      getBySlug(slug) {
        const query = `
          SELECT
            ${Article.getSQLSelectClause()},
            ${Person.getSQLSelectClause()},
            ${ArticleTag.getSQLSelectClause()},
            ${Tag.getSQLSelectClause()}
          FROM article
          JOIN person
            ON article.author_id = person.id
          LEFT JOIN article_tags
            ON article.id = article_tags.article_id
          LEFT JOIN tag
            ON article_tags.tag_id = tag.id
          WHERE article.slug = $(slug);
      `;
      return this.one(query, { slug });
      // OUTPUT: Article {person: Person, tags: Tags[Tag, Tag, Tag]}
    }
    

    select 子句使用业务对象“getSQLSelectClause”方法来节省键入列时的乏味,并确保名称没有冲突(没有什么神奇的事情,可以直接写出来)。

    this.one 是对 sql-toolkits 基本 DAO 类的调用。它负责将平面结果记录构造成一个很好的嵌套结构。

    (还要注意它是“一”,它与我们的 SQL 心智模型相匹配。用于 one、oneOrNone、many 和 any 的 DAO 方法确保它们对生成的顶级业务对象的数量进行计数 - 而不是sql 表达式返回的行!)

    查看repository,了解如何在pg-promise 之上设置它的详细信息。它严格来说是一种增强,并不寻求抽象出 pg-promise(您仍然设置 pg-promise 并可以直接使用它)。 (声明,我是sql-toolkit的作者。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-11
      • 1970-01-01
      • 2019-02-19
      • 1970-01-01
      • 1970-01-01
      • 2016-05-27
      相关资源
      最近更新 更多