mongo find selector causes unmatching results to be returns - javascript

userLink_titles = Entries.find({ _id:"bxSbMgszYxbCqDonF"})
returns:
docs: Object
CBqHrJvTE8xz7u2Rz: Object
_id: "CBqHrJvTE8xz7u2Rz"
author: "AHSwfYgeGmur9oHzu"
Q8m7PMbQr62E3A73f: Object
_id: "Q8m7PMbQr62E3A73f"
author: "AHSwfYgeGmur9oHzu"
bxSbMgszYxbCqDonF: Object
_id: "bxSbMgszYxbCqDonF"
author: "AHSwfYgeGmur9oHzu"
As you can see it returns the correct document but it also returns incorrect documents.
findOne: userLink_titles = Entries.findOne({ _id:"bxSbMgszYxbCqDonF"}) works as expected and only returns the correct document.
I would use the findOne except that the end intention is to make the _id selector an array such that find would return the documents for all documents that match the _id selectors in the array.
Bonus Points:
Let's say I wanted to retrieve the titles of a set of articles, where the references to those articles (the _ids of the Articles in their Articles collection) have been saved in a user collection.
So effectively I would retrieve the Article references from the user collection, and use those references to retrieve the titles of Articles from the Articles collection.
What would the code/pseudo code look like for that? Something like the following (I am assuming the below is a complete bastardization of some best practices for querying/retrieving records)
user_profile = Users.findOne({username : "Frank"});
user_saved_articles_ids = user_profile.findOne({saved_articles_ids});
userLinks = Articles.find({ _id:user_saved_articles_ids});
userLinksTitles = Articles.find({titles});

For those interested in the bonus question, you want MongoDB's $in operator.
If this will be used in a publish method on the server then you may want to check out publish-with-relations.
Something like the function below would be an efficient way to retrieve the information. It makes two calls to the database. Using the fields parameter prevents unnecessary data from being sent between the db and the webserver.
/**
* Titles of a User's saved articles
*
* #method getSavedTitles
* #param {String} username
* #return {Array} a list of article titles
*/
function getSavedTitles (username) {
var user,
ids,
linkedArticles,
titles;
user = Users.findOne({username: username},
{fields:{ _id:0, profile:1 }});
if (!user || !user.profile) {
throw new Meteor.Error(500, 'Missing user profile');
}
ids = user.profile.savedArticleIds;
if (!ids || !_.isArray(ids)) {
throw new Meteor.Error(500, 'Missing saved article ids');
}
linkedArticles = Articles.find({_id: {$in: ids}},
{fields:{ _id:0, title:1 }});
titles = _.pluck(linkedArticles.fetch(), "title");
return titles;
} // end getSavedTitles

If you want to inspect the results of a query in the console, use Entries.find(...).fetch()
Entries.find() returns a cursor, which is a construct used to render lists efficiently, so that only the entries that change need to be re-rendered. Returning cursors from helpers on which you use {{#each}} will lead to more responsive apps.

Related

How to read all nested collections of all users on firestore? [duplicate]

I thought I read that you can query subcollections with the new Firebase Firestore, but I don't see any examples. For example I have my Firestore setup in the following way:
Dances [collection]
danceName
Songs [collection]
songName
How would I be able to query "Find all dances where songName == 'X'"
Update 2019-05-07
Today we released collection group queries, and these allow you to query across subcollections.
So, for example in the web SDK:
db.collectionGroup('Songs')
.where('songName', '==', 'X')
.get()
This would match documents in any collection where the last part of the collection path is 'Songs'.
Your original question was about finding dances where songName == 'X', and this still isn't possible directly, however, for each Song that matched you can load its parent.
Original answer
This is a feature which does not yet exist. It's called a "collection group query" and would allow you query all songs regardless of which dance contained them. This is something we intend to support but don't have a concrete timeline on when it's coming.
The alternative structure at this point is to make songs a top-level collection and make which dance the song is a part of a property of the song.
UPDATE
Now Firestore supports array-contains
Having these documents
{danceName: 'Danca name 1', songName: ['Title1','Title2']}
{danceName: 'Danca name 2', songName: ['Title3']}
do it this way
collection("Dances")
.where("songName", "array-contains", "Title1")
.get()...
#Nelson.b.austin Since firestore does not have that yet, I suggest you to have a flat structure, meaning:
Dances = {
danceName: 'Dance name 1',
songName_Title1: true,
songName_Title2: true,
songName_Title3: false
}
Having it in that way, you can get it done:
var songTitle = 'Title1';
var dances = db.collection("Dances");
var query = dances.where("songName_"+songTitle, "==", true);
I hope this helps.
UPDATE 2019
Firestore have released Collection Group Queries. See Gil's answer above or the official Collection Group Query Documentation
Previous Answer
As stated by Gil Gilbert, it seems as if collection group queries is currently in the works. In the mean time it is probably better to use root level collections and just link between these collection using the document UID's.
For those who don't already know, Jeff Delaney has some incredible guides and resources for anyone working with Firebase (and Angular) on AngularFirebase.
Firestore NoSQL Relational Data Modeling - Here he breaks down the basics of NoSQL and Firestore DB structuring
Advanced Data Modeling With Firestore by Example - These are more advanced techniques to keep in the back of your mind. A great read for those wanting to take their Firestore skills to the next level
What if you store songs as an object instead of as a collection? Each dance as, with songs as a field: type Object (not a collection)
{
danceName: "My Dance",
songs: {
"aNameOfASong": true,
"aNameOfAnotherSong": true,
}
}
then you could query for all dances with aNameOfASong:
db.collection('Dances')
.where('songs.aNameOfASong', '==', true)
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, " => ", doc.data());
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
NEW UPDATE July 8, 2019:
db.collectionGroup('Songs')
.where('songName', isEqualTo:'X')
.get()
I have found a solution.
Please check this.
var museums = Firestore.instance.collectionGroup('Songs').where('songName', isEqualTo: "X");
museums.getDocuments().then((querySnapshot) {
setState(() {
songCounts= querySnapshot.documents.length.toString();
});
});
And then you can see Data, Rules, Indexes, Usage tabs in your cloud firestore from console.firebase.google.com.
Finally, you should set indexes in the indexes tab.
Fill in collection ID and some field value here.
Then Select the collection group option.
Enjoy it. Thanks
You can always search like this:-
this.key$ = new BehaviorSubject(null);
return this.key$.switchMap(key =>
this.angFirestore
.collection("dances").doc("danceName").collections("songs", ref =>
ref
.where("songName", "==", X)
)
.snapshotChanges()
.map(actions => {
if (actions.toString()) {
return actions.map(a => {
const data = a.payload.doc.data() as Dance;
const id = a.payload.doc.id;
return { id, ...data };
});
} else {
return false;
}
})
);
Query limitations
Cloud Firestore does not support the following types of queries:
Queries with range filters on different fields.
Single queries across multiple collections or subcollections. Each query runs against a single collection of documents. For more
information about how your data structure affects your queries, see
Choose a Data Structure.
Logical OR queries. In this case, you should create a separate query for each OR condition and merge the query results in your app.
Queries with a != clause. In this case, you should split the query into a greater-than query and a less-than query. For example, although
the query clause where("age", "!=", "30") is not supported, you can
get the same result set by combining two queries, one with the clause
where("age", "<", "30") and one with the clause where("age", ">", 30).
I'm working with Observables here and the AngularFire wrapper but here's how I managed to do that.
It's kind of crazy, I'm still learning about observables and I possibly overdid it. But it was a nice exercise.
Some explanation (not an RxJS expert):
songId$ is an observable that will emit ids
dance$ is an observable that reads that id and then gets only the first value.
it then queries the collectionGroup of all songs to find all instances of it.
Based on the instances it traverses to the parent Dances and get their ids.
Now that we have all the Dance ids we need to query them to get their data. But I wanted it to perform well so instead of querying one by one I batch them in buckets of 10 (the maximum angular will take for an in query.
We end up with N buckets and need to do N queries on firestore to get their values.
once we do the queries on firestore we still need to actually parse the data from that.
and finally we can merge all the query results to get a single array with all the Dances in it.
type Song = {id: string, name: string};
type Dance = {id: string, name: string, songs: Song[]};
const songId$: Observable<Song> = new Observable();
const dance$ = songId$.pipe(
take(1), // Only take 1 song name
switchMap( v =>
// Query across collectionGroup to get all instances.
this.db.collectionGroup('songs', ref =>
ref.where('id', '==', v.id)).get()
),
switchMap( v => {
// map the Song to the parent Dance, return the Dance ids
const obs: string[] = [];
v.docs.forEach(docRef => {
// We invoke parent twice to go from doc->collection->doc
obs.push(docRef.ref.parent.parent.id);
});
// Because we return an array here this one emit becomes N
return obs;
}),
// Firebase IN support up to 10 values so we partition the data to query the Dances
bufferCount(10),
mergeMap( v => { // query every partition in parallel
return this.db.collection('dances', ref => {
return ref.where( firebase.firestore.FieldPath.documentId(), 'in', v);
}).get();
}),
switchMap( v => {
// Almost there now just need to extract the data from the QuerySnapshots
const obs: Dance[] = [];
v.docs.forEach(docRef => {
obs.push({
...docRef.data(),
id: docRef.id
} as Dance);
});
return of(obs);
}),
// And finally we reduce the docs fetched into a single array.
reduce((acc, value) => acc.concat(value), []),
);
const parentDances = await dance$.toPromise();
I copy pasted my code and changed the variable names to yours, not sure if there are any errors, but it worked fine for me. Let me know if you find any errors or can suggest a better way to test it with maybe some mock firestore.
var songs = []
db.collection('Dances')
.where('songs.aNameOfASong', '==', true)
.get()
.then(function(querySnapshot) {
var songLength = querySnapshot.size
var i=0;
querySnapshot.forEach(function(doc) {
songs.push(doc.data())
i ++;
if(songLength===i){
console.log(songs
}
console.log(doc.id, " => ", doc.data());
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
It could be better to use a flat data structure.
The docs specify the pros and cons of different data structures on this page.
Specifically about the limitations of structures with sub-collections:
You can't easily delete subcollections, or perform compound queries across subcollections.
Contrasted with the purported advantages of a flat data structure:
Root-level collections offer the most flexibility and scalability, along with powerful querying within each collection.

How to match an integer from one collection with it's identical in another collection, creating a many to many relationship.

This is supposed to be a simple many to many relationships in meteor but I must be missing something because i cannot get it to work.
I have a Collection called reblog and in it is an array of integers called descovered see image
I have a second collection called posts which is a collection of posts, and these posts have an id. take a look at the second image
I want to create a many to many relationships between the posts and the reblog collection. i.e, I want to match the integer
descovered: 9
from the reblog collection, with:
id: 9
from the posts collection so that I can display only the posts matched from the reblog collection. This of course will allow me to display the title of the post and other attributes.
This is my js
Template.reblogging.helpers({
descovered() {
var id = FlowRouter.getParam('_id');
//fetch the reblog collection contents
var rebloged = reblog.find().fetch();
//log below is showing that the fetch is successful because i can see the objects fetched in console
console.log(rebloged);
//create the relationship between the posts collection and the reblog collection
var reblogger = posts.find({
id: {
$in: rebloged
}
}).fetch();
//nothing is showing with the log below, so something is going wrong with the line above?
console.log(reblogger);
return reblogger
}
});
I must be missing something because this seems a pretty straightforward thing but it's not woring
And my HTML is like this
<template name="reblogging">
{{#each descovered }}
<ul class="">
<li>
<h5 class="">{{title.rendered}}</h5>
</li>
</ul>
{{/each}}
</template>
You don't need to convert to strings and parse, you can use .map() directly on the cursor to create an array of descovered values. Also since you are using Blaze you can just return a cursor instead of an array. I suspect you also meant to use your FlowRouter _id parameter in your first .find(). If you didn't then there's no need to get that param in your helper.
Template.reblogging.helpers({
descovered() {
const id = FlowRouter.getParam('_id');
const reblogArr = reblog.find(id).map(el => { return el.descovered });
return posts.find({ id: { $in: reblogArr } });
}
);
As it turns out, the matching was accurate, however, the data from reblog collection needed to be treated with REGEX to get rid of everything else apart from the values I needed, then turn them into an array, this is the final code that worked. leaving it here, Hopefully, it will help someone in the future.
Template.reblogging.helpers({
descovered() {
var id = FlowRouter.getParam('_id');
//fetch the reblog collection contents
var rebloged = reblog.find().fetch();
//log below is showing that the fetch is successful because i can see the objects fetched in console
console.log(rebloged);
//turn it into a string so i can extract only the ids
var reblogString = JSON.stringify(rebloged).replace(/"(.*?)"/g, '').replace(/:/g, '').replace(/{/g, '').replace(/}/g, '').replace(/,,/g, ',').replace(/^\[,+/g, '').replace(/\]+$/g, '');
//after have extracted what i needed, i make it into an array
var reblogArr = reblogString.split(',').map(function(item) {
return parseInt(item, 10);
});
//create the relationship between the posts collection and the reblog collection
var reblogger = posts.find({
id: {
$in: reblogArr
}
}).fetch();
//nothing is showing with the log below, so something is going wrong with the line above?
console.log(reblogger);
return reblogger
}
});

Select all the fields in a mongoose schema

I want to obtain all the fields of a schema in mongoose. Now I am using the following code:
let Client = LisaClient.model('Client', ClientSchema)
let query = Client.findOne({ 'userclient': userclient })
query.select('clientname clientdocument client_id password userclient')
let result = yield query.exec()
But I want all the fields no matter if they are empty. As always, in advance thank you
I'm not sure if you want all fields in a SQL-like way, or if you want them all in a proper MongoDB way.
If you want them in the proper MongoDB way, then just remove the query.select line. That line is saying to only return the fields listed in it.
If you meant in a SQL-like way, MongoDB doesn't work like that. Each document only has the fields you put in when it was inserted. If when you inserted the document, you only gave it certain fields, that document will only have those fields, even if other documents in other collections have different fields.
To determine all available fields in the collection, you'd have to find all the documents, loop through them all and build an object with all the different keys you find.
If you need each document returned to always have the fields that you specify in your select, you'll just have to transform your object once it's returned.
const fields = ['clientname', 'clientdocument', 'client_id', 'password', 'userclient'];
let Client = LisaClient.model('Client', ClientSchema)
let query = Client.findOne({ 'userclient': userclient })
query.select(fields.join(' '))
let result = yield query.exec()
fields.forEach(field => result[field] = result[field]);
That forEach loop will set all the fields you want to either the value in the result (if it was there) or to undefined if it wasn't.
MongoDB is schemaless and does not have tables, each collection can have different types of items.Usually the objects are somehow related or have a common base type.
Retrive invidual records using
db.collectionName.findOne() or db.collectionName.find().pretty()
To get all key names you need to MapReduce
mapReduceKeys = db.runCommand({
"mapreduce": "collection_name",
"map": function() {
for (var key in this) {
emit(key, null);
}
},
"reduce": function(key, stuff) {
return null;
},
"out": "collection_name" + "_keys"
})
Then run distinct on the resulting collection so as to find all the keys
db[mapReduceKeys.result].distinct("_id") //["foo", "bar", "baz", "_id", ...]

How to search mongodb collection entry attributes

I have a collection in my mongo database by the name "user collection". The collection holds entries with one attribute by the name "DBinstaID".
{ "_id" : ObjectId("5595f6be3eaf90ae2d759cc6"), "DBinstaID" : "280430135" }
I am trying to determine whether a new entry has the same DBinstaID value, and if so increase the count of "idCount". The following code is used in my index.js routing file for node.js/express.
var collection = db.get('usercollection');
var checkDuplicate = collection.find({$where: function(){this.DBinstaID === instaID}});
console.log("DUPLICATE STATUS: " + checkDuplicate);
if(!(checkDuplicate)){
idCount++;
}
However, the checkDuplicate variable is simply set to [object Object] instead of the boolean true or false. How can I search the collection and return a boolean?
Thank you in advance for the help.
The collection.find() method is asynchronous, this means you can't get the results right away, and you need to handle anything dependant on these results directly in the callback.
I strongly advise you to have a good read of mongoosejs's docs (at least the code examples), and to take a look at basics of asynchronous programming in node.js (a google search away).
You can use collection.findOne() to get only one result :
var collection = db.get('usercollection');
collection.findOne({DBinstaID: instaID}, function(err, user) {
// don't forget to check for errors here, masked for clarity
console.log("DUPLICATE STATUS: ", user);
if(user) {
idCount++;
}
// rest of the code
});

Is it possible to retrieve a record from parse.com without knowing the objectId

See the sample code below - in this case, the objectId for the record I am trying to retrieve is known.
My question is, if I don't know the Parse.com objectId, how would I implement the code below?
var Artwork = Parse.Object.extend("Artwork");
var query = new Parse.Query(Artwork);
query.get(objectId, {
success: function(artwork) {
// The object was retrieved successfully.
// do something with it
},
error: function(object, error) {
// The object was not retrieved successfully.
// warn the user
}
});
Query.get() is used when you already know the Parse object id.
Otherwise, one can use query.find() to get objects based on the query parameters.
Sure, you can use Parse Query to search for objects based on their properties.
The thing that wasn't clear to me in the documentation is that once you get the object in the query, you would need to do:
With Query (can return multiple objects):
artwork[0].get('someField');
With 'first' or 'get':
artwork.get('someField');
You cannot do something like artwork.someField like I assumed you would

Categories

Resources