Aggregate total and distinct count - php

I have a document to stored products, one receipt can have many products inside.
{"_id" : "59871e65fffe165420e0b324"
"receiptid" : "4BA/ZFY7AQ4HvfjUMTRTLg=="
"area_type" : 1
"report_type" : 3
"id_station" : 2317
"date" : "2017-08-05 00:00:00.000"
"time" : "1501979220"
"amount" : 10113958
"item" : 32
},
{"_id" : "59871e65fffe165420e0b324"
"receiptid" : "4BA/ZFY7AQ4HvfjUMTRTLg=="
"area_type" : 1
"report_type" : 3
"id_station" : 2317
"date" : "2017-08-05 00:00:00.000"
"time" : "1501979320"
"amount" : 4000000
"item" : 12
}
I want to count total amount and total receipt in one query:
$store = array(2317);
$cursor = $collection->aggregate([
['$match' => [ 'id_station' => ['$in' => $store ], 'date' => ['$gte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-08-01")*1000), '$lte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-08-01")*1000)] ] ],
['$group' => ['_id' => ["id_station" => '$id_station'],
"amountclient"=> ['$sum' => '$amount']
]
],
['$group' => ['_id' => ["id_station" => '$id_station', "receiptid" => '$receiptid'],
"receipt"=> ['$sum' => 1]
]
]
]);
But the query doesn't show anything, how can I correct it?
I want the result:
{"store" => xxxx, "amount" => xxxx, "number of receipt" => xxx}

You want "distinct counts", which means you actually "compound" the grouping the other way around to what you are attempting:
$store = array(2317);
$cursor = $collection->aggregate([
['$match' => [
'id_station' => ['$in' => $store ],
'date' => [
'$gte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-08-01")*1000),
'$lte'=> new MongoDB\BSON\UTCDateTime(strtotime("2017-08-01")*1000)
]
]],
['$group' => [
'_id' => [ 'id_station' => '$id_station', 'receiptid' => '$receiptid' ],
'amount' => [ '$sum' => '$amount' ]
]],
[ '$group' => [
'_id' => '$_id.id_station',
'amount' => [ '$sum' => '$amount' ],
'numReceipt' => [ '$sum' => 1 ]
]]
]);
The first $group "includes" the "receiptid" field in the grouping _id so that the results returns are the "distinct" combination of the two keys. This allows the accumulation of the "amount" over that combination, and means that only the "distinct" values of "receipt_id" per each "id_station" are actually returned.
The "second" $group cuts down the key to just the "distinct" "id_station" value alone. Note the notation as '$_id.id_station' since the value was placed in a "compound key" of the _id in the previous $group stage. This is how "pipelines" work, by only having the available "input" that was the "output" of the previous stage.
For the "amount" you can pass the value into $sum again, and where there were "multiple combinations of "id_station" and "receiptid" then this is now reduced down to the total for just the "id_station" key. So you are "totaling the totals" from the previous stage output by the new reduced grouping key.
As for the "number of receipts", since the first $group made those values "distinct" within each "id_station", then the number is simply the [ '$sum' => 1 ] result.
Basically on the included data in the question where both the keys are shared in the documents, it would return the "total amount" and the count of 1 for the "receipts" since there is only one "distinct" value.

Related

Merge two multidimensional arrays using recursion, but not on full data set

I'm trying to add 2 array data to each other with array_merge(). It's just attached to the back. But lower levels are ignored.
Is there an alternative to array_merge() that will merge the user values without duolicating the color values?
Existing array data:
$existingtArr = [
"A" => [
"color" => 'red',
"user" => [
"Daniel" => ["01:18:08", "04:10:12"],
"Max" => ["01:04:00"],
"Serto" => ["02:00:02"],
]
],
"B" => [
"color" => 'blue',
"user" => [
"Franz" => ["08:40:52"],
"Hugo" => ["07:08:58"],
]
]
];
New array data:
$newArr = [
"A" => [
"color" => 'red',
"user" => [
"Fabian" => ["06:03:00"], // + 1 user
"Max" => ["04:10:12"], // + 1 new time
"Serto" => ["02:00:02"],
]
],
"B" => [
"color" => 'blue',
"user" => [
"Franz" => ["08:40:52", "14:05:32", "20:34:15"], // an older one is available, + 2 new times
"Hugo" => ["04:10:12"], // + 1 time
]
],
"C" => [ // + new whole group
"color" => 'green',
"user" => [
"Maxi" => ["07:08:58", "04:10:12"],
]
]
];
Supplement the existing data with the new data:
echo '<pre>';
print_r(array_merge($existingtArr, $newArr));
echo '</pre>';
Expected result array data:
$resultArr = [
"A" => [
"color" => 'red',
"user" => [
"Daniel" => ["01:18:08", "04:10:12"],
"Fabian" => ["06:03:00"],
"Max" => ["01:04:00", "04:10:12"],
"Serto" => ["02:00:02"],
]
],
"B" => [
"color" => 'blue',
"user" => [
"Franz" => ["08:40:52", "14:05:32", "20:34:15"],
"Hugo" => ["07:08:58", "04:10:12"],
]
],
"C" => [
"color" => 'green',
"user" => [
"Maxi" => ["07:08:58", "04:10:12"],
]
]
];
You cannot simply call array_merge_recursive() on the whole data sets because they will generated repeated color values, but you want the color values to remain singular and the user data to be recursively merged.
To accommodate this logic (assuming it is okay to simply mutate the $existingtArr array with the data from $newArr), perform a check for the existence of each letter-set, and either push the whole set for a non-present letter, or recursively merge the shared letter-sets.
Code: (Demo)
foreach ($newArr as $letter => $set) {
if (!isset($existingtArr[$letter])) {
$existingtArr[$letter] = $set;
} else {
$existingtArr[$letter]['user'] = array_merge_recursive(
$existingtArr[$letter]['user'],
$set['user']
);
}
}
var_export($existingtArr);

How to add unique identifier to each level of nested array with unknown values?

I have array that contains 4 levels of nesting:
$data = [
"" => [
'sub_level_1' => [
'sub_level_2' => [
'sub_level_3' => [
0 => ""
1 => "val"
]
]
]
],
"top_level_2" => [
'sub_level_1' => [
'sub_level_2' => [
'sub_level_3' => [
0 => ""
1 => "some_val"
2 => "foo"
]
]
]
]
];
I want to add other unique identifier for each nested level, so I use a counter on each level:
$top_level_counter = 0;
$sub_level_1_counter = 0;
$sub_level_2_counter = 0;
$sub_level_3_counter = 0;
foreach ($data as &$top_level)
{
$top_level['top_level_id'] = $top_level_counter++;
foreach($top_level as &$sub_level_1)
{
$sub_level_1['sub_level_1_id'] = $sub_level_1_counter++;
foreach($sub_level_1 as &$sub_level_2)
{
$sub_level_2['sub_level_2_id'] = $sub_level_2_counter++;
// and continue with the last level..
}
}
}
But as the example above, the keys can be empty ("") or even null because that's data from the database.
I end up getting errors related to invalid values, but I can't find out which.
I did figure that as I go inside each level, I have to check for:
if (is_array($sub_level_<x>)
because on each level above I already store the unique id which is not an array. But still, even with that, I get invalid input for foreach for example.
But I can't find out what's causing that, though I suspect it might be because of a null or empty value
Is there a better way to do that?
The reason I want to do it is to have an easier to work with identifier for each element in the array because the actual data is strings in another language.
Expected result would be:
$data = [
"" => [
'top_level_id' => 0,
'sub_level_1' => [
'sub_level_1_id' => 0,
'sub_level_2' => [
'sub_level_2_id' => 0,
'sub_level_3' => [
0 => ""
1 => "val"
]
]
]
],
"top_level_2" => [
'top_level_id' => 1,
'sub_level_1' => [
'sub_level_1_id' => 1,
'sub_level_2' => [
'sub_level_2_id' => 1,
'sub_level_3' => [
0 => ""
1 => "some_val"
2 => "foo"
]
]
]
]
];

Laravel - restructuring array for easy sync of many-to-many with additional pivot data

I have created what feels like a clunky solution to restructuring a data array in order to pass it to a sync() to update a many-to-many relationship with additional data in the pivot table and wondered if anyone could suggest a simpler approach.
I have an array coming from a request, here's a relevant extract:
"papers" => [
0 => [
"id" => 2
"code" => "123-321-888"
"name" => "Pop out"
"pivot" => [
"job_id" => 46
"paper_id" => 2
"qty_required" => 500
]
]
1 => [
"id" => 1
"code" => "444-666-999"
"name" => "Premium pro"
"pivot" => [
"job_id" => 46
"paper_id" => 1
"qty_required" => 1000
]
]
]
In order to do an easy sync of a many-to-many relationship with extra pivot data one needs to restructure that to:
[
paper[id] => [
'qty_required' => paper[pivot][qty_required]
]
]
Which for the above example would be:
[
2 => [
"qty_required" => "500"
]
1 => [
"qty_required" => "1000"
]
]
I'm currently doing a 2-step process to achieve this as follows:
$paperUpdate = Arr::pluck($request->input('papers'), 'pivot.qty_required', 'id');
//output: [ 2 => 500, 1 => 1000]
foreach ($paperUpdate as $key => $value) {
$paperSync[$key]['qty_required'] = $value;
}
//output: [ 2 => [ "qty_required" => "500" ], 1 => [ "qty_required" => "1000" ]
$job->papers()->sync($paperSync);
Is there an easier approach?
Your approach seems fine to me. If you want to nit pick, you could do one less iteration using:
$sync = array_reduce($request->input('papers'), function ($sync, $paper) {
$id = $paper['id'];
$sync[$id] = [ 'qty_required' => $paper['pivot']['qty_required'] ];
return $sync;
}, []);

Find document by field in sub-array and return another field in same sub-array

I have a collection called products, which has documents containing several fields, along with a variants sub-array.
The variants sub-arrays have several fields, including sku and id.
I know the id value, and I need to use it to get the sku value.
The simplified collection looks like this:
[
"_id" => "whatever_id_1",
"field_2" => "some value 1",
"variants" =>
[
"id" => "some_id_123"
"sku" => "SKU-1"
],
[
"id" => "some_id_124"
"sku" => "SKU-2"
],
[
"id" => "some_id_125"
"sku" => "SKU-3"
]
],
[
"_id" => "whatever_id_2",
"field_2" => "some value 2",
"variants" =>
[
"id" => "some_id_126"
"sku" => "SKU-4"
],
[
"id" => "some_id_127"
"sku" => "SKU-5"
],
[
"id" => "some_id_128"
"sku" => "SKU-6"
]
],
[
"_id" => "whatever_id_3",
"field_2" => "some value 3",
"variants" =>
[
"id" => "some_id_129"
"sku" => "SKU-7"
],
[
"id" => "some_id_130"
"sku" => "SKU-8"
],
[
"id" => "some_id_131"
"sku" => "SKU-9"
]
]
I am retrieving the correct document with
// Set item_id
$item_id = 'some_id_127';
// Build 'find product with inventory item id' query
$find_product_with_id_query = [ 'variants' => ['$elemMatch' => ['id' => $item_id] ] ];
// Get the product document to process
$inventory_update_product = $client_products_collection->findOne($find_product_with_id_query);
This properly returns the parent document with "_id" => "whatever_id_2".
Now, I know I can iterate over that results (eg. $inventory_update_product['variants'), and find the sku value that way.
QUESTIONS
1. But is there some way to get the sku value with MongoDB?
2. Is there any benefit to using MongoDB for this last step, or is it more efficient to just use PHP for loop to find the sku?
Yes, actually. You can use projection:
// Set item_id
$item_id = 'some_id_127';
// Build 'find product with inventory item id' query
$find_product_with_id_query = [ 'variants' => ['$elemMatch' => ['id' => $item_id] ] ];
// Project and limit options
$options = [
'projection' => [ 'variants.$' => 1 ],
'limit' => 1
];
// Get the product document to process
$inventory_update_product = $client_products_collection->find($find_product_with_id_query, $options);
This will return a cursor containing the document with the array variants with only the element matching the one you searched for.
Exact syntax may vary depending on driver version and whether or not you're using the userland library.

How to Count the number of objects in elequont Object inside a group by in a already queried collection?

This is my current output
Collection {#794 ▼
#items: array:8 [▼
"IN" => Collection {#795 ▶}
"NZ" => Collection {#787 ▶}
]}
I want the items to be hold the no of count for each codes like
"IN" => 4,
"NZ" => 3,
I know that I can directly write in a query like this
$query->groupBy('country_code')->orderBy('country_code', 'ASC');
return $query->get([
DB::raw('country_code as country_code'),
DB::raw('COUNT(*) as "count"')
]);
But I want the output from a already queried collection to reduce multiple queries which is a collection.
Right now I am only able to group by on the collection like this
$collection->groupBy('country_code');
$b = $a->groupBy('country_code');
You've done most of the job by proper grouping the data based on the country_code. Now it's just to iterate through the collection with a foreach, key, value and use the collections's count() method to count the number of elements stored under a given country_code
foreach ($b as $countryCode => $items) {
echo $items->count()."\n";
}
Reproduce:
php artisan ti
Psy Shell v0.7.2 (PHP 7.0.8-0ubuntu0.16.04.3 — cli) by Justin Hileman
>>> $cities = collect([['country_code' => 'pl', 'name' => 'Warszawa'], ['country_code' => 'pl', 'name' => 'Wrocław'], ['country_code' => 'de', 'name' => 'Berlin']]);
=> Illuminate\Support\Collection {#846
all: [
[
"country_code" => "pl",
"name" => "Warszawa",
],
[
"country_code" => "pl",
"name" => "Wrocław",
],
[
"country_code" => "de",
"name" => "Berlin",
],
],
}
>>> $grouped = $cities->groupBy('country_code');
=> Illuminate\Support\Collection {#836
all: [
"pl" => Illuminate\Support\Collection {#838
all: [
[
"country_code" => "pl",
"name" => "Warszawa",
],
[
"country_code" => "pl",
"name" => "Wrocław",
],
],
},
"de" => Illuminate\Support\Collection {#837
all: [
[
"country_code" => "de",
"name" => "Berlin",
],
],
},
],
}
>>> foreach ($grouped as $cCode => $cities) {
... echo $cCode . ' has '.$cities->count()."\n";
... }
pl has 2
de has 1

Categories