使用 PostgreSQL/NodeJS 获取 JOIN 表作为结果数组

Posted

技术标签:

【中文标题】使用 PostgreSQL/NodeJS 获取 JOIN 表作为结果数组【英文标题】:get JOIN table as array of results with PostgreSQL/NodeJS 【发布时间】: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() 对提供了更好的解决方案。请参阅下面的答案。 【参考方案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:map、any、task、batch


相关问题:

Get a parents + children tree with pg-promise Conditional task with pg-promise

如果您只想使用单个查询,那么使用 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的作者。)

【讨论】:

以上是关于使用 PostgreSQL/NodeJS 获取 JOIN 表作为结果数组的主要内容,如果未能解决你的问题,请参考以下文章

slf4j-jdk14 从哪里获取日志记录配置?

获取方面的“方法参数”的名称? (方面J)

尝试获取用户输入以生成二维数组乘法表

如何使用python获取我们的确切位置(纬度和经度)?

如何获取整数到数组(从整数 i 到整数 j)。 C#

如何获取数组中指定个数的几个最大值