[英]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 .
请参阅查询文件。
The choice between the two approaches presented above should be based on the performance requirements of your application:上述两种方法之间的选择应基于您的应用程序的性能要求:
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.