Mongo 关联表查询

cc
cc
2024-06-27 / 0 评论 / 40 阅读 / 正在检测是否收录...

摘要

Mongo支持与Mysql类似的关联表查询,但比较麻烦。

通过与Mysql实现进行对比,便于理解Mongo如何实现。

Mysql

Order表关联Product表的id, 实现查询订单支持搜索产品名称。

# Order

CREATE TABLE `order` (
  `id` bigint(20) DEFAULT NULL,
  `no` varchar(255) DEFAULT NULL,
  `amount` int(11) DEFAULT NULL COMMENT '单位- 分\n',
  `productId` bigint(20) DEFAULT NULL,
  `count` int(11) DEFAULT NULL,
  `userId` bigint(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  
-- ----------------------------
-- Records of product
-- ----------------------------
INSERT INTO `order` (`id`, `no`, `amount`, `productId`, `count`, `userId`) VALUES (1, '2024062701', 10000, 1, 1, 1);
INSERT INTO `order` (`id`, `no`, `amount`, `productId`, `count`, `userId`) VALUES (1, '2024062702', 20000, 1, 2, 1);

# Product
CREATE TABLE `product` (
  `id` bigint(20) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `price` int(11) DEFAULT NULL COMMENT '单位-分'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of product
-- ----------------------------

INSERT INTO `product` (`id`, `name`, `price`) VALUES (1, '测试产品', 10000);

关联查询

查询订单、产品名称,通过left join搜索产品名称

SELECT o,p.name from `order` as o left JOIN `product` as p on o.productId = p.id  where p.name like '%测试%';

## 结果
#id    no   amount productId count userId name
1    2024062701    10000    1    1    1    测试产品
1    2024062702    20000    1    2    1    测试产品

Mongo

同样在mongo中准备Order和Product的集合

## insert to product
db.product.insertOne({
  name: '测试产品',
  price: 10000
});

## result
{
    "_id" : ObjectId("667d7fbe04cfea555206351f"),
    "name" : "测试产品",
    "price" : 10000
}

## insert to orders
db.order.insertMany([
  {
    no: '2024062701',
    amount: 10000,
    productId: "667d7fbe04cfea555206351f",
    count: 1,
    userId: 1
  },
  {
    no: '2024062702',
    amount: 20000,
    productId: "667d7fbe04cfea555206351f",
    count: 2,
    userId: 1
  }
]);

# result
{
    "_id" : ObjectId("667d80b804cfea5552063520"),
    "no" : "2024062701",
    "amount" : 10000,
    "productId" : "667d7fbe04cfea555206351f",
    "count" : 1,
    "userId" : 1
}

{
    "_id" : ObjectId("667d80b804cfea5552063521"),
    "no" : "2024062702",
    "amount" : 20000,
    "productId" : "667d7fbe04cfea555206351f",
    "count" : 2,
    "userId" : 1
}

关联查询

使用Mongo提供的$lookup来实现关联查询

{
  "$lookup": {
    "from": {
      "connectionName": "<registered-atlas-connection>",
      "db": "<registered-database-name>",
      "coll": "<atlas-collection-name>"
    },
    "localField": "<field-in-source-messages>",
    "foreignField": "<field-in-from-collection>",
    "as": "<output-array-field>"
  }
}

那么我们使用Mongo查询,应该这样写

db.order.aggregate(
    {
        $lookup: {
            from: "product", # 需要关联的product表
            localField: "proudctId", # order中的productId
            foreignField: '_id', # 与proudct中的_id关联
            as: "p" # product的别名,出现在查询结果中,默认是一个数组
        }

    },
    { $unwind: "$p" }, # 将p拉平,如果p是多个,那么会出现多条记录
    # 此时得到 order + p:{}
    { 
        $match: {
            "p.name": { # 过滤p.name
                $regex: ".*测试.*", # 使用正则,模糊查询= '%测试%'
                $options: "i" # 忽略大小写
            }
        }
    })

但是,没有查询到结果!?

因为 在orderproductId是String,而product._id是ObjectId,他俩不是同一个类型,所以是不等的。

解决

他俩类型不一样,那么把他俩类型弄成一样再比较!

db.order.aggregate(
    {
        $lookup: {
            from: "product",
            let: { pid: "$productId" }, ## 取order.productId到变量pid中
            pipeline: [ ## 在product表操作,将pid变成ObjectId后与order._id进行等于比较
                {
                    $match: {
                        $expr: {
                            $eq: ["$_id", { $toObjectId: "$$pid" }]
                        }
                    }
                }
            ],
            as: "p"
        }
    },
    { $unwind: "$p" },
    {
        $match: {
            "p.name": {
                $regex: ".*测试.*",
                $options: "i"
            }
        }
    })



## Result
// collection: order
{
    "_id" : ObjectId("667d80b804cfea5552063520"),
    "no" : "2024062701",
    "amount" : 10000,
    "productId" : "667d7fbe04cfea555206351f",
    "count" : 1,
    "userId" : 1,
    "p" : {
        "_id" : ObjectId("667d7fbe04cfea555206351f"),
        "name" : "测试产品",
        "price" : 10000
    }
}
// collection: order
{
    "_id" : ObjectId("667d80b804cfea5552063521"),
    "no" : "2024062702",
    "amount" : 20000,
    "productId" : "667d7fbe04cfea555206351f",
    "count" : 2,
    "userId" : 1,
    "p" : {
        "_id" : ObjectId("667d7fbe04cfea555206351f"),
        "name" : "测试产品",
        "price" : 10000
    }
}

lookup 解释

1、将order.productId声明一个新的变量:pid
2、将order._id与 pid比较,此时的pid转换成了obejctId

AI 解释

这段代码是MongoDB聚合管道($aggregate)中的一部分,使用了$lookup阶段来实现集合之间的关联查询。这里是将当前集合(假设为订单或其他与产品相关的集合)与"product"集合进行关联,以获取与每个文档中productId字段对应的产品详细信息。我将逐步解释每一部分的含义:

$lookup: 这是MongoDB聚合管道的一个操作符,用于在当前集合的文档中连接来自另一个集合的文档。它基于指定的键执行类似SQL中的左外连接。
 - from: "product": 指定要连接的另一个集合名为"product"。这是你想要获取额外数据的集合。
 - let: { pid: "$productId" }: 定义了一个变量pid,其值为当前文档中的productId字段。这在后续的pipeline中作为查找依据。
 - pipeline: 这是一个数组,包含了在"product"集合上执行的聚合管道操作。这里仅有一个操作:

 - $match: 用于过滤"product"集合中的文档。它只选择那些满足特定条件的文档。
   - $expr: 允许在$match中使用聚合表达式。
   - $eq: 检查两个表达式是否相等。在这个例子中,检查"product"集合文档的_id字段是否等于通过$toObjectId转换后的$$pid(即来自外部文档的productId,已经转换为ObjectId类型)。

- as: "p": 指定将连接结果存储在当前文档的新字段p中,作为数组形式存在,包含匹配到的所有"product"集合的文档。

Spring Data Mongo Tempalte 实现

        Pageable pageable= "Spring data Pageable';
        String name= "测试"
        // Template不支持let,所以自己实现
        AggregationOperation l = context -> new Document("$lookup",
                new Document("from", "product")
                        .append("let", new Document("pid", "$projectId"))
                        .append("pipeline", Arrays.asList(
                                new Document("$match",
                                        new Document("$expr",
                                                new Document("$eq", Arrays.asList("$_id", new Document("$toObjectId", "$$pid"))))
                                )
                        ))
                        .append("as", "p"));

        UnwindOperation unwindOperation = Aggregation.unwind("p");

        Criteria c = new Criteria();
        if (StringUtils.isNotBlank(name)) {
            c.and("p.name").regex(".*" + name + ".*", "i");
        }

        

        Aggregation aggregation = Aggregation.newAggregation(
                l, unwindOperation,
                Aggregation.match(c),
                Aggregation.count().as("c")
        );

        Document count = MongoUtils.template(dbName).aggregate(aggregation, Order.class, Document.class)
                .getUniqueMappedResult();

        // 没有搜索到结果,这里的count会是null
        if (count == null) {
            return PageResult.empty();
        }

        Aggregation pageAggregation = Aggregation.newAggregation(
                // 这有顺序关联: 先跳过,在limit
                l, unwindOperation,
                Aggregation.match(c),
                Aggregation.skip(pageable.getOffset()),
                Aggregation.limit(pageable.getPageSize())
        );

        List<Order> data = MongoUtils.template(dbName).aggregate(pageAggregation, Order.class, Order.class)
                .getMappedResults();


     return Page.of(count.getInteger("c"), data);
0

评论 (0)

取消