PHP MongoDB $set using arrayFilters - php

I'm trying to update the value of "position" in "attributes" using the $ set operator with arrayFilters.
The command below works as expected on the terminal
db.produtos.insert(
{
"_id": 1,
"nome":"Nome do Produto",
"descricao":"Lorem ipsum dolor sit amet.",
"atributos": [
{ "id" : 1, "posicao" : 2 },
{ "id" : 2, "posicao" : 1 },
{ "id" : 3, "posicao" : 3 }
]
}
)
db.produtos.update(
{"_id": 1 },
{
$set: {
"atributos.$[elem1].posicao": 1,
"atributos.$[elem2].posicao": 2,
"atributos.$[elem3].posicao": 3
}
},
{
arrayFilters: [
{ "elem1.id": 1 },
{ "elem2.id": 2 },
{ "elem3.id": 3 },
]
}
)
But when I try to create the same command in PHP it does not work.
In my tests, the document is found but never updated.
Just to explain, I get the "attributes.id" of a string, I mount an array with the update and another to filter
<?php
$ids = explode(',', '1,2,3');
$update = [];
$filters = [];
$i = 1;
foreach ($ids as $linha) :
$update['atributos.$[elem'.$i.'].posicao'] = $i;
$filters[]['elem'.$i.'.id'] = $linha;
$i++;
endforeach;
$conexao = new MongoDB\Driver\Manager('mongodb://localhost:27017');
$cmd = new MongoDB\Driver\Command(
[
'findAndModify' => 'produtos',
'query' => ['_id' => 1],
'update' => ['$set' => $update],
'upsert' => true,
'returnDocument' => true,
'new' => true,
'arrayFilters' => $filters,
]
);
$result = $conexao->executeCommand('teste', $cmd)->toArray();
$result = json_decode(json_encode($result), true);
echo '<pre>';
print_r($cmd);
print_r($result);

Related

Join 2 laravel collections

I have 2 collections :
$collection1 = [
{ name : "aaa" , score : 10 },
{ name : "bbb" , score : 20 }
];
$collection2 = [
{ name : "aaa" , score : 30 },
{ name : "bbb" , score : 10 }
];
and I want to join them to get the following result :
$collection3 = [
{ name : "aaa" , score : 40 },
{ name : "bbb" , score : 30 }
]
merge() and union() didn't do the job for me .
Combine the collections, group by the name and sum the score. This can be done like so with collections in Laravel. Note that group by returns a collection of the grouped data.
$collection1->concat($collection2);
$collection1->groupBy('name')->map(function ($scores) {
$score = $scores->first();
$score['score'] = $scores->sum('score');
return $score;
});
it can be achieve by laravel collection map() and where() function
$collection1 = collect([
[
"name" => "aaa",
"score" => 10
],
[
"name" => "bbb",
"score" => 20
]
]);
$collection2 = collect([
[
"name" => "aaa",
"score" => 30
],
[
"name" => "bbb",
"score" => 10
]
]);
$collection3 = $collection1->map(function ($item) use ($collection2) {
$found = $collection2->where('name', $item['name']);
if ($found) {
$item['score'] = $item['score'] + $found->first()['score'];
}
return $item;
});
dd($collection3);
This is the result https://phpsandbox.io/n/soft-flower-ap1e-vo2xy
$collection = collect();$collection->merge(['col1' => $col1, 'col2' => $col2 ]);
OR $states = [ 'Delhi', 'Tamil Nadu', 'Uttar Pradesh'];
foreach ($states as $state)
{ $data = DB::table('user')->select('user.*') ->where("user.city", $state)->where('user.status', 1)->get(); if (!empty($data[0])) $stateUser = $stateUser->merge([$state => $data]);}

Convert Mongodb shell query with map and aggregate to php

I wrote a mongodb query that I am having a hard time converting to php code:
var geoips = db.geoip.find().map(function(like){ return like.ip; });
var result = db.audit.aggregate([
{ $match: { ip: { $nin: geoips } } },
{ $group: {
_id: "$ip",
count: { $sum: 1 }
}}
]);
UPDATE:
The above query is the equivalent of the following Relation Database Query
Select ip,count(*)
from audit
where ip not in (select ip from geoip)
group by ip
Since I had to make this query in mongodb version 3.0, I was unable to take advantage of $lookup as suggested in an answer.
The below PHP code accomplishes the above objective and works as expected. It gets the distinct ips from geoip collection. It passes that result and does an aggregate on the audit collection to get the desired result.
$geoipcolln = $this->dbConn->selectCollection('geoip');
$geoips = $geoipcolln->distinct('ip');
$match = array('ip' => array('$nin' => $geoips));
$result = $this->collection->aggregate(
array(
'$match' => $match
),
array('$group' => array(
'_id' => '$ip',
'count' => array('$sum' => 1.0),
))
);
This can be done in one aggregation query using the $lookup operator as follows:
var result = db.audit.aggregate([
{
"$lookup": {
"from": "geoip",
"localField": "ip",
"foreignField": "ip",
"as": "geoips"
}
},
{ "$match": { "geoips.0": { "$exists": false } } },
{ "$group": {
"_id": "$ip",
"count": { "$sum": 1 }
}}
])
which can then be translated to PHP as:
<?php
$m = new MongoClient("localhost");
$c = $m->selectDB("yourDB")->selectCollection("audit");
$ops = array(
array(
"$lookup" => array(
"from" => "geoip",
"localField" => "ip",
"foreignField" => "ip",
"as" => "geoips"
)
),
array( "$match" => array( "geoips.0" => array( "$exists" => false ) ) ),
array( "$group" => array(
"_id" => "$ip",
"count" => array( "$sum" => 1 )
))
);
$results = $c->aggregate($ops);
var_dump($results);
?>

PHP array problems - grouping array

I'm trying to convert some array values (group then actually), but I don't know how to do that.
I have something like this:
[
{
"nsr": "000086310",
"type": "3",
"date": "2015-07-18",
"time": "00:06",
"pis": "12138790985"
},
{
"nsr": "000086313",
"type": "3",
"date": "2015-07-18",
"time": "00:33",
"pis": "16073736879"
},
{
"nsr": "000086316",
"type": "3",
"date": "2015-07-18",
"time": "00:58",
"pis": "16634402451"
},
{
"nsr": "000086316",
"type": "3",
"date": "2015-07-19",
"time": "00:58",
"pis": "98127981729"
},
{
"nsr": "000086316",
"type": "3",
"date": "2015-07-19",
"time": "00:58",
"pis": "12398712938"
}
]
And I want to convert to this:
[
"date" : "2015-07-18",
"pis" : [
"12138790985",
"16073736879",
"16634402451"
]
],
[
"date" : "2015-07-19",
"pis" : [
"98127981729",
"12398712938"
]
]
I tried to do something like this:
public function index()
{
$this->setTxtData('../../txt_files/CAP 3 18 07 2015 FABRICA.txt');
$txtdata = $this->getTxtData();
$dataToCompare = array();
foreach($txtdata as $ponto){
$time = $ponto['time'];
$date = $ponto['date'];
$pis = $ponto['pis'];
// $dataToCompare = array();
// if(strpos($ponto['pis'], '00000000000') === false){
// $pis_temp[][''] = $ponto['pis'];
// }
if(isset($dataToCompare)){
foreach($dataToCompare as $dateToSet){
if($dateToSet['data'] == $date){
$dateToSet['pis'][] = $pis;
}
else{
$dateToSet['data'] = $date;
$dateToSet['pis'][] = $pis;
}
}
}
else{
$dataToCompare = array(
[
'data' => $date,
'pis' => array($pis)
]
);
}
$funcionario_id = DB::table('funcionario')
->select('id')
->where('pis_pasep', '=', $pis)
->pluck('id');
if($funcionario_id !== null){
$validate = DB::table('horas_trabalho')
->select('id')
->where('hora', '=', $time)
->where('data', '=', $date)
->where('funcionario_id', '=', $funcionario_id)
->pluck('id');
if($validate === null){
DB::table('horas_trabalho')
->insert([
'hora' => $time,
'data' => $date,
'funcionario_id' => $funcionario_id
]);
}
}
}
//-------------------------------------Lógica para faltas-----------------------------------
/**
* Pega o pis
*/
$db_all_funcionarios = DB::table('funcionario')
->select('pis_pasep')
->where('pis_pasep', '!=', 0)
->get();
foreach($db_all_funcionarios as $pis){
if(strpos($pis->pis_pasep, '00000000000') !== true){
$global_pis[] = $pis->pis_pasep;
}
}
// $faltantes = array_diff($pis_temp, $global_pis);
// foreach($faltantes as $faltante){
// DB::table('falta')
// ->insert([
// 'data' => date('2015-07-16')
// ]);
// }
// $ponto_db[] = DB::table('horas_trabalho')
// ->join('funcionario', 'horas_trabalho.funcionario_id', '=', 'funcionario.id')
// ->select('funcionario.nome', 'horas_trabalho.hora', 'horas_trabalho.data')
// ->get();
return $txtdata;
}
I see the date in your filename and think you want group all entities at all:
$dataToCompare = array(
'date' => $txtData[0]['date'],
'pis' => array_map(function($el){return $el['pis'];}, $txtData)
);
For multiple dates:
$hash = array();
foreach ($txtData as $entity) {
if (!isset($hash[$entity['date']])) $hash[$entity['date']] = array();
$hash[$entity['date']][] = $entity['pis'];
}
$result = array();
foreach($hash as $date=>$pis) {
$result[] = array('date'=>$date, 'pis'=>$pis);
}
It might be a little sloppy, but it does the job. Or either way, it works as you wanted in the example:
<?php
$array = array(
0 => array(
"nsr" => "000086310",
"type" => "3",
"date" => "2015-07-18",
"time" => "00:06",
"pis" => "12138790985"
),
1 => array(
"nsr" => "000086313",
"type" => "3",
"date" => "2015-07-18",
"time" => "00:33",
"pis" => "16073736879"
),
2 => array(
"nsr" => "000086316",
"type" => "3",
"date" => "2015-07-18",
"time" => "00:58",
"pis" => "16634402451"
)
);
$newarray = array();
$pis = array();
foreach($array as $part){
array_push($pis,$part['pis']);
$newarray = array(
"date" => $part['date'],
"pis" => $pis
);
}
var_dump($newarray);
?>
$array = json_decode($str, true);
// make array date => pis
$tmp = array();
foreach ($array as $item) {
if (!isset($tmp[$item['date']])) $tmp[$item['date']]['pis'] = array();
$tmp[$item['date']]['pis'][] = $item['pis'];
}
// Than move date from key to item
$result = array();
foreach($tmp as $k=>$v)
$result[] = array('date' => $k, 'pis' => $v['pis']);
print_r($result);

mongodb avg in aggregation of array element

I have the following collection structure
{
"_id": {
"d_timestamp": NumberLong(1429949699),
"d_isostamp": ISODate("2015-04-25T08:14:59.0Z")
},
"XBT-USD-cpx-okc": [
{
"buySpread": -1.80081
}
I run the following aggregation
$spreadName ='XBT-USD-stp-nex';
$pipe = array(
array(
'$match' => array(
'_id.d_isostamp' => array(
'$gt' => $start, '$lt' => $end
)
)
),
array(
'$project' => array(
'sellSpread' =>'$'.$spreadName.'.sellSpread',
)
),
array(
'$group' => array(
'_id' => array(
'isodate' => array(
'$minute' => '$_id.d_isostamp'
)
),
'rsell_spread' => array(
'$avg' => '$sellSpread'
),
)
),
);
$out = $collection->aggregate($pipe ,$options);
and I get as a result the value 0 for rsell_spread whereas if I run a $max for instance instead of an $avg in the $group , I get an accurate value for rsell_spread , w/ the following structure
{
"_id": {
"isodate": ISODate("2015-04-25T08:00:58.0Z")
},
"rsell_spread": [
-4.49996▼
]
}
So I have two questions :
1/ How come does the $avg function does not work?
2/ How can I can a result not in an array when I use $max for example (just a regular number)?
The $avg group accumulator operator does work, it's only that in your case it is being applied to an element in an array and thus gives the "incorrect" result.
When you use the $max group accumulator operator, it returns the the highest value that results from applying an expression to each document in a group of documents, thus in your example it returned the maximum array.
To demonstrate this, consider adding a few sample documents to a test collection in mongoshell:
db.test.insert([
{
"_id" : {
"d_timestamp" : NumberLong(1429949699),
"d_isostamp" : ISODate("2015-04-25T08:14:59.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80081
}
]
},
{
"_id" : {
"d_timestamp" : NumberLong(1429949710),
"d_isostamp" : ISODate("2015-04-25T08:15:10.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80079
}
]
},
{
"_id" : {
"d_timestamp" : NumberLong(1429949720),
"d_isostamp" : ISODate("2015-04-25T08:15:20.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80083
}
]
},
{
"_id" : {
"d_timestamp" : NumberLong(1429949730),
"d_isostamp" : ISODate("2015-04-25T08:15:30.000Z")
},
"XBT-USD-stp-nex" : [
{
"sellSpread" : -1.80087
}
]
}
])
Now, replicating the same operation above in mongoshell:
var spreadName = "XBT-USD-stp-nex",
start = new Date(2015, 3, 25),
end = new Date(2015, 3, 26);
db.test.aggregate([
{
"$match": {
"_id.d_isostamp": { "$gte": start, "$lte": end }
}
},
{
"$project": {
"sellSpread": "$"+spreadName+".sellSpread"
}
}/*,<--- deliberately omitted the $unwind stage from the pipeline to replicate the current pipeline
{
"$unwind": "$sellSpread"
}*/,
{
"$group": {
"_id": {
"isodate": { "$minute": "$_id.d_isostamp"}
},
"rsell_spread": {
"$avg": "$sellSpread"
}
}
}
])
Output:
/* 0 */
{
"result" : [
{
"_id" : {
"isodate" : 15
},
"rsell_spread" : 0
},
{
"_id" : {
"isodate" : 14
},
"rsell_spread" : 0
}
],
"ok" : 1
}
The solution is to include an $unwind operator pipeline stage after the $project step, this will deconstruct the XBT-USD-stp-nex array field from the input documents and outputs a document for each element. Each output document replaces the array with an element value. This will then make it possible for the $avg group accumulator operator to work.
Including this will give the aggregation result:
/* 0 */
{
"result" : [
{
"_id" : {
"isodate" : 15
},
"rsell_spread" : -1.80083
},
{
"_id" : {
"isodate" : 14
},
"rsell_spread" : -1.80081
}
],
"ok" : 1
}
So your final working aggregation in PHP should be:
$spreadName ='XBT-USD-stp-nex';
$pipe = array(
array(
'$match' => array(
'_id.d_isostamp' => array(
'$gt' => $start, '$lt' => $end
)
)
),
array(
'$project' => array(
'sellSpread' =>'$'.$spreadName.'.sellSpread',
)
),
array('$unwind' => '$sellSpread'),
array(
'$group' => array(
'_id' => array(
'isodate' => array(
'$minute' => '$_id.d_isostamp'
)
),
'rsell_spread' => array(
'$avg' => '$sellSpread'
),
)
),
);
$out = $collection->aggregate($pipe ,$options);

MongoDB aggregate query using PHP driver

I have a working MongoDB aggregate query which I can run via the MongoDB shell. However, I am trying to convert it to work with the official PHP Mongo driver (http://php.net/manual/en/mongocollection.aggregate.php).
Here is the working raw MongoDB query:
db.executions.aggregate( [
{ $project : { day : { $dayOfYear : "$executed" } } },
{ $group : { _id : { day : "$day" }, n : { $sum : 1 } } } ,
{ $sort : { _id : -1 } } ,
{ $limit : 14 }
] )
Here is my attempt (not working) in PHP using the Mongo driver:
$result = $c->aggregate(array(
'$project' => array(
'day' => array('$dayOfYear' => '$executed')
),
'$group' => array(
'_id' => array('day' => '$day'),
'n' => array('$sum' => 1)
),
'$sort' => array(
'_id' => 1
),
'$limit' => 14
));
The error from the above PHP code is:
{"errmsg":"exception: wrong type for field (pipeline) 3 != 4","code":13111,"ok":0}
Any ideas? Thanks.
The parameter in your Javascript is an array of 4 objects with one element each, in your PHP it's an associative array (object) with 4 elements. This would represent your Javascript:
$result = $c->aggregate(array(
array(
'$project' => array(
'day' => array('$dayOfYear' => '$executed')
),
),
array(
'$group' => array(
'_id' => array('day' => '$day'),
'n' => array('$sum' => 1)
),
),
array(
'$sort' => array(
'_id' => 1
),
),
array(
'$limit' => 14
)
));
In addition, if you have at least PHP5.4, you can use simpler array syntax. Transformation to PHP is then trivial, you simply replace curly braces with square brackets and colons with arrows:
$result = $c->aggregate([
[ '$project' => [ 'day' => ['$dayOfYear' => '$executed'] ] ],
[ '$group' => ['_id' => ['day' => '$day'], 'n' => ['$sum' => 1] ] ],
[ '$sort' => ['_id' => 1] ],
[ '$limit' => 14 ]
]);
You can also use the JSON array directly:
$json = [
'{ $project : { day : { $dayOfYear : "$executed" } } },
{ $group : { _id : { day : "$day" }, n : { $sum : 1 } } } ,
{ $sort : { _id : -1 } } ,
{ $limit : 14 }'
];
$pipeline = [];
foreach ($json as $stage) {
array_push($pipeline, toPHP(fromJSON($stage)));
}
$result = $c->aggregate($pipeline);
You can also use combination like this:
use function MongoDB\BSON\toRelaxedExtendedJSON;
use function MongoDB\BSON\fromPHP;
use function MongoDB\BSON\toPHP;
use function MongoDB\BSON\fromJSON;
$operator = '$dayOfYear';
$json = [];
array_push($json, toRelaxedExtendedJSON(fromPHP(
array('$project ' => array('day' => array($operator => '$executed')))
)));
array_push($json,
'{ $group : { _id : { day : "$day" }, n : { $sum : 1 } } } ,
{ $sort : { _id : -1 } } ,
{ $limit : 14 }'
);
$pipeline = [];
foreach ($json as $stage) {
array_push($pipeline, toPHP(fromJSON($stage)));
}
$result = $c->aggregate($pipeline);
or
$operator = '$dayOfYear';
$json = [];
array_push($json,
'{ $group : { _id : { day : "$day" }, n : { $sum : 1 } } } ,
{ $sort : { _id : -1 } } ,
{ $limit : 14 }'
);
$pipeline = [];
array_push($pipeline, array('$project ' => array('day' => array($operator => '$executed'))));
foreach ($json as $stage) {
array_push($pipeline, toPHP(fromJSON($stage)));
}
$result = $c->aggregate($pipeline);
If your aggregation query is dynamic or based on user-input be aware of NoSQL-Injection!
https://www.acunetix.com/blog/web-security-zone/nosql-injections/

Categories