使用 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-toolkit
s 基本 DAO 类的调用。它负责将平面结果记录构造成一个很好的嵌套结构。
(还要注意它是“一”,它与我们的 SQL 心智模型相匹配。用于 one、oneOrNone、many 和 any 的 DAO 方法确保它们对生成的***业务对象的数量进行计数 - 而不是sql 表达式返回的行!)
查看repository,了解如何在pg-promise
之上设置它的详细信息。它严格来说是一种增强,并不寻求抽象出 pg-promise(您仍然设置 pg-promise 并可以直接使用它)。 (声明,我是sql-toolkit
的作者。)
【讨论】:
以上是关于使用 PostgreSQL/NodeJS 获取 JOIN 表作为结果数组的主要内容,如果未能解决你的问题,请参考以下文章