How to sort an inner array in a document? - php

I'm doing "inserts" by $push in an array inside my document, and inside it there is a field date that I want to sort it when I use the find(), but not sorting the "_id". How do I sort by date (ordens.dtOrdem)?
<?php
$mongo = (new MongoDB\Client('mongodb://localhost:27017'))->Carteira;
$collection = $mongo->ativo;
/*First way that I've tried*/
$result1 = $collection->find([].['ordens' => ['sort' => ['dtOrdem' => -1]]]);
/*Second way that I've tried*/
$result2 = $collection->find([],['ordens' => ['each' => ['ordens' => ['sort' => ['dtOrdem' = -1]]]]]);
?>
The field 'ordens.dtOrdem' is no been sorted in descending.

You cannot sort an array during a find(). You have two options:
You can specify a $sort operation during your $push to ensure that your elements are added to the array in sorted order.
You can using aggregation to sort the array on retrieval.
Solutions might look something like the following (some modification may be necessary):
// Sorting on push.
$collection->updateOne(
[...],
[ '$push' => [
'ordens' => [
// You MUST include the $each operator here or this will not work.
'$each' => [
[ 'dtOrdem' => 5 ]
],
// Where the magic happens, sorts in descending order.
'$sort'=> [ 'dtOrdem' => -1 ]
]
]]
);
// Sorting on retrieval.
$collection->aggregate([
[ '$unwind' => '$ordens' ],
[ '$sort' => [
'$ordens.dtOrdem' => -1
]],
[ '$group' => [
'_id' => '$_id',
'ordens' => [
'$push' => '$ordens'
]
]]
]);

Related

PHP MongoDB aggregate $match and $group and $addToSet

Having this document structure in MongoDB :
{
"_id":<MongoDBID>,
"chatUser1ID": 2,
"chatUser2ID": 3
}
Now i want to get all chat partners from Mongo where the Chat Partner with the ID 2 is included in either "chatUser1" or "chatUser2". For that i want to use the $match and $group function.
$chatUserID = $_POST["chatUserID"]; // 2 in my example
$chatCursor = $chatCollection->aggregate([
[
'$match' =>
[
'$or' =>
[
["chatUser1ID" => $chatUserID],
["chatUser2ID" => $chatUserID]
]
]
]
,[
'$group' =>
[
'_id' => 0,
'chatUsers' => ['$addToSet' => '$chatUser1ID'],
'chatUsers' => ['$addToSet' => '$chatUser2ID'],
'chatUsers1' => ['$addToSet' => '$chatUser1ID'],
'chatUsers2' => ['$addToSet' => '$chatUser2ID'],
]
]
]);
'chatUsers1' => ['$addToSet' => '$chatUser1ID'],
This is putting all the ID's of the field chatUser1ID in the chatUsers1 set and
'chatUsers2' => ['$addToSet' => '$chatUser2ID']
is putting all all the ID's of the field chatUser2ID in the chatUsers2 set where the $chatUserID is either in the chatUser1ID or chatUser2ID field of the document.
After that i want want to get the unique ID's
$chatUserIDs = array();
foreach ($chatCursor as $counter => $document) {
$bson = MongoDB\BSON\fromPHP($document);
$value = MongoDB\BSON\toJSON($bson);
$value = json_decode($value, true);
$chatUserIDs = array_unique(array_merge($value['chatUsers1'], $value['chatUsers2']));
}
unset($chatUserIDs[array_search($chatUserID, $chatUserIDs)]);
array_unshift($chatUserIDs);
So basically it's working but i want the solution where i get a list of unique ID's right away from the database.
Originally i thought the lines
'chatUsers' => ['$addToSet' => '$chatUser1ID'],
'chatUsers' => ['$addToSet' => '$chatUser2ID'],
would add the ID's to the set chatUsers but unfortunately the set is overwritten in the second line. Is there a way to "append" the ID's to the set instead of overwrite them ? And maybe a way where i can exclude the $chatUserID because i want only the chatPartners.
Thanks in advance.
If you don't care about the order they appear in, you can build two arrays of user1 and user2, then concat them together in a later stage. This won't handle deduplicating though.
$chatUserID = $_POST["chatUserID"]; // 2 in my example
$chatCursor = $chatCollection->aggregate([
[
'$match' => [
'$or' =>[
["chatUser1ID" => $chatUserID],
["chatUser2ID" => $chatUserID]
]
]
], [
'$group' => [
'_id' => 0,
'chatUsers1' => ['$addToSet' => '$chatUser1ID'],
'chatUsers2' => ['$addToSet' => '$chatUser2ID'],
]
], [
'$addFields' => [
'chatUsers' => [
'$concatArrays' => [
'$chatUsers1',
'$chatUsers2'
]
]
]
],
]);

How can I manage some irregular array to regular array?

I write code with some array that have different structure, but I must extract the data to do something else. How can I manager these array?
The array's structure are as follow:
$a = [
'pos1' => 'somedata',
'pos2' => ['data2', 'data3'],
'pos3' => '';
];
$b = [
[
'pos1' => ['data1', 'data2', ['nest1', 'nest2']],
'pos2' => ['data1', 'data2', 'data3'],
],
['data1', 'data2'],
'data4',
];
The array's Index can be a key or a position, and the value of the corresponding index may be a array with the same structure. More tough problem is that the subarray can be nesting, and the time of the nesting has different length.
Fortunately, every array has it's owe fixed structure.
I want to convert the these array to the format as follow. When the index is a value, change it to the keyword; and if the index is a keyword, nothing changed.
$a = [
'pos1' => 'somedata',
'pos2' => [
'pos2_1' => 'data2',
'pos2_2' => 'data3'
],
'pos3' => '';
];
$b = [
'pos1' => [
'pos1_1' => [
'pos1_1_1' => 'data1',
'pos1_1_2' => 'data2',
'pos1_1_3' => [
'pos1_1_3_1' => 'nest1',
'pos1_1_3_2' => 'nest2',
],
],
'pos1_2' => [
'pos1_2_1' => 'data1',
'pos1_2_2' => 'data2',
'pos1_2_3' => 'data3',
],
],
'pos2' => ['data1', 'data2'],
'pos3' => 'data4',
];
My first solution is for every array, write the function to convert the format(the keyword will specify in function). But it is a huge task and diffcult to manage.
The second solution is write a common function, with two argument: the source array and the configuration that specify the keyword to correspondent value index. For example:
$a = [0, ['pos10' => 1]];
$conf = [
// It means that when the value index is 0, it will change it into 'pos1'
'pos1' => 0,
'pos2' => 1,
];
The common funciton will generate the result of:
$result = [
'pos1' => 0,
'pos2' => ['pos10' => 1],
]
But this solution will lead to a problem: the config is diffcult to understand and design, and other people will spend a lot of time to understand the format after conversion.
Is there are some better solution to manage these array that other people can easy to use these array?
Thanks.

Alphabetically sort in new mongo db query command

I try to solved alphabetically sort using new MongoDB manager using command aggregation pipeline
below are my code:
$manager = new MongoDB\Driver\Manager("mongodb://localhost:27017");
$pipeline = [
[ '$match' => ['listingStatus' => ''] ],
[ '$group' => ['_id' => '$listingParticipants.email'] ],
[ '$limit' => 10 ],
[ '$skip' => 0],
[ '$sort' => ['listingParticipants.firstName' => 1]]
];
$aggregate = new \MongoDB\Driver\Command([
'aggregate' => 'Test_collection',
'pipeline' => $pipeline,
'cursor' => new stdClass
]);
$cursor = $manager->executeCommand('test_database', $aggregate);
in above code i use sort in pipeline with my string data type name field but it's not working properly so please write my code to solve this issue.
The problem here is, in a $group stage there is no name field that you are using for sort,
Better would be to sort after group
add listingParticipants.firstName in $group stage,
Also Note:
Mongo can not sort case-sensitive
meaning -- for the names like Abcd, ABcd, abcd, Bacd, bxyz
the sorted result will be (if sorted in ascending order).
Abcd, ABcd, Bacd, abcd, bxyz.
I suggest you add a new field sortName for the name with $toUpper or $toLower operator,
sortName: {$toUpper: "$name"}
and then use sortName for sorting.

Grouping element array php based first character value

I have an array based MySql database.
This is the array.
[
0 => [
'id' => '1997'
'lokasi_terakhir' => 'YA4121'
]
1 => [
'id' => '1998'
'lokasi_terakhir' => 'PL2115'
]
2 => [
'id' => '1999'
'lokasi_terakhir' => 'PL4111'
]
]
How can I get the element lokasi_terakhir that grouped by the first character ? What the best way ?
This is the goal :
[
"Y" => 1,
"P" => 2
]
Please advise
Here are two refined methods. Which one you choose will come down to your personal preference (you won't find better methods).
In the first, I am iterating the array, declaring the first character of the lokasi_terakhir value as the key in the $result declaration. If the key doesn't yet exist in the output array then it must be declared / set to 1. After it has been instantiated, it can then be incremented -- I am using "pre-incrementation".
The second method first maps a new array using the first character of the lokasi_terakhir value from each subarray, then counts each occurrence of each letter.
(Demonstrations Link)
Method #1: (foreach)
foreach($array as $item){
if(!isset($result[$item['lokasi_terakhir'][0]])){
$result[$item['lokasi_terakhir'][0]]=1; // instantiate
}else{
++$result[$item['lokasi_terakhir'][0]]; // increment
}
}
var_export($result);
Method #2: (functional)
var_export(array_count_values(array_map(function($a){return $a['lokasi_terakhir'][0];},$array)));
// generate array of single-character elements, then count occurrences
Output: (from either)
array (
'Y' => 1,
'P' => 2,
)
You can group those items like this:
$array = [
0 => [
'id' => '1997',
'lokasi_terakhir' => 'YA4121'
],
1 => [
'id' => '1998',
'lokasi_terakhir' => 'PL2115'
],
2 => [
'id' => '1999',
'lokasi_terakhir' => 'PL4111'
]
];
$result = array();
foreach($array as $item) {
$char = substr($item['lokasi_terakhir'], 0, 1);
if(!isset($result[$char])) {
$result[$char] = array();
}
$result[$char][] = $item;
}
<?php
$array=[
0 => [
'id' => '1997',
'lokasi_terakhir' => 'YA4121'
],
1 => [
'id' => '1998',
'lokasi_terakhir' => 'PL2115'
],
2 => [
'id' => '1999',
'lokasi_terakhir' => 'PL4111'
]
];
foreach($array as $row){
$newArray[]=$row['lokasi_terakhir'][0];
}
print_r(array_flip(array_unique($newArray)));
this code gets the first letter of the fields lokasi_terakhir , get the unique values to avoid duplicates and just flips the array to get the outcome you want.
The output is this :
Array ( [Y] => 0 [P] => 1 )

PHP Append array in nested array

I have below array, I need to append a new array inside $newData['_embedded']['settings']['web/vacation/filters']['data'], How can I access and append inside it ?
$newData = [
"id" => "47964173",
"email" => "abced#gmail.com",
"firstName" => "Muhammad",
"lastName" => "Taqi",
"type" => "employee",
"_embedded" => [
"settings" => [
[
"alias" => "web/essentials",
"data" => [],
"dateUpdated" => "2017-08-16T08:54:11Z"
],
[
"alias" => "web/personalization",
"data" => [],
"dateUpdated" => "2016-07-14T10:31:46Z"
],
[
"alias" => "wizard/login",
"data" => [],
"dateUpdated" => "2016-09-26T07:56:43Z"
],
[
"alias" => "web/vacation/filters",
"data" => [
"test" => [
"type" => "teams",
"value" => [
0 => "09b285ec-7687-fc95-2630-82d321764ea7",
1 => "0bf117b4-668b-a9da-72d4-66407be64a56",
2 => "16f30bfb-060b-360f-168e-1ddff04ef5cd"
],
],
"multiple teams" => [
"type" => "teams",
"value" => [
0 => "359c0f53-c9c3-3f88-87e3-aa9ec2748313"
]
]
],
"dateUpdated" => "2017-07-03T09:10:36Z"
],
[
"alias" => "web/vacation/state",
"data" => [],
"dateUpdated" => "2016-12-08T06:58:57Z"
]
]
]
];
$newData['_embedded']['settings']['web/vacation/filters']['data'] = $newArray;
Any Hint to quickly append it, I don't want to loop-in and check for keys inside loops.
The settings subarray is "indexed". You first need to search the alias column of the subarray for web/vacation/filters to find the correct index. Using a foreach loop without a break will mean your code will continue to iterate even after the index is found (bad coding practice).
There is a cleaner way that avoids a loop & condition & break, use array_search(array_column()). It will seek your associative element, return the index, and immediately stop seeking.
You can use the + operator to add the new data to the subarray. This avoids calling a function like array_merge().
Code: (Demo)
if(($index=array_search('web/vacation/filters',array_column($newData['_embedded']['settings'],'alias')))!==false){
$newData['_embedded']['settings'][$index]['data']+=$newArray;
}
var_export($newData);
Perhaps a more considered process would be to force the insert of the new data when the search returns no match, rather than just flagging the process as unsuccessful. You may have to tweak the date generation for your specific timezone or whatever... (Demo Link)
$newArray=["test2"=>[
"type" =>"teams2",
"value" => [
0 => "09b285ec-7687-fc95-2630-82d321764ea7",
1 => "0bf117b4-668b-a9da-72d4-66407be64a56",
2 => "16f30bfb-060b-360f-168e-1ddff04ef5cd"
],
]
];
if(($index=array_search('web/vacation/filters',array_column($newData['_embedded']['settings'],'alias')))!==false){
//echo $index;
$newData['_embedded']['settings'][$index]['data']+=$newArray;
}else{
//echo "couldn't find index, inserting new subarray";
$dt = new DateTime();
$dt->setTimeZone(new DateTimeZone('UTC')); // or whatever you are using
$stamp=$dt->format('Y-m-d\TH-i-s\Z');
$newData['_embedded']['settings'][]=[
"alias" => "web/vacation/filters",
"data" => $newArray,
"dateUpdated" => $stamp
];
}
You need to find the key that corresponds to web/vacation/filters. For Example you could use this.
foreach ($newData['_embedded']['settings'] as $key => $value) {
if ($value["alias"]==='web/vacation/filters') {
$indexOfWVF = $key;
}
}
$newData['_embedded']['settings'][$indexOfWVF]['data'][] = $newArray;
From the comments. Then you want to merge the arrays. Not append them.
$newData['_embedded']['settings'][$indexOfWVF]['data'] = array_merge($newData['_embedded']['settings'][$indexOfWVF]['data'],$newArray);
Or (if it's always Filter1):
$newData['_embedded']['settings'][$indexOfWVF]['data']['Filter1'] = $newArray['Filter1'];

Categories