MongoDB return 6 random rows from collection - php

Hi I want to show random 6 rows from a collection. each row as a timestamp so I could use that but my question is how do I return only 6 rows from the collection and make it random
here is a sample of my collection - I use PHP
{
"age": "2",
"breed": "Bengal",
"dislikes": "Dislikes being patted by people",
"likes": "Like to purr and get headbutts. Sleeps on our bed, with Woody our dog, and also comes in for food at 6pm, loves Tin fish and is known to meow quite lo [...]",
"lost": true,
"pet_lost_date": NumberInt(1361366445),
"type": "cat"
}
I saw this
db.items.find().skip(randonNumberHere).limit(1); - MongoDB: Pulling multiple random documents from a collection
but I did not understand it, all i understand from that is the find() which finds everything skip() which skips a number of rows and limit() which is how many get returned.
However My question is more about getting all the lost pets and random them and only showing 6
public function lost_pets($no){
$collection = static::db()->ipet_mypet;
$pet = $collection->find(array('lost': true, '$where'=> function(){var randomNumber=Math.random(); return this.random>=randomNumber || this.random>randomNumber })).sort(array('pet_lost_date'=> 1)).limit(6);
}

You could use this:
db.collection.find({'lost': true, $where: function(){var randomNumber=Math.random(); return this.pet_lost_date>=randomNumber || this.pet_lost_date>randomNumber }}).next();
Find({'lost':true}) fetches the documents with field 'lost': true.
The $where clause returns a DBCursor, which points to a particular document. By calling "next()", we obtain the next document pointed by the cursor. So we fetch one random document at a time.

According to the way I see it logically, the easy way is to save records with a random number, then sort by that number when reading from the db.

Let say you have 20 records
you want to randomly fetch 5 records
We will generate a random skip value between 0 to 20 - limit value (5) = 15.
With this we are sure to return 5 records even if the random skip value starts from 15. We can also force the skip value to be zero if its becomes negative after we substract the random value from the total records
Example code:
$total_records = $collection->count(); <br>
$limit = 5; <br>
$skip = mt_rand(0, $total_records);<br>
$collection->find()->skip($skip < 0 ? 0 : $skip)->limit($limit);

Related

How to find Intersection of Two Collection in monogdb?

Let say, I have 2 collection
first one :-
db.product_main
{
_id:123121,
source_id:"B4456dde1",
title:"test Sample",
price: 250
quantity: 40
}
which consist approx ~10000 objects (Array) and unique field is source_id.
Second :-
db.product_id
{
"_id":58745633,
"product_id":"B4456dde1"
}
which consist of ~500 and only have field "product_id" which is equals to "source_id" of db.product_main
now, i want to intersect two collection so that i only find those which don't exist in db.product_id.
db.product_main.aggregate({any query})
Just use the lookup stage to find the products associated with the 'product_main' collection and then match for empty array (i.e. records where no product_id was found)
db.product_main.aggregate([
{
$lookup: {
from: "product_id",
localField: "source_id",
foreignField: "product_id",
as: "products_available"
}
},
{
$match: {
products_available: {
$size: 0
}
}
}
])
On WRITE operations using aggregate pipeline You can also directly offload statistics update by using $out command and store cached result in product_stats collection (for example).
Later in web/ui/api READ operations just use this cached collection. Of cause, You can create database query methods for cached and non-cached results.

How to check if a value is greater than within an array

I have the following SQL statement:
$query = "SELECT item, COUNT(*) as number FROM shop GROUP BY item";
This will give me the following result:
item number
item1 23
item2 15
item3 4
I want to use this to make menu items, so normally the menu would look:
item1
item2
item3
But I want to do a check if an item has less than 10 records, that I don't want to display this item.
So in this example, the menu would be like:
item1
item2
Any idea how to achieve this?
I would like to do this in PHP because I need all the items in the query but will only want to show them which are greater then 10 and need the other items later on.
If you want to do this in PHP then you can do like this
function filterArray($value){
return ($value.number > 10);
}
$filteredArray = array_filter($yourDBArray, 'filterArray');
foreach($filteredArray as $k => $v){
//your desired array
}
In terms of speed Mysql option is good as suggested above.
Just change your query from
SELECT item, COUNT(*) as number FROM shop GROUP BY item
to
SELECT item, COUNT(*) as number FROM shop GROUP BY item HAVING number>=10
As you really need to perform this in PHP you could use array_filter() which, using a closure, will remove items which number is less than 10:
$more_than_ten = array_filter($items, function ($i) { return $i['number'] >= 10; });
Doing it with SQL would be a better solution (about performances). In case you'd need it, you could use the HAVING clause (you can't perform a WHERE number >= 10):
SELECT
item,
COUNT(*) as number
FROM shop
GROUP BY item
HAVING number >= 10
I noticed php is tagged. For the sake of options, here's how I'd go about separating the unneeded data in php if you were to get it from the database as-is:
foreach ($data as $item) {
$num = (int) $item['number']; // force of habit
if ($num >= 10) {
// display it
}
}
I'd probably separate the data at the database step, but this works if it's the route you want to take.
There is two options to filter the data so only the rows with more then 10 will appear.
At the SQL query
__
SELECT item, COUNT(*) as number FROM shop GROUP BY item HAVING number > 9
This will cause you to recieve only the requested rows from the database
Filter with PHP - every time you want to print the menu or testing it out, just can the value of 'number' in the array reutrned from the query. You can also allocate new array and insert all the values that contains 'number' that bigger then 10.

Ranking based on users placement instead of score

I have a issue that I cannot wrap my head around.
I am using the Laravel Framework.
I am trying to make a ranking table based on placement (Meaning the user does not have any SCORE, they just have placements)
How I want it to work is the following way:
User A = Placement: 1
User B = Placement: 10
User B wins over User A, then User B gets placed as number 1 and User A gets placed as number 2, and then I want it to update all the other users accordingly.
I can't seem to find a reliable way of doing this.
I don't think this is a Laravel challenge but an SQL one. And it may be simple to solve: basically, you will ask for the actual position of the defeated person, if the position is greater than the winner, you do nothing, otherwise you will assign the position of the loser to the new winner and update the rest of the table with a +1 in the position column.
In code it would be something like this:
$winner_player = User::where('id', userA->id)->first();
$loser_player = User::where('id', userB->id)->first();
if($winner_player->position < $loser_player->position) {
//Update the rest of the users.
//We add 2 because we need space for the new winner and for
//the loser that is still above of the rest of the players.
DB::table('users')
->where('position', '>', $loser_player->position)
->update(DB::raw('position+2'));
//Set the winner with the actual position of the loser.
$winner_player->position = $loser_player->position;
$winner_player->save();
//Set the looser with the new position (+1 of his actual).
$loser_player->position = $loser_player->position + 1;
$loser_player->save();
}
UPDATED LOGIC
As Classified pointed out, it moves the rows around but doesn't do it correctly, so I'm updating the logic to make it work as it is supposed to, and it will be a little simpler too.
$winner_player = User::where('id', userA->id)->first();
$loser_player = User::where('id', userB->id)->first();
if($winner_player->position < $loser_player->position) {
//Set the winner with the actual position of the loser.
$winner_player->position = $loser_player->position;
//Update the users between the swap. There is no need to update
//the whole table, we only update the records between the swap.
DB::table('users')
->where([['position', '<', $winner_player->position],
['position', '>=', $loser_player->position]])
->update(DB::raw('position+1'));
//Save the value of the winner AFTER updating the positions
//between winner and loser.
$winner_player->save();
}

Redis zscore zadd uri wrong score

Im currently working on assigning groups to a special url, with their groupId as score.
create group-url:
$this->cache->redis->zadd("group_route",$groupId,$groupUrl);
search if it is a group-url, and get the group:
function isCostumUrl($groupUrl) {
$group = $this->cache->redis->zrank("group_route",$groupUrl);
if ($group) {
return $group;
} else {
return false;
}
}
Problem My problem is that somehow the result-groupid is wrong.
I am searching for katt, that has id 4, but it reply with 3 wich acually belongs to group-url fisk.
how can i acually make it return the right result?
The rank is not the same with the score. The rank is zero -0- based, so in the case above rank 3 is correct for group_route katt. For example you can have different scores of your items 2, 3, 4 and 5, but the rank (or index) will always be the same. Take a look at the Redis rank command
But zscore would work correct for you (you actually put zscore in title, but use zrank in example)

Find in mongodb (search in the first 30 records)

how do I make an appointment within 30 mongo documents?
Ex:
db.find (). count () = 100 documents //my base
I wanted
db.find ({$ {and 'user.gender': 'Male'}, {'in first 30 records "}) = 5 documents
it is not
db.find ({'user.gender': 'Male'}). limit (30) = 30 documents //I do not want it
You can use the $aggregate in mongodb
for eg
db.collName.aggregate([
{ $limit: 30 },
{ $match: { 'user.gender': 'Male'} }
])
If you want to sort the collection on basics of any field you can use the $sort in the aggregation pipeline.
Also please do remember the fact that "there is no guarantee that your documents are returned in any particular order by a query as long as you don't sort explicitly. Documents in a new collection are usually returned in insertion order, but various things can cause that order to change unexpectedly, so don't rely on it.
When you sort by _id field the items are returned by creation Date. So you can also sort by _id first and then limit your documents.
For eg:
db.collName.aggregate([
{ $sort: { _id: 1 } },
{ $limit: 30 },
{ $match: { 'user.gender': 'Male'} }
])
Explaination:
It will first sort your documents based on the _id field in documents and then limit your documents to 30 and then find a matchon the returned documents based on the query.

Categories