我正在提取具有以下信息的数据库查询:
id, name, roleId, roleTitle
在查询中,我正在拉动用户及其角色。每个用户可以拥有0到N个角色。我想最终得到一个像这样的对象:
{
id
name
roles: [{
id
title
}]
}
这样做最有效的方法是什么?目前我正在做这样的事情:
const data = [];
arr.forEach((u) => {
const index = data.findIndex(x => x.id === u.id);
if (index >= 0) {
data[index].roles.push({ id: u.roleId, title: u.roleTitle });
} else {
data.push({
id: u.id,
name: u.name,
roles: u.roleId ? [{
id: u.roleId,
title: u.roleTitle,
}] : [],
});
}
}
此解决方案正常工作但不确定如果我们将用户数量扩展到10k,每个用户的平均角色为3或50k,每个用户有5个角色,这是否是完成此操作的最快方法
你最好的选择是在SQL中实现这一切,因为你正在为你的数据库使用PostgreSQL(如评论中所述)。我不知道你的表和列的确切名称,所以你可能需要调整它,但这会得到你想要的:
SELECT json_agg(t)
FROM (
SELECT
u.id,
u.name,
ro.roles
FROM "user" u
LEFT JOIN (
SELECT
ur.user_id,
json_agg(
json_build_object(
'id', r.id,
'title', r.title
)
) AS roles
FROM user_role ur
LEFT JOIN "role" r ON r.id = ur.role_id
GROUP BY ur.user_id
) ro ON ro.user_id = u.id
) t;
SQL小提琴:http://www.sqlfiddle.com/#!17/5f6ca/11
json_build_object
将使用指定的名称/值对创建对象,因此:
json_build_object(
'id', r.id,
'title', r.title
)
将角色id
和title
组合成一个JSON对象,如下所示:
{id: 1, title: "Title 1"}
json_agg
将多行聚合到一个JSON数组中,因此它将上面的角色对象转换为单个列,这是每个用户的角色对象数组(感谢内部子查询的GROUP BY u.id
部分)。内部子查询为我们提供了这样的结果集(每个用户一行)
| user_id | roles |
|---------|------------------------------------------------------|
| 1 | [{id: 1, title: "Role 1"}, {id: 2, title: "Role 2"}] |
然后子查询连接到用户表,并且所有这些都包含在另一个子查询中,因此可以在整个结果上使用json_agg
并返回单个json对象,该对象是具有角色的用户数组。
这几乎肯定不是最有效的版本,但比你现在正在做的更快:
const data = Object.values(arr.reduce((obj, {id, name, roleId, roleTitle}) => {
if (!(id in obj)) {
obj[id] = {
id,
name,
roles: {},
};
}
if (!obj[id].roles[roleId]) {
obj[id].roles[roleId] = {
id: roleId,
title: roleTitle,
};
}
return obj;
}, {}));
通过使用对象(散列)而不是数组,确定用户是否已经存在或者用户是否已经具有角色是恒定时间O(1)操作(散列函数的成本)。但是,根据所使用的搜索方法,搜索数组在最坏的情况下是线性的O(n),甚至最好的情况是O(log n)。
您可以选择随风而变化的微优化兔子洞,但选择正确的数据结构和算法通常可以让您获得最优惠的优势。
我已经使用Object.values
转换回最后的数组,如果省略这个并且只是坚持使用它可能会更快。
希望这可以帮助。
var modified_array = function(xs, key) {
return xs.reduce(function(rv, x) {
obj = (rv[x[key]] = rv[x[key]] || {});
obj.id = x.id;
obj.name = x.name;
obj.roles = obj.roles || []
obj.roles.push({ id: x.roleId, title: x.roleTitle})
return rv;
}, {});
};
arr = [{id:1,name:"abd",roleId: 10,roleTitle: "hello"},
{id:1, name: "abd", roleId: 15,roleTitle: "fello"}]
console.log( Object.values(modified_array(arr, 'id')));