mongodb查询以将子文档显示为主文档并使用它进行分页

Posted

技术标签:

【中文标题】mongodb查询以将子文档显示为主文档并使用它进行分页【英文标题】:mongodb query to show subdocument as maindocument and having pagination with it 【发布时间】:2021-02-06 03:38:31 【问题描述】:

首先,我有这种类型的模型:

const produkSchema = new mongoose.Schema(
    nama_produk: String,
    etalase: type: mongoose.Schema.Types.ObjectID, ref: 'kategori',
    kategori: type: mongoose.Schema.Types.ObjectID, ref: 'kategori',
    jenis: type: mongoose.Schema.Types.ObjectID, ref: 'kategori.jenis',
    bahan: String,
    warna: String,
    deskripsi: String,
    foto_produk: [String],
    harga: Number,
    link_bukalapak: String,
    link_shopee: String,
    link_tokopedia: String,
    link_lazada: String,
    link_website: String,
    display: type: Boolean, default: false,
, 
    weights: 
        nama_produk: 5,
    ,
    timestamps: true
)

const tokoSchema = new mongoose.Schema(
    username: type: String, trim: true,
    password: type: String, required: true, select: false,
    merek: String,
    listMerek: [type: mongoose.Schema.Types.ObjectID, ref: 'produk'],
    deskripsi: String,
    follower: [type: mongoose.Schema.Types.ObjectID, ref: 'user'],
    email: type: String, trim: true, unique: true,
    instagram: String,
    whatsapp: String,
    website: String,
    alamat: String,
    foto_profil: String,
    bukalapak: String,
    shopee: String,
    tokopedia: String,
    fotoktp: String,
    banner: [
        gambar: type: String, required: true, trim: true,
        // order: type: Number, required: true,
    ],
    produk: [produkSchema],
    etalase: [type: mongoose.Schema.Types.ObjectID, ref: 'kategori'],
    // etalase: [
    //     kategori: type: mongoose.Schema.Types.ObjectID, ref: 'kategori',
    //     order: Number
    // ],
    approve: type: Number, default: 0, // 0: pending, 1: reject, 2: approve
    populer: type: Boolean, default: false,
    gambar_populer: [String],
    pilihan: type: Boolean, default: false,
, timestamps: true);

我有一个端点可以使用以下代码过滤此produkSchema

exports.filterProduk = (req, res) => 
    const merek, warna, kategori, jenis, hargaAwal, hargaAkhir, skip, limit = req.body

    let query = 
    const $and = []

    if (merek) 
        $and.push($or: merek.map(id => (_id: mongoose.Types.ObjectId(id))))
    

    if (warna) 
        $and.push($or: warna.map(warna => ("produk.warna": warna)))
    

    if (kategori) 
        query["produk.etalase"] = mongoose.Types.ObjectId(kategori)
    

    if (jenis) 
        $and.push($or: jenis.map(id => ("produk.jenis": mongoose.Types.ObjectId(id))))
    

    if (hargaAwal !== '') 
        query["produk.harga"] = 
            $gte: parseInt(hargaAwal),
        
    
    if (hargaAkhir !== '') 
        query["produk.harga"] = 
            $lte: parseInt(hargaAkhir)
        
    

    if ($and.length > 0) 
        query = $and, ...query
    

    toko.aggregate([
        $unwind: '$produk',
        $match: query,
        
            $lookup: 
                from: "kategoris",
                as: "produk.etalase",
                let: pjid: "$produk.jenis",
                pipeline: [
                    $unwind: "$jenis",
                    $match: $expr: $eq: ["$$pjid", "$jenis._id"],
                    
                        $project: 
                            "jenis._id": 1,
                            "jenis.label": 1
                        
                    
                ]
            
        ,
        $unwind: path: "$produk.etalase",
        $group: _id: '$_id', produk: $push: '$produk', foto_profil: $first: '$foto_profil',
        $limit: skip + limit,
        $skip: skip
    ])
        .then(async data => 
            res.status(200).json(data, prefix: produk: "uploads/produk", toko: "uploads/toko")
        )

实际结果是:

[
_id: blabla,
 foto_profil: "blabla",
 produk:[nama_produk: "blabla", bahan: "blabla", ...rest ProdukSchema as query]
,
_id: blabla,
 foto_profil: "blabla",
 produk:[nama_produk: "blabla", bahan: "blabla", ...rest ProdukSchema as query]
,
_id: blabla,
 foto_profil: "blabla",
 produk:[nama_produk: "blabla", bahan: "blabla", ...rest ProdukSchema as query]

]

预期:

[
nama_produk: "blabla", bahan: "blabla", ...rest ProdukSchema as query, foto_profil(from toko schema): "blabla",
nama_produk: "blabla", bahan: "blabla", ...rest ProdukSchema as query, foto_profil(from toko schema): "blabla"
]

并使用此produkSchema 进行分页(限制和偏移)

其实在此之前,我已经在here求过这个查询,但是这个查询会产生很多数据,需要分页,

我该怎么做?我应该将我的produkSchema 拆分为主子文档吗?或者是否存在针对这种情况的任何查询?

【问题讨论】:

@turivishal 非常好!两个 Playground 都与我的预期输出相同,但我需要此查询的跳过和限制,你能更新 Playground 吗? 【参考方案1】:

我不确定确切的要求,但我可以猜到 2 个选项,

假设变量:
let skip = 0;
let limit = 10;

第一个选项:

从末尾删除$group 阶段并在$unwind 阶段之后开始 $replaceRoot 合并对象 produkfoto_profil 使用 $mergeObjects 并替换根
  
    $replaceRoot: 
      newRoot: 
        $mergeObjects: [ foto_profil: "$foto_profil" , "$produk"]
      
    
  ,
   $skip: skip * limit ,
   $limit: limit 

Playground


第二个选项:

从末尾删除$group 阶段并在$unwind 阶段之后开始 $group by toko id 和 produk id,这将分组 produk 并获得唯一的第一个 produk $replaceRoot 合并对象 produkfoto_profil 使用 $mergeObjects 并替换根
  
    $group: 
      _id: 
        _id: "$_id",
        produk_id: "$produk._id"
      ,
      root:  $first: "$$ROOT" 
    
  ,
  
    $replaceRoot: 
      newRoot: 
        $mergeObjects: [ foto_profil: "$root.foto_profil" , "$root.produk"]
      
    
  ,
   $skip: skip * limit ,
   $limit: limit 

Playground

【讨论】:

【参考方案2】:

也许您可以在查找后执行此操作:

    $unwind produk(其中 produk 仍为数组类型,然后展开它)。 $group: _id: nama_produk 等... $project ...... 或任何你想做的事情。

这样您就可以通过 nama_produk 作为 _id 获取组列表。

【讨论】:

嗨@Fair,感谢您的回答,但我对您的回答有点困惑。您能否添加代码示例?无论如何,分页怎么样? 对不起,我没有注意到你已经在第一行 $unwind "produk"。重点是在您的 $group 上,您需要在“$group”上的“_id”中填写您要制作主文档的密钥。您写道,您的第一个字段预期输出是“nama_produk”,然后在 $group 上用“nama_produk”填写您的“_id”。 mongo 中的分页与 sql base 类似。在 sql 基础上,您使用偏移量和限制。在 mongo 中,您使用跳过和限制。或在使用 mongo 聚合时加 $ 符号。我以前从 req.query 客户端获取 limit & skip 的值,所以后端只发回客户端需要的值。

以上是关于mongodb查询以将子文档显示为主文档并使用它进行分页的主要内容,如果未能解决你的问题,请参考以下文章

只有在满足条件的情况下,有没有办法将子文档推送到 mongodb 数组中?

mongodb 查询数据

mongodb:查询文档

MongoDB 查询文档

MongoDB 查询文档随笔

MongoDB 查询文档