简体   繁体   English

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

[英]get JOIN table as array of results with PostgreSQL/NodeJS

I'm creating an app where users are able to create questions, and others can upvote/downvote them.我正在创建一个应用程序,用户可以在其中创建问题,而其他人可以对它们进行投票/否决。

The following is a part of my sql schema:以下是我的 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
);

What I would like to have is Postgres giving me each question with an array of votes, like that:我想要的是 Postgres 给我一个投票数组的每个问题,就像这样:

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

I looked at aggregate functions (like array_agg()) but it gave me only the values.我查看了聚合函数(如 array_agg()),但它只给了我值。 A JOIN gave me a question joined with a vote, and would force me to do server side operations, which I would prefer not to. A JOIN 给了我一个加入投票的问题,并会迫使我进行服务器端操作,而我不想这样做。

Is there any way to do that?有没有办法做到这一点? Is my reasoning regarding what I want to obtain wrong?我关于我想要获得的东西的推理是错误的吗?

Thanks for your time.谢谢你的时间。

This is easy to do with pg-promise :使用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);
    });

The same as above, but using ES7 async / await syntax:同上,但使用 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 API: map , any , task , batch


Related questions:相关问题:


And if you want to use just a single query, then using PostgreSQL 9.4 and later syntax you can do the following:如果您只想使用单个查询,那么使用 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

And then your pg-promise example would be:然后你的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);

And you definitely will want to keep such complex queries in external SQL files.而且您肯定希望在外部 SQL 文件中保留如此复杂的查询。 See Query Files .请参阅查询文件

Conclusion结论

The choice between the two approaches presented above should be based on the performance requirements of your application:上述两种方法之间的选择应基于您的应用程序的性能要求:

  • The single-query approach is faster, but is somewhat difficult to read or extend, being fairly verbose单查询方法更快,但有点难以阅读或扩展,相当冗长
  • The multi-query approach is easier to understand and to extend, but it is not great for performance, due to dynamic number of queries executed.多查询方法更易于理解和扩展,但由于执行的查询数量是动态的,因此性能不佳。

UPDATE-1更新-1

The following related answer offers more options, by concatenating child queries, which will give a much improved performance: Combine nested loop queries to parent result pg-promise .以下相关答案通过连接子查询提供了更多选项,这将大大提高性能:将嵌套循环查询组合到父结果 pg-promise

UPDATE-2更新-2

Another example added, using ES7 async / await approach.添加了另一个示例,使用 ES7 async / await方法。

Please think simple way, May be I am right, I use knex js请想简单的方法,也许我是对的,我使用 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');

sql-toolkit does exactly this. sql-toolkit正是这样做的。 It's a node library built for pg-promise which allows you to write regular native SQL and receive back properly structured (nested) pure business objects, without either having to split up the query or rewrite it with json_build_object .它是一个为pg-promise构建的节点库,它允许您编写常规的本机 SQL 并接收正确结构化(嵌套)的纯业务对象,而无需拆分查询或使用json_build_object重写它。

For example:例如:

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

The select clause uses the business object "getSQLSelectClause" methods to save tedium in typing the columns, as well as ensure no collisions of names (nothing magical going on, and could just be written out instead). select 子句使用业务对象“getSQLSelectClause”方法来节省键入列的乏味,并确保没有名称冲突(没有什么神奇的事情发生,可以改为写出)。

The this.one is a call into sql-toolkit s base DAO class. this.one是对sql-toolkit的基本 DAO 类的调用。 It is responsible for structuring the flat result records into a nice nested structure.它负责将平面结果记录构建为一个很好的嵌套结构。

(Also notice that it is "one" which matches our mental model for the SQL. The DAO methods for one, oneOrNone, many, and any ensure their count against the number of generated top level business objects - not the number of rows the sql expression returns!) (还要注意,它是“一”,它与我们的 SQL 心智模型相匹配。一、oneOrNone、多和任何的 DAO 方法确保它们根据生成的顶级业务对象的数量进行计数 - 而不是 sql 的行数表达式返回!)

Check out the repository for details on how to set it up on top of pg-promise .查看存储库以获取有关如何在pg-promise之上进行设置的详细信息。 It's strictly an enhancement, and does not seek to abstract out pg-promise (you still set up pg-promise and can use it directly).它严格来说是一种增强,并不寻求抽象出 pg-promise(您仍然设置 pg-promise 并可以直接使用它)。 (Disclamer, I am the author of sql-toolkit .) (免责声明,我是sql-toolkit的作者。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM