Retrieve random key from firebase database - javascript

I face the problem that I have a list of keys inside my firebase database and I want to retrieve one random key out of it, without downloading all keys and selecting one in my own app. Downloading all keys with for example .once() is a big no go, especialy because I may up end with 10.000+ keys in the future and I dont want my firebase bills to explode, Im still a student with no budget at all.
I already researched this topic and found following answer for my question.
The problem with this is, that I can not use limitToFirst() and also limitToLast() in the same query, else I get following error:
[Unhandled promise rejection: Error: Query.limitToLast: Limit was
already set (by another call to limit, limitToFirst, or limitToLast).]
Also other people face the same problem like me and I generaly see that this question always gets much views but rather poor answers which are, like apperently in the case I mentioned earlier outdated, or just not for usage in Javascript and React-Native.
Additional informations: I have a counter for my keys so I know at every time how many keys are stored inside my database. Following picture shows my database entirely:
This is the part of my code which shows the mentioned error:
const numberOfUsers = await firebase.database().ref("usercounter").once("value").then(snapshot => {
return(snapshot.val()); //retrieve numbers of users from "usercounter"
});
const randomIndex = Math.floor(Math.random() * numberOfUsers) + 1; //"generating random number, excluding 0"
var ref = firebase.database().ref('users'); //ref to my users keys
await ref.limitToFirst(randomIndex).limitToLast(1).once('value').then(DataSnapshot =>
{
//This is the query that does not work at all.
console.log(DataSnapshot.val())
});
Incase there is a way to overcome that problem, please let me know.

I would solve that problem this way:
Create for each list element a value with the field name id or some other you want. It should be a simple integer. Idealy incrementaly growing with the list. You could use the counter to increase the value each time you add an element.
When trying to get a random element from the list first get a random integer between 0 and the total count of all elements. Then use this query to get a single random element:
firebase.database().ref("users").orderByChild("id").startAt(randomNumber).limitToFirst(1);
Even if you don't hit the id with the randomNumber you would the neareas next element. This should be able to scale to any size.
The most diffictult part would be to optain the id incrementaly without repeating them. I would use for that an cloud function that does it when an item is added. Even a combination with the counter would be interesting.

Related

Is there a way to count the channels inside of a category in discord.js?

I'm trying to write a bot that will tell me the number of channels in a specific category.
if (strMessage === "!outline") {
var outlineSize;
message.reply("There are " + outlineSize + " outlines currently hidden away. Type '?outline' to learn more about them.");
}
I'm trying to set outlineSize to the number of channels in the category (the category is always the same, hence the specific variable name). For example, if there are two channels I want outlineSize to equal 2.
I know that it has something to do with CategoryChannelChildManager in discord.js v14. However, I've found nothing that's been able to teach me how to use it. To be fair, I'm new to programming and JavaScript which is where I expect the main part of the problem lies.
Also note: There are a few similar questions on this site that pertain to this, but due to the always-changing nature of Discord's API I don't believe they've been accurate for over a year now and give me nothing but errors when I try them.
You can fetch the category channel by its ID and grab its children property. As you've mentioned it's a CategoryChannelChildManager so you will need to access its cache that returns a Collection. And finally, Collections have a size property that returns the number of items in the given collection. That's what you'll need:
let CATEGORY_ID = '813845582854001392';
let category = await client.channels.fetch(CATEGORY_ID);
let outlineSize = category.children.cache.size;
message.reply(`There are ${outlineSize} outlines currently hidden away. Type '?outline' to learn more about them.`);

How to know how many items a Firestore query will return while implementing pagination

Firestore has this guide on how to paginate a query:
Firestore - Paginate data with query cursors
They show the following example:
Paginate a query
Paginate queries by combining query cursors with the limit() method. For example, use the last document in a batch as the start of a cursor for the next batch.
var first = db.collection("cities")
.orderBy("population")
.limit(25);
return first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible)
.limit(25);
});
QUESTION
I get the example, but how can I know how many items (in total, without the limit restriction) that query will return? I'll need that to calculate the number of pages and control the pagination component, won't I?
I can't simply display next and back buttons without knowing the limit.
How is it supposed to be done? Am I missing something?
You can't know the size of the result set in advance. You have to page through all the results to get the total size. This is similar to not being able to know the size of a collection without also recording that yourself somewhere else - it's just not scalable to provide this information, in the way that Cloud Firestore needs to scale.
this is not possible, the iterator cannot know how many documents it contains, as they are fetched via a gRPC stream.
But there is a workaround... but you have to make a few stuff:
1) write a counter in a firebase doc, which you increment or decrement everything you make a transaction
2) store the count in a field of your new entry, like position 10 or something.
Then you create an index on that field (position DESC).
This way you can do a skip+limit with a where("position", "<", N).orderBy("position", DESC)
It's complex but it does the trick

Record answer randomization in Qualtrics using javascript

I am making a survey in Qualtrics. This survey has a repeating question with six answer choices. The six choices are randomized (in the standard way, no javascript). The question is being repeated using loop&merge, which works great because it's the same question structure over and over (36 times), but I can use the field function to adjust the question and answers for every iteration.
However, one problem I am running into is that Qualtrics does not (as standard) support the recording of the randomization data in the results - i.e. how it has randomized the six answer choices in each iteration. When I use the 'Export Randomized Viewing Order data' function when downloading results, it only shows the answer order of the last time it asked the question. So it seems that this is a value that gets overwritten after each iteration.
So now I'm looking to record the answer order for each iteration through javascript. However, I haven't found a function that gives the order answer (after randomization). I have consulted the Qualtrics javascript API and found some functions that seem promising, such as getChoices (), but when I try this all I get back is the order of answers without randomization (i.e. just 1,2,3,4,5,6).
Does anyone know a way to record the randomized choice order for each iteration, using javascript or otherwise?
I found a different way to record the loop and merge randomization order.
Create an embedded data field in survey flow. Here we will record the randomization order. I will call the field rand_order.
Add a loop and merge field with a unique number to identify each loop (e.g. 1, 2, 3, 4, 5, ..., n).
Then add the next javascript to any page of the looped block.
//*Place Your Javascript Below This Line*/
var questionText = "${lm://Field/1}"; // "${lm://Field/1}" will actually evaluate to
//whatever is Field 1 in the current Loop & Merge loop.
// You can do this with embedded data too, as seen in the next line
var order = "${e://Field/rand_order}" + "|" + questionText; // gets the value of the embedded data
// field "rand_order", concatenates the current loop's identifier to it,
//and stores that as a variable
Qualtrics.SurveyEngine.setEmbeddedData('rand_order', order); // updates the
//embeddeddata field "rand_order" to be our order variable, which has the current loop's
//identifier attached, effectively constructing a string of numbers representing the order
You will get a column with the name "rand_order" filled with "1|5|23|2...|n". You can change the separator to make more compatible with whatever script you are using to manipulate data.
Qualtrics already records this information for you. It's just a matter of explicitly asking for it when you download your data. Number 5 on this page has more info, but I'll recount the important bits:
In the “Data & Analysis” tab, click “Export & Import” and then “Export Data”.
In the “Download Data Table” window click “More Options”.
Check the box for “Export viewing order data for randomized surveys”.
I think the thing here is to look at the order of choices in the DOM. Qualtrics provides the getChoiceContainer() method to get the div containing the choices. Here's a snippet I wrote and minimally tested:
//get the div containing the choices, then get all input child elements of that div
var choices = this.getChoiceContainer().getElementsByTagName("input");
//initialize an array for the IDs of the choices
var choiceIDs = []
//add the ID of each choice to the array
for (var i=0; i < choices.length; i++) {
choiceIDs.push(choices[i].id);
}
//get the current choice order from embedded data and add this loop to it.
//Add a | to distinguish between loops.
var choiceOrder = "${e://field/choiceorder}" + choiceIDs.toString() + "|";
//set the embedded data with the new value
Qualtrics.SurveyEngine.setEmbeddedData("choiceorder", choiceOrder);
A couple of notes/caveats:
I only tested this on a basic multiple choice question with radio buttons. It may need to be adjusted for different question types.
I also just got the IDs of the question choices. You could probably modify it pretty easily to get other information, like the label of the choice, or the numeric value it corresponds to.

Can't delete items from Mongo Database in Meteorjs

I have created an object in Meteorjs to hold the database info for N numbers of dice rolls, and then the dice are displayed using handlebars #each iterator. Here is some of my code:
The global Mongo collection:
Items = new Meteor.Collection('items');</code>
When I roll the dice, this is what happens to the collection when a button is clicked:
//Don't want to bore you with all code, so here's just important parts...
var randomNumber=Math.floor(Math.random() * numSides) +1);
var numDice = 6;// It's really a variable passed in, but for here it's 6.
for (var i = 0; i < numDice; i++) {
Items.insert(item: randomNumber)
};
And then they display the info as dice because it displays numbers that I have CSS'd to look like Dice. But I'm straying here... Anyway, the dice rolling is awesome, but I want to clear the dice when I roll again. right now, they just keep adding up. And when I try to use any method to delete Mongo DB Items stuff, it crashes my app. Since I'm not sure how to debug very well yet in a browser, I need some help, and I'mm going to ask it here...
Now, my main problem is, when the dice are rolled again, I want to purge the database and start again. I am new to JavaScript and Meteor, and come from Java && Ruby land, so any suggestions are greatly appreciated.
I've tried
Items.removeIndexes(),
Items.purge(),
Items.remove({})
They all just freeze my app, and the numbers I displayed in the #each iterator are still there. I thought it would delete the stuff, and push the changes... No??? Please help.
The code is on GitHub # http://www.github.com/rabbitfighter81/DMware/
You should use the remove method, but if you call it from the client, you can only remove one document per call, and the selector must refer to the documents _id field. So, here's an example to remove all the documents in a collection the client has:
TheCollection.find().forEach(function(doc){
TheCollection.remove(doc._id)
})

Compound Query JS SDK paRse.com

I have one class Messages with 3 principal fields:
id FromUser ToUser
I do have a query where the To = Value field and the From field is not repeated. I mean, get all FROMUSER who sent me a message.
Any Idea?
Thanks!
As #Fosco says, "group by" or "select distinct" are not supported yet in Parse.com.
Moreover keep in mind the restriction on the selection limit (max 1000 results for query) and timeout request call ( 3 seconds in the before save events, 7/10 seconds in the custom functions ). For the "count" selection, the restriction is the timeout request call.
I'm working on Parse.com too, and i've changed a lot the structure of my db model, often adding some inconsistent columns in several classes, keeping them carefully updated for each necessary query.
For cases like yours, i suggest to make a custom function, that keep in input two parameter ( we can say, "myLimit" and "myOffset" ) for the lazy loading, then select the slices, and programmatically try to filter the resulting array item list (with a simple search using for..loop, or using some utility of UnderscoreJS). Start with small slices ( eg: 200-300 records maximum for selection ) until the last selection returns zero results ( end reached). You could count all items before start all of this, but the timeout limitation could cause you problems. If this not works as expected try to make the same, client side.
You could also make a different approach, so creating another table/class, and for each new message, adding the FromUser in that table ONLY if it doesn't already exist, for that specified ToUser.
Hope it helps

Categories

Resources