How can I get the last N entries from MongoDB in a ascending order in PHP?
this is the code im using right now the gets me the last 60 entries but it gives me in a descending form I need it in a ascending form.
<?php
header("Content-type: text/json");
require ($_SERVER['DOCUMENT_ROOT'] . '/grafic/mongodb_php/vendor/autoload.php');
$client = new MongoDB\Client;
$traficodb = $client->traficodb;
$trafico_total = $traficodb->trafico_total;
$filter = [];
$options = ['sort' => ['time_stamp' => -1], 'limit' => 60];
$show = $trafico_total->find($filter, $options);
foreach ($show as $collection) {
$json[]= [strtotime($collection["time_stamp"])*1000, (int)$collection["tx"], (int)$collection["rx"]];
}
echo json_encode($json);
?>
For example if I have rows with timestamp: 1,2,3,4,5,6,7,8,9. I want result shown as 5,6,7,8,9 and not as 9,8,7,6,5
If it's only a relatively small number of results which you are not "paging" ( i.e using limit and skip ) then the most efficient action would be to simply "reverse" the returned results after converting the MongoDB\Driver\Cursor as returned from MongoDB\Collection::find() to an "array".
This is what the Cursor->toArray() method of the driver does, along with array_reverse() as a standard PHP function.
$filter = [];
$options = ['sort' => ['time_stamp' => -1], 'limit' => 60];
$show = array_reverse($trafico_total->find($filter, $options)->toArray());
Then as you iterate the list the results are in ascending order, being the "reverse" of what the cursor returned them as.
Alternately you could use use aggregate()
$show = $tracfico_total->aggregate([
[ '$match' => [] ],
[ '$sort' => ['time_stamp' => -1 ] ],
# [ '$skip' => $itemsInPage ],
[ '$limit' => 60 ],
[ '$sort' => ['time_stamp' => 1 ] ]
]);
And you would typically $skip through previous results before applying your limit, then re-sort the final output. But it really does not add much to what the regular query already does, and the regular query probably does it more efficiently.
There generally is little point asking the database to do things that don't actually "reduce" the results to be returned, so you typically would not perform this on the server unless you had something else to do "on the server" with the results which reduced what was to be returned.
The other case would be typically for a "large resultset" which would be more suited to iterating a cursor. In that case the aggregation approach would be recommended as opposed to converting the cursor to an array "in memory" instead of simply iterating the results in the returned order.
Related
I have this PHP array:
$this->user_list = array( 0 => 'Not paid',1 => 'Not paid', 2 => 'Not paid', 7 => 'Waiting, 15 => 'Waiting', 10 => 'Cancelled' );
How can I simplify this array as the id numbers are different, but some of them have same status?
I tried it like this:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
but it doesn't work as expected.
Basically I want to achieve this:
echo $this->user_list[15] should give me Waiting, echo $this->user_list[10] should give me Cancelled, etc. So this is working in my first array very well, I am just thinking about grouping duplicate names there.
As mentioned by other contributors, there is no native support in the PHP grammar for your intended use case. As clearly stated in the PHP: Arrays documentation:
An array can be created using the array() language construct. It takes any number of comma-separated key => value pairs as arguments.
So basically each element in an array is a key => value pair, which means you cannot associate multiple keys to a single element.
This also explains why your first tentative didn't work:
$this->user_list = array( [0,1,2 => 'Not paid'],[7,15 => 'Waiting'],10 => 'Cancelled' );
If you don't specify a key for an element, PHP uses a progressive index (0, 1, ...). So basically in the example above, the first zero is not actually a key, but a value, and PHP binds it to the key = 0. Maybe it could be easier for you to understand how it works if you print a var_dump or print_r of $this->user_list. You would get something similar to the following structure (NOTE: I have simplified the structure to make it more clear):
[
0 => [
0 => 0
1 => 1
2 => "Not paid"
],
1 => [
0 => 7,
15 => "Waiting"
],
10 => "Cancelled"
]
So how do we resolve this problem? Well... actually there is no need to contort the structure by swapping keys with values as other contributors seem to suggest. Changing the structure might simplify your "data entry" work but might also create big issues in other parts of the program because who knows, maybe accessing the invoice data by "ID" is simply more efficient than by "status" ... or something.
Since PHP does not provide such a feature out of the box, I believe a better solution would be to develop our own function; a good starting point could be the one in the example below.
function explode_array($config, $sep = ',') {
$res = [];
foreach($config as $configKey => $value) {
// split key values
$keys = explode($sep, $configKey);
foreach($keys as $key) {
$res[$key] = $value;
}
}
return $res;
}
$config = [
'0,1,2' => 'Not paid',
'7,15' => 'Waiting',
'10' => 'Cancelled'
];
$myArr = explode_array($config);
print_r($myArr);
The idea is quite simple: since we cannot use an array as key we leverage the next best data type, that is a CSV string. Please note there is no error handling in the above code, so the first thing you may want to do is adding some validation code to the explode_array (or however you wish to name it) function.
you should use like this. if id number is invoice id or something else and other value is there status about it.
$arr = array(
'Not paid' => [0,1,2] ,
'Waiting' => [5,6],
'Cancelled' =>[8]
);
foreach($arr as $key => $val){
foreach($val as $keys => $vals){
echo "invoiceid ".$vals ." status ".$key;
echo"<br>";
}
}
// for only one status you can use like this
foreach($arr['Not paid'] as $key => $val){
echo $val;
echo"<br>";
}
just try to run this and check output.
PHP has no built-in function or structure for handling cases like this. I'd use a simple array value-cloning function to map your duplicates. Simply have one instance of each status, then map the aliases, and then run a function that clones them in. As follows:
// Status list:
$ulist = [ 0 => 'Not paid', 7 => 'Waiting', 10 => 'Cancelled' ];
// Alternative IDs list, mapped to above source IDs:
$aliases = [ 0 => [1,2], 7 => [15] ];
// Function to clone array values:
function clone_values(array &$arr, array $aliases)
{
foreach($aliases as $src => $tgts) {
foreach($tgts as $tgt) {
$arr[$tgt] = $arr[$src];
}
}
ksort($arr); // If the order matters
}
// Let's clone:
clone_values($ulist, $aliases);
This results in the following array:
array(6) {
[0] · string(8) "Not paid"
[1] · string(8) "Not paid"
[2] · string(8) "Not paid"
[7] · string(7) "Waiting"
[10] · string(9) "Cancelled"
[15] · string(7) "Waiting"
}
....which can be accessed as you expect, here $ulist[2] => Not paid, etc. If the use case is as simple as illustrated in the OP, I'd personally just spell it out as is. There's no dramatic complexity to it. However, if you have dozens of aliases, mapping and cloning begins to make sense.
As said in the comments, you can't have multiple keys with one value. The best way is to use the keyword => [ number, number, number...] construction.
//set a result array
$result = [];
//loop the original array
foreach ( $this->user_list as $number => $keyword ){
//if the keyword doesn't exist in the result, create one
if(!isset ( $result [ $keyword ] ) ) $result[ $keyword ] = [];
//add the number to the keyword-array
$result[ $keyword ] [] = $number;
}
Both of my arrays have over 500 000 elements.
I would like to return only those elements from multidimensional array that ARE NOT PRESENT in indexed array.
Here is what my multidimensional array looks like:
$new_codes = [
0 => [
'id' => 1,
'code' => 'code1',
... another values
],
1 => [
'id' => 2,
'code' => 'code2',
... another values
],
2 => [
'id' => 3,
'code' => 'code3',
... another values
]
];
Another array is just plain indexed array with code values:
$old_codes = [
'code1',
'code2',
];
For this limited example, after filtering, $new_codes should only have index 2 because that value doesn't exist in $old_codes array.
I've tried using the code bellow, but because the arrays are so huge, the operation takes so long that I thought that I somehow created infinite loop, but it seems that checking if the values for over 500000 elements exist in another array that also has over half a million elements takes very long time.
// option 1
$new = array_filter($new_codes, function ($var) use ($old_codes) {
return !in_array($var['code'], $old_codes);
});
// option 2
$filtered = [];
foreach($new_codes as $code) {
if(in_array($code['code']){
continue;
}
$filtered[] = $code;
}
Any suggestions for more optimized solutions are welcome.
Thanks in advance.
Reading this question I realized that using isset() is much better option for handling such a large amount of elements so I did this:
// switch array keys and values
$old_array = array_flip($old_array);
foreach($new_codes as $code) {
if(isset($old_array[$code['code']])){
continue;
}
$filtered[] = $code;
}
Doing this reduced the time to just few seconds.
I am trying to create a PHP application that lets me uploading data to a MongoDB collection. For that I have "installed" the PHP driver with no problems.
However, I cannot find anyway -neither in the PHP guide- how could I update an element in an array of arrays.
Collection structure
As you may see, _unidades is an array of arrays. Each of those arrays contains an ID, a string and another array. In my case, which will be chosen depends on previous param -it has to match with element 1 of one of them.
Once I have selected that structure, I want to insert a new array into its array of arrays (position 2).
Regarding code, I tried the following:
$bulk = new MongoDB\Driver\BulkWrite();
$bulk->update(
[
'_isbn' => (int) $_POST["isbn"],
'_topics' =>
[
'0' => (int) $_POST["topic"]
]
],
[
'$set' => [
'1' => array($_POST["sentence"], $_POST["question"])
]
]
);
$writeConcern = new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY, 100);
$resultado = $manager->executeBulkWrite('libreee.faq', $bulk, $writeConcern);
However as you can see I am not capable to determine at least that it doesn't have to be an specific array (7th line).
Once said that I look forward to receiving your help.
Thanks a lot in advance.
Regards,
Ciconia.
I have a query that populates an array from the database. In some cases, this query returns a great amount of data, (let's say for purpose of an example, 100.000 records). Each row of the database has at least 6 or 7 columns.
$results = [
['id' => 1, 'name' => 'name', 'status' => true, 'date' => '10-01-2012'],
['id' => 2, 'name' => 'name 2', 'status' => false 'date' => '10-01-2013'],
...
]
I need to perform a substitution of some of the data inside the $results array, based on another one that give me some information about how i would change the values in the rows.
$model = [
'status' => ['function' => 'formatStatus', params => ['status']],
'date' => ['function' => 'formatDate', params => ['date']]
]
Now that i have all the data and what do i do with it i have the following routine.
foreach ($results as &$itemResult) {
$oldValues = $itemResult;
foreach ($itemResult as $attribute => &$value) {
if (isset($model[$attribute]['function'])) {
$function = $model[$attribute]['function'];
$params = $model[$attribute]['params'];
$paramsSize = count($params);
for ($i = 0; $i < $paramsSize; $i++) {
$newParams[] = $oldValues[$params[$i]];
}
$itemResult[$attribute] = call_user_func_array([$this, $function], $newParams);
$newParams = null;
}
}
}
So, for each attribute for each row of my data array, i run check for the existence of a function and params information. When the attribute in question needs to be replaced, i call the function via call_user_func_array and replace the value with the function return value.
Also notice that i am replacing the current array, not creating another, by passing the reference &$itemResult inside the loop, so in the end, i have the same array from the beginning but with all columns that needed to be replaced with its new values.
The thing is, for little arrays, this method is quite good. But for big ones, it becomes a pain.
Could you guys provide me some alternative to the problem?
Should i use another data structure instead of the PHP array?
I have a collection, from which i get particular type of users using $query
Then I need sort them according to user_id ascending and limit them to 2000
From these I need the max user_id, so I sort them in descending order and limit to 1.
But this second sort forgets the limit of 2000 and sorts over over the entire cursor from find().
Any work-around?
$cursor = $collection ->find($query) // too many entries
->sort(array('user_id'=>1)) // go ascending
->limit(2000) // got our limited qouta
->sort(array('user_id'=>-1)) // go descending to get max
->limit(1); // the one with max(user_id)
Your cannot do a sort and then a limit and then a sort. The Cursor object is exactly that and it will not run the query until you iterate to the first result via getNext() which is either run manually or within a foreach loop not only that but sort is just a property of the object as such making two sorts just overwrites the property.
The best way to achieve what your looking for is:
$doc = $collection->find($query)->sort(array('user_id' => 1))
->skip(1999)->limit(1)->getNext();
That will always pick the highest user_id (which occurs at the end in this sort) of the group, which will give off the same results as doing two sorts.
How about using skip():
$cursor = $collection ->find($query)
->sort(array('user_id'=>1))
->limit(2000)
->skip(1999);
What is the reason behind sort-limit-sort-limit approach?
Can't you just do
$cursor = $collection ->find($query)
->sort(array('user_id'=>-1))
->limit(1);
EDIT: Also, only the last sort applied to the cursor has an effect. (This line is present in pymongo docs here, which makes sense.)
I am using the following code in php5 and mongodb latest build:
$doc = $collection->find($query)->sort(array('user_id' => 1))
->skip(1999)->limit(1)->getNext();
It stops working when I use ->skip(1999)->limit(1) after sort()
The cursor $doc does give me values . I tried braking it down into this:
$doc = $collection->find($query)->sort(array('user_id' => 1))
$doc = $doc->skip(1999)->limit(1);
That worked. May be you should try that in new versions.
Answer by Sammaye is correct.
You can still achieve the way you wanted. You can use aggregation framework, as it executes one stage after other stage and so on.
$doc = $collection->aggregate(array(
array(
'$match' => $query,
),
array(
'$sort' => array(
'user_id'=> 1
),
),
array(
'$limit' => 2000
),
array(
'$sort' => array(
'user_id'=> -1
),
),
array(
'$limit' => 1
),
));