I am currently using the code below in node.js to find and return data on various nesting levels from a mongo database. I'd like to add another layer of nesting (as mentioned in #3).
Collection:
[
{
"title": "Category A",
"link": "a",
"items": [
{
"title": "Item C",
"link": "a-c",
"series": [
{
"title": "Item C X",
"link": "a-c-x"
},
{
"title": "Item C Y",
"link": "a-c-y"
},
]
},
{
"title": "Item D",
"link": "a-d"
}
]
},
{
"title": "Category B",
"link": "b"
}
]
The query:
const doc = await ... .findOne(
{
$or: [
{ link: id },
{ "items.link": id },
{ "items.series.link": id }
],
},
{
projection: {
_id: 0,
title: 1,
link: 1,
items: { $elemMatch: { link: id } },
},
}
);
Intended results:
(works) if link of the document is matched,
(works) there should only be an object with the title and link returned
e.g.
value of id variable: "a"
expected query result: { title: "Category A", link: "a"}
(works) if items.link of subdocument is matched,
(works) it should be the same as above + an additional element in the items array returned.
e.g.
value of id variable: "a-c"
expected query result: { title: "Category A", link: "a", items: [{ title: "Item C", link: "a-c" }]}
(works) if items.series.link of sub-subdocument is matched
(struggling with this) it should return the same as in 2. + an additional element inside the matched items.series
e.g.
value of id variable: "a-c-y"
expected query result: { title: "Category A", link: "a", items: [{ title: "Item C", link: "a-c", series: [{ title: "Item C Y", link: "a-c-y" }]}]}
current query result: The whole Category A document with all sub-documents
Questions:
a.) How do I modify the projection to return the expected output in #3 as well?
b.) Is the approach above sound in terms of reading speed from a denormalized structure? I figured there'd probably need to be indexes on link, items.link and items.series.link as they are all completely unique in the document, but maybe there is a way to achieve the above goal with a completely different approach?
Ended up with going half-way via mongodb and get the full item for both - when the item link is matched and the series link is matched:
projection: {
_id: 0,
title: 1,
link: 1,
items: { $elemMatch: { $or: [
{ link: id },
{"series.link": id }
]}},
}
After that javascript filters the series array to see if the series is matched:
doc?.items?.[0]?.series?.find(item => item.link === id)
if the js is truthy (returns an object) we matched a series, if there is a doc, but the js is falsy we matched an item result.
Although not a full mongodb solution and there is definitely room for improvement the above seems to achieve the end goal to be able to distinguish between category, item and series results.
Related
I have this document structure in the collection:
{"_id":"890138075223711744",
"guildID":"854557773990854707",
"name":"test-lab",
"game": {
"usedWords":["akşam","elma","akım"]
}
}
What is the most efficient way to get its fields except the array (it can be really large), and at the same time, see if an item exists in the array ?
I tried this:
let query = {_id: channelID}
const options = { sort: { name: 1 }, projection: { name: 1, "game.usedWords": { $elemMatch: { word}}}}
mongoClient.db(db).collection("channels").findOne(query, options);
but I got the error: "$elemMatch can not be used on nested fields"
If I've understood correctly you can use this query:
Using positional operator $ you can return only the matched word.
db.collection.find({
"game.usedWords": "akşam"
},
{
"name": 1,
"game.usedWords.$": 1
})
Example here
The output is only name and the matched word (also _id which is returned by default)
[
{
"_id": "890138075223711744",
"game": {
"usedWords": [
"akşam"
]
},
"name": "test-lab"
}
]
Below is my code to display review array data which is part of the restaurant collection object:
async get(reviewId) {
const restaurantsCollection = await restaurants();
reviewId = ObjectId(reviewId)
const r = await restaurantsCollection.findOne(
{ reviews: { $elemMatch: { _id : reviewId } } },
{"projection" : { "reviews.$": true }}
)
return r
}
My object looks like:
{
_id: '6176e58679a981181d94dfaf',
name: 'The Blue Hotel',
location: 'Noon city, New York',
phoneNumber: '122-536-7890',
website: 'http://www.bluehotel.com',
priceRange: '$$$',
cuisines: [ 'Mexican', 'Italian' ],
overallRating: 0,
serviceOptions: { dineIn: true, takeOut: true, delivery: true },
reviews: []
}
My output looks like:
{
"_id": "6174cfb953edbe9dc5054f99", // restaurant Id
"reviews": [
{
"_id": "6176df77d4639898b0c155f0", // review Id
"title": "This place was great!",
"reviewer": "scaredycat",
"rating": 5,
"dateOfReview": "10/13/2021",
"review": "This place was great! the staff is top notch and the food was delicious! They really know how to treat their customers"
}
]
}
What I want as output:
{
"_id": "6176df77d4639898b0c155f0",
"title": "This place was great!",
"reviewer": "scaredycat",
"rating": 5,
"dateOfReview": "10/13/2021",
"review": "This place was great! the staff is top notch and the food was delicious! They really know how to treat their customers"
}
How can I get the output as only the review without getting the restaurant ID or the whole object?
So the query operators, find and findOne do not allow "advanced" restructure of data.
So you have 2 alternatives:
The more common approach will be to do this in code, usually people either use some thing mongoose post trigger or have some kind of "shared" function that handles all of these transformations, this is how you avoid code duplication.
Use the aggregation framework, like so:
const r = await restaurantsCollection.aggregate([
{
$match: { reviews: { $elemMatch: { _id : reviewId } } },
},
{
$replaceRoot: {
newRoot: {
$arrayElemAt: [
{
$filter: {
input: "$reviews",
as: "review",
cond: {$eq: ["$$review._id", reviewId]}
}
},
0
]
}
}
}
])
return r[0]
I have a Chapter schema like this:
const ChapterSchema = new Schema({
title: {
type: String,
required: [true, 'Chapter title is required']
},
topics: { type: [TopicSchema] }
})
So, there is a topics array as sub-documents of Chapter.
I want to get a particular topic by its _id from Chapter. For that I've tried this query below:
let data = await Chapter.findOne({ "topics._id": _id })
return res.json(data)
But it returns a whole chapter of this topic with topic sibling like this:
{
"_id": "5e504271ee36f61ba8d76f37",
"title": "Roshayon Chapter 2",
"topics": [
{
"_id": "5e52bdf994b60b4c540cab33",
"title": "topic 4",
"intro": "<p><b>This text is bold</b></p><p><i>This text is italic</i></p><p>This is<sub> subscript</sub> and <sup>superscript</sup></p>"
},
{
"_id": "5e52bdf994b60b4c540cab34",
"title": "topic 5",
"intro": "<p><b>This text is bold</b></p><p><i>This text is italic</i></p><p>This is<sub> subscript</sub> and <sup>superscript</sup></p>"
}
]
}
I don't need whole chapter as above. I just need a single topic object which I am looking for by its id.
How can I able to get
Expected result:
{
"_id": "5e52bdf994b60b4c540cab34",
"title": "topic 5",
"intro": "<p><b>This text is bold</b></p><p><i>This text is italic</i></p><p>This is<sub> subscript</sub> and <sup>superscript</sup></p>"
}
You need to use $elemMatch while projecting so it gives us the matching array rec ord.
something like this should work
let data = await Chapter.findOne({ "topics._id": _id }, {_id: 0, topics: {$elemMatch: {_id: _id}}});
If you need to get the just object, you can use aggregate and use following query
await Chapter.aggregate([
{$match: {'topics._id': "5e52bdf994b60b4c540cab33"}},
{$project: {
topics: {$filter: {
input: '$topics',
as: 'topic',
cond: {$eq: ['$$topic._id', '5e52bdf994b60b4c540cab33']}
}},
_id: 0
}}
]).unwind("topics").exec()
Hope it helps.
I am trying to extract some data out of a MongoDB.
My DB looks something like this;
{
"name": "A",
"address": "London Road",
"values": [{"id": 1234, "name":"ABCD"},{"id": 6784, "name":"test"}]
}
{
"name": "B",
"address": "South Road",
"values": [{"id": 4327, "name":"guest"},{"id": 6743, "name":"demo"}]
}
{
"name": "C",
"address": "North Road",
"values": [{"id": 1234, "name":"ABCD"}]
}
I am trying to extract data based on the values id key.
So if I match 1234 to values id i'll return Elements A and C - I am able to do this.
The values I want to match may change, they could be 1234 or 6743 - returning Elements A, B and C.
I have created the below $or to handle this however the number of values to match varies, so the number of $or functions should change dependent on the number of values to match.
How do I create a query that allows me to match an unknown number of different values? - Maybe a loop?
Thanks in advance. I have search SO and the net and haven't had much success!
const orders = await findr.find({
$or: [{
values: {
$elemMatch: {
id: "1234",
}
}
},
{
values: {
$elemMatch: {
id: "6743",
}
}
}
]
}).toArray()
You can write the query using $in: {"values.id": { $in: ["123", "456"] }}
$elemMatch isn't necessary because you're only specifying a single criterion.
I am using a Cloudant database on Bluemix to storage products in a Node.js server. These products will be searched by categories. To find a product that has only one category, would not be a problem because a search is made by comparing the string that is sent as a search parameter with the category string that is saved in the database. The problem occurs when a product has two or more categories. At the time of making the comparison of string to string, it would never coincide.
The products can have as many categories as they need.
Any ideas?
if i am understanding your question correctly, you may want to store category as an array of strings and index each element in the array. you can then search products against a single or multiple categories.
for example, given the following documents:
doc 1
{
"name": "product1",
"category: ["abc"]
}
doc 2
{
"name": "product2",
"category: ["abc", "def"]
}
you can set up a search index similar to:
function (doc) {
if (doc.category) {
for (var i in doc.category) {
index("category", doc.category[i], {"store": true, "facet": true})
}
}
}
then you may run queries like such:
.../{dbname}/_design/{designdocname}/_search/{searchindexname}?q=category:abc
which would return both product1 and product2
or:
.../{dbname}/_design/{designdocname}/_search/{searchindexname}?q=category:abc+AND+category:def
which would return only product2.
additional info: https://developer.ibm.com/clouddataservices/cloudant-search/
You should store one or more categories in array format in Cloudant database in "category" parameter
{
"_id": "producto1",
"category: ["TV", "cameras", "computers"]
}
Then you should create a search index
function (doc) {
if (doc.category) {
for (var i in doc.category) {
index("category", doc.category[i], {"store": true, "facet": true})
}
}
}
Now you can query the documents from Cloudant Query
{
"selector": {
"_id": {
"$gt": 0
},
"category": {
"$all": [
"TV",
"cameras"
]
}
},
"fields": [
"_id",
"_rev"
],
"sort": [
{
"_id": "asc"
}
]
}
Or you can use
https://{url_database}/{name_database}/_design/{name_design}/_search/{name_search}?q=category:TV