Find documents through embedded documents in MongoDB using an array of strings? - arrays

I am currently practicing MongoDB by building a site powered by it. The db has 5 collections and uses cross-references, either through objectid references or through a hybrid objectid-embedded document system. Mostly things have went very smoothly.
Now, I am trying to build an advanced search function. One field lets you insert multiple strings, separated by commas. In the backend, these will be separated into an array. The objects that are being searched all have an array field which normally contains one or more embedded documents. All of these embedded documents have an objectid reference and a "title" property. This query array I mentioned lets users search for multiple of this title entries, and I want to return all objects that contains the embedded documents with "title" properties all matching the array, so if you search for "X,Y" and there are a document with two embedded documents with the title properties "X" respective "Y" they will be returned, whereas if only of those embedded documents is present, that document will be ignored. The document structure looks like this:
{
"_id":"5dfcd95fe9d81c32437ba3fb",
"usedBy":[
{
"_id":"5ddaf35f51528c0881e84507",
"title":"X"
},
{
"_id":"5e00b2c04facc56b40930eac",
"title":"Y"
}
],
//more properties follow...
}
So far however, without success. I have tried a few methods, like $in and $elemMatch, but they don't seem to do what I want to have them do. Which method would you suggest? Right now, I tried to converted the array of strings to an array of objects, which in turn are passed to a filter object for the "find"-method:
if(usedBy){
usedBy = usedBy.split("%5C%2C%2");
for(var i=0; i<usedBy.length; i++){
usedBy[i] = {title: usedBy[i]};
}
filter.usedBy = usedBy;
}
Thanks.

You need to filter by subfield usedBy.title
var titles = usedBy.split(",");
db.collection.find({"usedBy.title":{$in: titles}});

Related

How can I filter large amount of JSON in OpenRefine?

I'm using OpenRefine to pull in information on publisher policies using the Sherpa Romeo API (Sherpa Romeo is a site that aggregates publisher policies). I've got that.
Now I need to parse the returned JSON so that those with certain pieces of information remain. The results I'm interested in need to include the following:
'any_website',
'any_repository',
'institutional_repository',
'non_commercial_institutional_repository',
'non_commercial_repository'
These pieces on information all fall under an array called "permitted_oa". For some reason, I can't even work out how to just pull out that array. I've tried writing grel expressions such as
value.parseJson().items.permitted_oa
but it never reutrns anything.
I wish I could share the JSON but it's too big.
I can see a couple of issues here.
Firstly the Sherpa API response items is an array (i.e. a list of things). When you have an array in the JSON, you either have to select a particular item from the array, or you have to explicitly work through the list of things in the array (aka iterate across the array) in your GREL. If you've previously worked with arrays in GREL you'll be familiar with this, but if you haven't
value.parseJson().items[0] -> first item in the array
value.parseJson().items[1] -> second item in the array
value.parseJson().items[2] -> third item in the array etc. etc.
If you know there is only ever going to be a single item in the array then you can safely use value.parseJson().items[0]
However, if you don't know how many items will be in the array and you are interested in them all, you will have to iterate over the array using a GREL control such as "forEach":
forEach(value.parseJson().items, v, v)
is a way of iterating over the array - each time the GREL finds an item in the array, it will assign it to a variable "v" and then you can do a further operation on that value using "v" as you would usually use "value" (see https://docs.openrefine.org/manual/grel#foreache1-v-e2 for an example of using forEach on an array)
Another possibility is to use join on the array. This will join all the things in an array into a string.
value.parseJson().items.join("|")
It looks like the Sherpa JSON uses Arrays liberally so you may find more arrays you have to deal with to get to the values you want.
Secondly, in the JSON you pasted "oa_permitted" isn't directly in the "item" but in another array called "publisher_policy" - so you'll need to navigate that as well. So:
value.parseJson().items[0].publisher_policy[0].permitted_oa[0]
would get you the first permitted_oa object in the first publisher_policy in the first item in the items array. If you wanted to (for example) get a list of locations from the JSON you have pasted you could use:
value.parseJson().items[0].publisher_policy[0].permitted_oa[0].location.location.join("|")
Which will give you a pipe ("|") separated list of locations based on the assumption there is only a single item, single publisher_policy and singe permitted_oa - which is true in the case of the JSON you've supplied here (but might not always be true)

How to store an inclusion/exclusion list in Mongo without storing every single item to represent a selection of "All"

My app has geofencing functionality. In the UI there is a table which lets the user add a country to either the "blocked" or the "allowed" list. The overwhelming majority of objects in the collection do not need geofencing and thus by default all countries should be in the allowed list for a new object.
How do I represent the 3 possible states (all, some, none) in Mongo? The last two are obvious - an array containing either some 2 letter country codes, or none. But what about the first case? I feel like it would be a massive waste of bytes to store however many (200+) country codes for each object.
The two alternatives I can come up with are:
Using a value of object.allowed_countries = [ 1 ] which also doesn't scream right to me.
Or storing an additional property to represent the global "all".
object: {
geofencing: {
enabled: true | false,
blocked_countries: [ ]
}
}
Is there anything smarter I'm not thinking of?
Thank you in advance!

FireStore and maps/arrays, document-list to array in Kotlin

I've finally started to understand a lot of info regarding FireStore, but I'm wondering if I can get some assistance.
If I had a setup similar to or like this:
          races
                Android
                      name: Android
                      size: medium
                       stats          <---- this is the map
                                str: 10
                                sex: 12.... (more values)
How would I parse this? I am looking to make specific TextViews apply values found in the database so that I can simply update the database and my app will populate those values so that hard coding and code updating won't be nearly as troublesome in the future.
I currently use something like this:
val androidRef = db.collection("races").document("Android")
androidRef.get().addOnSuccessListener { document ->
if (document != null) {
oneOfTheTextViews.text = document.getString("str")
} else {
}
The issue is currently I can only seem to access from collection (races) / document (android) / then a single field (I have "str" set as a single field, not part of a map or array)
What would the best practice be to do this? Should I not nest them at all? And if I can reference said nesting/mapping/array, what functions need to be called? (To be clear, I am not asking only whether or not it is possible - the reference guides and documents allude to such - but what property/class/method/etc needs to be called in order to access only one of those values or point to one of those values?).
Second question: Is there a way to get a list of document names? If I have several races, and simply want to make a spinner or recycler view based on document names as part of a collection, can I read that to the app?
What would the best practice be to do this?
If you want to get the value of your str property which is nested within your stats map, please change the following line of code:
oneOfTheTextViews.text = document.getString("str")
to
oneOfTheTextViews.text = document.getString("stats.str")
If your str property is a number and not a String, then instead of the above line of code please use this one:
oneOfTheTextViews.text = document.getLong("stats.str")
Should I not nest them at all?
No, you can nest as many properties as you want within a Map.
Is there a way to get a list of document names?
Yes, simply iterate the collection and get the document ids using getId() function.

In Meteor Collections, use an array-field or another collection?

The question
In short, my question is: when an array in a document is changed, will the users receive the new array, or just the changes?
If that question is unclear, I've described my problem below.
The problem
I have a collection whose documents contain an array field two users will push values to. A document in this collection kind of looks like this:
var document = {
userId1: "...user id...", // The id of the first of the two users.
userId2: "...user id...", // The id of the second of the two users.
data: [] // The field the two users will push values to.
}
data will from the beginning be empty, and the users will then take turns pushing values to it.
When one of the user pushes some value to data, the server will send the changes to the second user. Will the second user receive the entire data-array, or just the changes (the pushed value)? I'm a little bit worried that the second user will receive the entire data-array, even though it's just a single value that's been pushed to it, and if data contains many values, I fear this will become a bottleneck.
Is this the case? If it is, using another collection for storing the values will solve it, right? Something like this:
var document = {
id: "...unique id...",
userId1: "...user id...", // The id of the first of the two users.
userId2: "...user id..." // The id of the second of the two users.
}
var documentData = {
idReference: "...the unique id in the document above...",
value: "...a value..."
}
Instead of pushing the values into an array in document, insert them into a collection containing documentData. This (I know) won't have the downside I fear the first solution has (but I rather use the first solution if it doesn't have the downside).
As per https://github.com/meteor/meteor/blob/master/packages/livedata/DDP.md
changed (server -> client):
collection: string (collection name)
id: string (document ID)
fields: optional object with EJSON values
cleared: optional array of strings (field names to delete)
Users will receive the new array. To only send "diffs," use a collection of {userId: userId, value: value} documents.
I inspected what was sent as commented by user728291, and it seems like the entire array-field is sent, and not just the pushed value. I don't know if this always is the case (I just tested with an array containing few and small values; if it contains many or big values Meteor maybe try to do some optimization I couldn't see in my tiny test), but I'll go with the solution using another collection instead of the array-field.

Find a document by searching through numerically indexed arrays for a value

I have a collection with records that look like this
{
"_id":{
"$oid":"4e3923963b123b59b73bde67"
},
"ident":"terrainHome",
"columns":[
[
"4e3fbe57dccd1a0cc47509ab",
"4e3fbe57dccd1a0cc47509ac"
],
[
]
]
}
each document can have two or three columns,
each column is an array of blocks, which are stored in a different collection,
I want a query that will return the ident for the document that contains a block.
I tried
db.things.find({ columns[0] : "4e3fbe57dccd1a0cc47509ac" });
but this didn't work
I'll keep trying. :)
You are mixing types here (ObjectId != String). You should always keep things as ObjectId not sometimes as strings, as you have in the array. This is probably not the root of your problem, but could be problematic later.
In you example you can do this:
db.things.find({ "columns.0" : "4e3fbe57dccd1a0cc47509ac" });
Generally arrays of arrays can be challenging to query on when they are more structured (like embedded docs).
If you don't care about WHERE the match is, try
db.things.find({'columns' : $in : ["4e3fbe57dccd1a0cc47509ac"] });
This works
db.things.find({"$where":"typeof(this.columns[0]) != \"undefined\" && this.columns[0].indexOf(\"4e48ed8245333bd40d000010\") != -1"});
I'll accept my own answer in a couple of days or so, unless someone can suggest a simpler solution.
Also here's a screen grab of the mongo console of the two suggested solutions that didn't work, (thanks though for taking the time), plus the complicated solution I've found that works. The screen grab demos that there are documents in the collection, the two suggested find commands, and the $where javascript solution showing that this works for first item in the array, second item in the array and returns no records for a non matching id.
I've tried dozens of variations of the suggested solutions, but they all turn up blank results.

Resources