【问题标题】:mongodb query to show subdocument as maindocument and having pagination with itmongodb查询以将子文档显示为主文档并使用它进行分页
【发布时间】: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 吗?

标签: mongodb mongoose mongodb-query aggregation-framework mongoose-schema


【解决方案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】:

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

    1. $unwind produk(其中 produk 仍为数组类型,然后展开它)。
    2. $group: { _id: nama_produk 等...}
    3. $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 的值,所以后端只发回客户端需要的值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-18
    • 1970-01-01
    • 2016-09-29
    相关资源
    最近更新 更多