'How can I include an empty array after using $match in mongodb aggregation
I am trying to fetch boards.lists that have an empty cards array or some cards that are not archived, but using $match with $or operators not work. How can I solve this problem??
The sample document is like:
{
_id: ObjectId("616bcd746bfed71fdcf892c3"),
userId: ObjectId("611cb3a14f142d5d94daa395"),
name: "myworkspace",
description: "",
boards: [
{
_id: ObjectId("6175f8c69c03810ea8e7b31e"),
name: 'myboard',
color: '',
labels: [
{ color: 'blue', title: 'project1' }
],
lists: [
{
archived: true,
_id: ObjectId("6175f8c69c03810ea8e7b321"),
cards: []
},
{
archived: false,
_id: ObjectId("6175f8c69c03810ea8e7b322"),
cards: []
},
{
archived: false,
_id: ObjectId("6175f8c69c03810ea8e7b323"),
cards: [{
_id: ObjectId("61721cbc3092d32970cf2fed")
archived: true,
labelSelected: [],
title: "mycard",
description: "",
attachments: [],
comments: []
}]
},
{
archived: false,
_id: ObjectId("6175f8c69c03810ea8e7b324"),
cards: [{
_id: ObjectId("6175f8d09c03810ea8e7b32d")
archived: false,
labelSelected: [],
title: "mycard",
description: "",
attachments: [],
comments: []
}]
},
]
}
]
}
What I did:
docs = await WorkspaceModel.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(workspaceId) } },
{ $unwind: "$boards" },
{ $match: { "boards._id": mongoose.Types.ObjectId(boardId) } },
{ $unwind: "$boards.lists" },
{ $match: { "boards.lists.archived": false } },
{
$unwind: {
path: "$boards.lists.cards",
preserveNullAndEmptyArrays: true,
},
},
{
$match: {
$or: [
{ "boards.lists.cards": { $exists: true, $size: 0 } },
{ "boards.lists.cards.archived": false }
]
},
},
{
$group: {
_id: "$boards._id",
name: { $first: "$boards.name" },
color: { $first: "$boards.color" },
labels: { $first: "$boards.labels" },
lists: { $addToSet: "$boards.lists" },
},
},
]).exec();
What I got: [], before the $match statement, it just shows all the lists that are not archived.
What I expect:
[
{
_id: ObjectId("6175f8c69c03810ea8e7b31e"),
name: 'myboard',
color: '',
labels: [
{ color: 'blue', title: 'project1' }
],
lists: [
{
archived: false,
_id: ObjectId("6175f8c69c03810ea8e7b322"),
cards: []
},
{
archived: false,
_id: ObjectId("6175f8c69c03810ea8e7b324"),
cards: [{
_id: ObjectId("6175f8d09c03810ea8e7b32d")
archived: false,
labelSelected: [],
title: "mycard",
description: "",
attachments: [],
comments: []
}]
},
]
}
]
Solution 1:[1]
Query
- unwind boards
- from lists keep only that have
(and (= archived false) (or empty_cards cards_contains_not_archived_card) - if you want to filter out the empty lists also you can add a match
{"$match" : {"lists": {"$ne" : [] }}} - in your expected output you dont have an _id to know to which document each list belong, in the bellow query the same happens
aggregate(
[{"$unwind": {"path": "$boards"}},
{"$replaceRoot": {"newRoot": "$boards"}},
{"$set":
{"lists":
{"$filter":
{"input": "$lists",
"cond":
{"$and":
[{"$eq": ["$$this.archived", false]},
{"$or":
[{"$eq": ["$$this.cards", []]},
{"$in": [false, "$$this.cards.archived"]}]}]}}}}}])
Solution 2:[2]
the problem is from your match according to your sample data I create aggregate like this just with match and group if you need you can change or add some pipelines
https://mongoplayground.net/p/SWr_q-bI9A5
db.collection.aggregate([
{
"$unwind": "$lists"
},
{
$match: {
$or: [
{
"lists.cards": []
},
{
"lists.cards": {
$elemMatch: {
"archived": false
}
}
}
]
}
},
{
"$group": {
"_id": "_id",
"lists": {
"$addToSet": "$lists"
},
"name": {
"$first": "$name"
},
"lables": {
"$first": "$labels"
},
"color": {
"$first": "$color"
},
}
}
])
Solution 3:[3]
this is my solution:
$filternon archived list first and use as the input of$map$filternon archived or empty cards and map the result to each list- it will show the non archived lists without cards or with non archived cards
docs = await WorkspaceModel.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(workspaceId) } },
{ $unwind: "$boards" },
{ $match: { "boards._id": mongoose.Types.ObjectId(boardId) } },
{ $replaceRoot: { newRoot: "$boards" } },
{
$set: {
lists: {
$map: {
input: {
$filter: {
input: "$lists",
as: "list",
cond: {
$eq: ["$$list.archived", false],
},
},
},
as: "filteredList",
in: {
title: "$$filteredList.title",
cards: {
$filter: {
input: "$$filteredList.cards",
as: "card",
cond: {
$or: [
{ $eq: ["$$card", []] },
{ $eq: ["$$card.archived", false] },
],
},
},
},
},
},
},
},
},
]).exec();
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | mohammad Naimi |
| Solution 3 | Blake Sim |
