MongoDB数据库中如何实现多表联查的举例
前些天遇到一个需求,不复杂,用 SQL 表现的话,大约如此:
SELECT *
FROM db1 LEFT JOIN db2 ON db1.a = db2.b
WHERE db1.userId=’$me’ AND db2.status=1
FROM db1 LEFT JOIN db2 ON db1.a = db2.b
WHERE db1.userId=’$me’ AND db2.status=1
没想到搜了半天,我厂的代码仓库里没有这种用法,各种教程也多半只针对合并查询(即只筛选 db1
,没有 db2
的条件)。所以最后只好读文档+代码尝试,终于找到答案,记录一下。
- 我们用 mongoose 作为连接库
- 联查需要用
$lookup
- 如果声明外键的时候用
ObjectId
,就很简单:
// 假设下面两个表 db1 和 db2
export const Db1Schema = new mongoose.Schema(
{
userId: { type: String, index: true },
couponId: { type: ObjectId, ref: Db2Schema },
},
{ versionKey: false, timestamps: true }
);
export const Db2Schema = new mongoose.Schema(
{
status: { type: Boolean, default: 0 },
},
{ versionKey: false, timestamps: true }
);
export const Db1Schema = new mongoose.Schema(
{
userId: { type: String, index: true },
couponId: { type: ObjectId, ref: Db2Schema },
},
{ versionKey: false, timestamps: true }
);
export const Db2Schema = new mongoose.Schema(
{
status: { type: Boolean, default: 0 },
},
{ versionKey: false, timestamps: true }
);
// 那么只要
db1Model.aggregate([
{
$lookup: {
from: ‘db2’, // 目标表
localField: ‘couponId’, // 本地字段
foreignField: ‘_id’, // 对应的目标字段
as: ‘source’,
},
{
$match: [ /* 各种条件 */ ],
},
]);
但是我们没有用 ObjectId
,而是用 string
作为外键,所以无法直接用上面的联查。必须在 pipeline
里手动转换、联合。此时,当前表(db1
)的字段不能直接使用,要配合 let
,然后加上 $$
前缀;连表(db2
)直接加 $
前缀即可。
最终代码如下:
mongo.ts
// 每次必有的条件,当前表的字段用 `$$`,连表的字段用 `$`
const filter = [{ $eq: [‘$$userId’, userId] }, { $eq: [‘$isDeleted’, false] }];
if (status === Expired) {
dateOp = ‘$lte’;
} else if (status === Normal) {
dateOp = ‘$gte’;
filter.push({ $in: [‘$$status’, [Normal, Shared]] });
} else {
dateOp = ‘$gte’;
filter.push({ $eq: [‘$$status’, status] });
}
const results = await myModel.aggregate([
{
$lookup: {
from: ‘coupons’,
// 当前表字段必须 `let` 之后才能用
let: { couponId: ‘$couponId’, userId: ‘$userId’, status: ‘$status’ },
// 在 pipeline 里完成筛选
pipeline: [
{
$match: {
$expr: {
// `$toString` 是内建方法,可以把 `ObjectId` 转换成 `string`
$and: [{ $eq: [{ $toString: ‘$_id’ }, ‘$$couponId’] }, …filter, { [dateOp]: [‘$endAt’, new Date()] }],
},
},
},
// 只要某些字段,在这里筛选
{
$project: couponFields,
},
],
as: ‘source’,
},
},
{
// 这种筛选相当 LEFT JOIN,所以需要去掉没有连表内容的结果
$match: {
source: { $ne: [] },
},
},
{
// 为了一次查表出结果,要转换一下输出格式
$facet: {
results: [{ $skip: size * (page – 1) }, { $limit: size }],
count: [
{
$count: ‘count’,
},
],
},
},
]);
const filter = [{ $eq: [‘$$userId’, userId] }, { $eq: [‘$isDeleted’, false] }];
if (status === Expired) {
dateOp = ‘$lte’;
} else if (status === Normal) {
dateOp = ‘$gte’;
filter.push({ $in: [‘$$status’, [Normal, Shared]] });
} else {
dateOp = ‘$gte’;
filter.push({ $eq: [‘$$status’, status] });
}
const results = await myModel.aggregate([
{
$lookup: {
from: ‘coupons’,
// 当前表字段必须 `let` 之后才能用
let: { couponId: ‘$couponId’, userId: ‘$userId’, status: ‘$status’ },
// 在 pipeline 里完成筛选
pipeline: [
{
$match: {
$expr: {
// `$toString` 是内建方法,可以把 `ObjectId` 转换成 `string`
$and: [{ $eq: [{ $toString: ‘$_id’ }, ‘$$couponId’] }, …filter, { [dateOp]: [‘$endAt’, new Date()] }],
},
},
},
// 只要某些字段,在这里筛选
{
$project: couponFields,
},
],
as: ‘source’,
},
},
{
// 这种筛选相当 LEFT JOIN,所以需要去掉没有连表内容的结果
$match: {
source: { $ne: [] },
},
},
{
// 为了一次查表出结果,要转换一下输出格式
$facet: {
results: [{ $skip: size * (page – 1) }, { $limit: size }],
count: [
{
$count: ‘count’,
},
],
},
},
]);
同事告诉我,这样做的效率不一定高。我觉得,考虑到实际场景,他说的可能没错,不过,早晚要迈出这样的一步。而且,未来我们也应该慢慢把外键改成 ObjectId
类型。
总结
到此这篇关于MongoDB中实现多表联查的文章就介绍到这了,更多相关MongoDB多表联查内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!