I have data on invoices and I want to group them by time period wall. for example 0-10 and 11-20:
I need to solve them with $aggregate in mongoDB.
{
'_id': '1',
'value': 10,
'due_date': '20221001'
},
{
'_id': '2',
'value': 10,
'due_date': '20221012'
},
{
'_id': '2',
'value': 10,
'due_date': '20221030'
},
I need to group by period 0-10 days, 11-20 days and sum the values: For the example above the result would be:
[{
"_id": '0-10 days',
"total": 10,
},
{
"_id": '11-20 days',
"total": 10,
},
{
"_id": '>20 days',
"total": 10,
}]
I try with:
['$facet' => [
['due_date_one' => [
['$match' => [
'due_date' => [
'$gt' => new UTCDateTime((new Carbon())-> subDays(100) -> getTimestamp()),
'$lte' => date('Y-m-d', strtotime('now'))
]
]
]],
]]
You could use $bucket or add a few $addFields and $group stages:
Example mongo playground - https://mongoplayground.net/p/B0zl_GGH4sG
Example Documents:
[
{
"_id": "1a",
"value": 10,
"due_date": "20221001"
},
{
"_id": "1b",
"value": 5,
"due_date": "20221102"
},
{
"_id": "1c",
"value": 7,
"due_date": "20221102"
},
{
"_id": "2a",
"value": 10,
"due_date": "20221012"
},
{
"_id": "2b",
"value": 7,
"due_date": "20221113"
},
{
"_id": "2c",
"value": 8,
"due_date": "20221113"
},
{
"_id": "3a",
"value": 10,
"due_date": "20221030"
},
{
"_id": "3b",
"value": 9,
"due_date": "20221131"
},
{
"_id": "3c",
"value": 11,
"due_date": "20221131"
}
]
Aggregation query:
db.collection.aggregate([
{
$addFields: {
day: {
$toInt: { $substr: [ "$due_date", 6, 2 ] }
}
}
},
{
$addFields: {
bucketDate: {
$switch: {
branches: [
{ case: { $gt: [ "$day", 20 ] }, then: ">20 days" },
{ case: { $gt: [ "$day", 10 ] }, then: "11-20 days" }
],
"default": "0-10 days"
}
}
}
},
{
$addFields: {
bucketDateWithMonth: {
$concat: [
{ $substr: [ "$due_date", 0, 6 ] },
" ",
"$bucketDate"
]
}
}
},
{
$group: {
//_id: "$bucketDate", //No grouped month
_id: "$bucketDateWithMonth", //With grouped month
count: { $sum: 1 },
value: { $sum: "$value" }
}
}
])
Output: (grouped month)
[
{
"_id": "202210 0-10 days",
"count": 1,
"value": 10
},
{
"_id": "202211 0-10 days",
"count": 2,
"value": 12
},
{
"_id": "202210 11-20 days",
"count": 1,
"value": 10
},
{
"_id": "202211 11-20 days",
"count": 2,
"value": 15
},
{
"_id": "202210 \u003e20 days",
"count": 1,
"value": 10
},
{
"_id": "202211 \u003e20 days",
"count": 2,
"value": 20
}
]
Output: (No grouped month)
[
{
"_id": "\u003e20 days",
"count": 3,
"value": 30
},
{
"_id": "0-10 days",
"count": 3,
"value": 22
},
{
"_id": "11-20 days",
"count": 3,
"value": 25
}
]
Related
{ "_id": "apples", "qty": 5, "packing": "cardboard" }, { "_id": "bananas", "qty": 7 }, { "_id": "oranges", "qty": { "in stock": 8, "ordered": 12 }, }, { "_id": "avocados", "qty": "fourteen" }
How to find apples and oranges only in one find query.
Use the $in operator:
db.collection.find({ _id: { $in: [ "apples", "oranges" ] } })
i need to combine two jsons in a job task, according to the person's location. I have a Json with people from location type 1, and another from location type 2.
I need to combine in a new array for each time they have the same location, and they are repeated and separate according to type. I tried to do it with a foreach inside the other, but with no success. Here is an example of JSON.
Json 1:
[
{
"id": 1,
"name": "Jacob",
"place": "Brazil",
"type": 1
},
{
"id": 2,
"name": "Izac",
"place": "Brazil",
"type": 1
},
{
"id": 3,
"name": "Anran",
"place": "Brazil",
"type": 1
},
{
"id": 4,
"name": "Irbur",
"place": "Brazil",
"type": 1
},
{
"id": 5,
"name": "Lusos",
"place": "Brazil",
"type": 1
},
{
"id": 6,
"name": "Gamio",
"place": "Brazil",
"type": 1
},
{
"id": 7,
"name": "Nubeil",
"place": "Brazil",
"type": 1
},
{
"id": 8,
"name": "Usgon",
"place": "Brazil",
"type": 1
},
{
"id": 15,
"name": "Fikis",
"place": "England",
"type": 1
}
]
Json 2:
[
{
"id": 9,
"name": "Gipin",
"place": "Brazil",
"type": 0
},
{
"id": 10,
"name": "Paoir",
"place": "Brazil",
"type": 0
},
{
"id": 11,
"cia": "Mutue",
"place": "Brazil",
"type": 0
},
{
"id": 12,
"name": "Ziefel",
"place": "England",
"type": 0
},
{
"id": 13,
"name": "Liedo",
"place": "England",
"type": 0
},
{
"id": 14,
"name": "Vicis",
"place": "England",
"type": 0
}
]
the result might look something like this:
{
"groups": [ // groups (same place)
{
"tipe1": [ // groups type 1: same place
{
"id": 1,
"name": "Jacob",
"place": "Brazil"
},
{
"id": 1,
"name": "Jacob",
"place": "Brazil"
}
]
"tipe2": [ // groups type 2: same place
{
"id": 11,
"name": "Mutue",
"place": "Brazil"
},
]
},
{
"tipe1": [
{
"id": 15,
"name": "Fikis",
"place": "England"
}
]
"tipe2": [
{
"id": 14,
"name": "Vicis",
"place": "England"
},
]
}
],
}
The rule for grouping is, two or more items of type 1 can be grouped with 1 of type 2, in reverse as well. It can be thought of as outward flights, and back flights.
Given the following two arrays, how can they be merged efficiently to result in the third array?
productData
$productData =
[
{
"product_id": 4,
"type": "electronic",
"name": "monitor",
"specs": {
"HDMI": true,
"VGA": false
}
},
{
"product_id": 5,
"type": "electronic",
"name": "HDMI cable",
"specs": {
"length": "3ft"
}
},
{
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
]
products
$products =
{
"products": 3,
"per_page": 10,
"current_page": 1,
"data": [
{
"id": 4,
"product_type": "electronic",
"product_id": 6
},
{
"id": 6,
"type": "electronic",
"product_id": 5
},
{
"id": 9,
"type": "kitchen",
"product_id": 4
}
]
}
productsFinal ($productData merged into $products - based on matching combo of product_id/product_id and type/product_type)
$productsFinal =
{
"products": 3,
"per_page": 10,
"current_page": 1,
"data": [
{
"id": 4,
"product_type": "electronic",
"product_id": 6,
// How to merge product data and wrap with "data" key
"data": {
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
},
{
"id": 6,
"type": "electronic",
"product_id": 5,
// How to merge product data and wrap in "data" key
"data": {
"product_id": 5,
"type": "electronic",
"name": "HDMI cable",
"specs": {
"length": "3ft"
}
}
},
{
"id": 9,
"type": "kitchen",
"product_id": 4,
// How to merge product data and wrap in "data" key
"data": {
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
}
]
}
I tried different things for the outcome in a foreach loop but still cannot get it to render as intended:
foreach($productData as $productDataItem) {
// when $productDataItem.product_id == $product.product_id && $productDataItem.type == $product.product_type
// move the matching $productDataItem object into matching $product object, wrapped in a new "data" key
}
I don't know Laravel too well. However you can join your data objects quite easily:
<?php
$productData = json_decode('[
{
"product_id": 4,
"type": "electronic",
"name": "monitor",
"specs": {
"HDMI": true,
"VGA": false
}
},
{
"product_id": 5,
"type": "electronic",
"name": "HDMI cable",
"specs": {
"length": "3ft"
}
},
{
"product_id": 6,
"type": "kitchen",
"name": "spoon"
}
]');
$products = json_decode('{
"products": 3,
"per_page": 10,
"current_page": 1,
"data": [
{
"id": 4,
"type": "electronic",
"product_id": 6
},
{
"id": 6,
"type": "electronic",
"product_id": 5
},
{
"id": 9,
"type": "kitchen",
"product_id": 4
}
]
}');
// combine both data objects
foreach($products->data As &$p) {
foreach($productData As $d) {
if(property_exists($p, "product_id") && property_exists($d, "product_id") && property_exists($p, "type") && property_exists($d, "type")) {
if($p->product_id==$d->product_id && $p->type==$d->type) {
//$p = (object) array_merge((array) $p, (array) $d);
$p->data = $d; // updated answer
continue;
}
}
}
}
echo("<pre>");
echo json_encode($products, JSON_PRETTY_PRINT);
?>
You can test the code here: http://sandbox.onlinephpfunctions.com/code/98a50c35ee32c30f0d2be1661f7afb5895174cbe
Update: http://sandbox.onlinephpfunctions.com/code/aeebfdcf4f4db5e960260e931982570cfed19e0e
I would suggest to check this package dingo/api. I assume you want to display some kind of JSON response. Take a look at Transformers. You can do something like this :
<?php
namespace App\Http\Transformers;
use App\Http\Controllers\ProductData;
use League\Fractal\TransformerAbstract;
class ProductsDataTransformer extends TransformerAbstract
{
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(ProductData $productdata)
{
return [
'id' => $productdata->id,
'product_type' => $productdata->product_type,
'product /*or data*/' => Product::find($productdata->product_id),
];
}
}
This would find the product by it's ID and look like this :
{
"id": 4,
"product_type": "electronic",
"product" {
"product_id": 6,
"type": "kitchen",
"name": "spoon"
},
},
You can then also create a transformer for Product to take care of your specs attribute to do the same thing.
I have an associative array object :
[ {
"id": 15,
"owner_id": 1,
"container_info": {
"id": 1,
"container_id": 15
},
"filters": [
{
"id": 3,
"parent_id": null
},
{
"id": 6,
"parent_id": null
}
],
"children_recursive": [
{
"id": 7,
"owner_id": 1,
"container_info": null,
"filters": [
],
"children_recursive": [
{
"id": 8,
"owner_id": 1,
"container_info": null,
"filters": [
],
"children_recursive": [
]
}
]
},
{
"id": 16,
"owner_id": 1,
"container_info": {
"id": 2,
"container_id": 16
},
"filters": [
],
"children_recursive": [
]
},
]
}
]
I want to recursively loop through all object and their children_recursive key. And each children_recursive object (at any depth) needs to be processed
So I used :
public function traverseContainerRecursively($containerItems)
{
Log:info(' CHECK 1');
foreach ($containerItems as $containerItem) {
Log::info(json_encode($containerItem->id));
Log::info(json_encode($containerItem->owner_id));
Log::info(json_encode($containerItem->container_info));
Log::info(json_encode($containerItem->children_recursive));
}
Log::info(' CHECK 2');
foreach ($containerItems as $containerItem) {
Log::info(json_encode($containerItem['id']));
Log::info(json_encode($containerItem['owner_id']));
Log::info(json_encode($containerItem['container_info']));
Log::info(json_encode($containerItem['children_recursive']));
}
Log::info(' CHECK 3');
foreach ($containerItems as $key=>$value) {
if( $key == "children_recursive" ) {
Log::info(json_encode($value));
$this->traverseContainerRecursively($value);
}
} //foreach end
}
OUTPUT :
CHECK 1
15
1
null
null
CHECK 2
15
1
null
null
CHECK 3
{
"id": 15,
"owner_id": 1,
"container_info": {
"id": 1,
"container_id": 15
},
"filters": [
{
"id": 3,
"parent_id": null
},
{
"id": 6,
"parent_id": null
}
],
"children_recursive": [
{
"id": 7,
"owner_id": 1,
"container_info": null,
"filters": [
],
"children_recursive": [
{
"id": 8,
"owner_id": 1,
"container_info": null,
"filters": [
],
"children_recursive": [
]
}
]
},
{
"id": 16,
"owner_id": 1,
"container_info": {
"id": 2,
"container_id": 16
},
"filters": [
],
"children_recursive": [
]
},
]
} // i.e. the entire passed object
So I am unable to retrieve the value for key "children_recursive".
Please guide.
I figured out the issue.
Following code worked:
$obj = json_decode($containerItem);
Log::info(json_encode($obj->children_recursive));
I'm using FOSElasticaBundle with Symfony2 on my project and there are entry and user tables on MySQL database and each entry belongs to one user.
I want to get just one entry per a user among the whole entries from the database.
Entries Representation
[
{
"id": 1,
"name": "Hello world",
"user": {
"id": 17,
"username": "foo"
}
},
{
"id": 2,
"name": "Lorem ipsum",
"user": {
"id": 15,
"username": "bar"
}
},
{
"id": 3,
"name": "Dolar sit amet",
"user": {
"id": 17,
"username": "foo"
}
},
]
Expected result is:
[
{
"id": 1,
"name": "Hello world",
"user": {
"id": 17,
"username": "foo"
}
},
{
"id": 2,
"name": "Lorem ipsum",
"user": {
"id": 15,
"username": "bar"
}
}
]
But it returns all entries on table. I've tried to add an aggregation to my elasticsearch query and nothing changed.
$distinctAgg = new \Elastica\Aggregation\Terms("distinctAgg");
$distinctAgg->setField("user.id");
$distinctAgg->setSize(1);
$query->addAggregation($distinctAgg);
Is there any way to do this via term filter or anything else? Any help would be great. Thank you.
Aggregations are not easy to understand when you are used to MySQL group by.
The first thing, is that aggregations results are not returned in hits, but in aggregations. So when you get the result of your search, you have to get aggregations like that :
$results = $search->search();
$aggregationsResults = $results->getAggregations();
The second thing is that aggregations wont return you the source. With the aggregation of your example, you will only know that you have 1 user with ID 15, and 2 users with ID 15.
E.g. with this query :
{
"query": {
"match_all": {}
},
"aggs": {
"byUser": {
"terms": {
"field": "user.id"
}
}
}
}
Result:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 1,
"hits": [ ... ]
},
"aggregations": {
"byUser": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 17,
"doc_count": 2
},
{
"key": 15,
"doc_count": 1
}
]
}
}
}
If you want to get results, the same way you would do with a GROUP BY in MySQL, you have to use a top_hits sub-aggregation:
{
"query": {
"match_all": {}
},
"aggs": {
"byUser": {
"terms": {
"field": "user.id"
},
"aggs": {
"results": {
"top_hits": {
"size": 1
}
}
}
}
}
}
Result:
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 1,
"hits": [ ... ]
},
"aggregations": {
"byUser": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 17,
"doc_count": 2,
"results": {
"hits": {
"total": 2,
"max_score": 1,
"hits": [
{
"_index": "test_stackoverflow",
"_type": "test1",
"_id": "1",
"_score": 1,
"_source": {
"id": 1,
"name": "Hello world",
"user": {
"id": 17,
"username": "foo"
}
}
}
]
}
}
},
{
"key": 15,
"doc_count": 1,
"results": {
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "test_stackoverflow",
"_type": "test1",
"_id": "2",
"_score": 1,
"_source": {
"id": 2,
"name": "Lorem ipsum",
"user": {
"id": 15,
"username": "bar"
}
}
}
]
}
}
}
]
}
}
}
More informations on this page : https://www.elastic.co/blog/top-hits-aggregation