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
Related
I want to modify nested collection using group by.
This is sample collection
"document": [
{
"id": 1,
"company_id": 4,
"client_id": 1,
"status": 1,
"client": {
"id": 1,
"company_id": 4,
"name": "1663159185735-client"
},
"document_items": [
{
"id": 1,
"master_id": 5,
"text_value": "piyo",
},
{
"id": 2,
"master_id": 5,
"text_value": "fuga",
},
{
"id": 3,
"master_id": 3,
"text_value": "hoge",
}
]
}
]
I want to change like this.
"document": [
{
"id": 1,
"company_id": 4,
"client_id": 1,
"status": 1,
"client": {
"id": 1,
"company_id": 4,
"name": "1663159185735-client"
},
"document_items": [
5: [{
"id": 1,
"master_id": 5,
"text_value": "piyo",
},
{
"id": 2,
"master_id": 5,
"text_value": "fuga",
}
],
3: [{
"id": 2,
"master_id": 5,
"text_value": "fuga",
}
]
]
}
]
I try write below code;
$result->map(function ($v){
$v->documentItems = $v->documentItems->groupBy('master_id');
return $v;
});
but output key is documentItems not document_items
I changed to
$v->document_items = $v->documentItems->groupBy('master_id');
key is drawing_drawing_items but not groupby(simple array)
how to modify group by and preserve key case?
I changed to
$v->document_items = $v->documentItems->groupBy('master_id');
key is drawing_drawing_items but not groupby(simple array)
You should also change like this $v->document_items->groupBy('master_id')
The key is 'document_items' not 'documentItems'
Task: you need to get a students object, with the conditions "group_id*" and "specialty_*id", where all these 3 tables are connected. It is also necessary to paginate only those objects of students for whom a group and specialty was found.
My problem: if the group or specialty data is not found, then the object is still added with the "group: null" attribute to pagination, thereby failing to display the requested number of elements on the page. How can this problem be solved?
The code:
$students = Student::with([
"group" =>
function($query) {
$query->where("id", \request("group_id"));
},
"group.speciality" =>
function($query) {
$query->where("id", \request("speciality_id"));
},
])->paginate(\request("page_size") ? : 10)->toArray();
return response()->json($students);
Preview: when I make a request with parameters
group_id = 2
speciality_id = 2
page_size = 2
The following object is returned:
{
"current_page": 1,
"data": [
{
"id": 1,
"receipt_date": "2010-11-02",
"user": {
"id": 1,
"login": "ykirillova#gmail.com",
"phone": "+7 (922) 472-9240",
"role": "user",
"passport": {
"series": 1762,
"number": 384282,
"date_of_issue": "1991-11-27",
"issued": "magni",
"division_code": 3,
"scan": "*photo link*",
"secondname": "Kilikova",
"firstname": "Olga",
"thirdname": "Anisimova",
"birthday": "1973-05-13",
"sex": "W"
}
},
"group": {
"id": 2,
"group_code": "4433",
"speciality": {
"id": 2,
"specialty_title": "Programming in computer systems",
"faculty": "SPO IKTZI"
}
}
},
{
"id": 2,
"receipt_date": "1973-11-07",
"user": {
"id": 2,
"login": "marta.fedorov#dackov.net",
"phone": "+7 (922) 903-0339",
"role": "user",
"passport": {
"series": 8241,
"number": 419233,
"date_of_issue": "1980-06-05",
"issued": "quos",
"division_code": 33,
"scan": "*photo link*",
"secondname": "Efremov",
"firstname": "Boleslav",
"thirdname": "Kostin",
"birthday": "2009-04-03",
"sex": "W"
}
},
"group": null
}
],
"per_page": "2",
"total": 75
}
Whereas with group == null, only 2 objects with groups should be returned
{
"current_page": 1,
"data": [
{
"id": 1,
"receipt_date": "2010-11-02",
"user": {
"id": 1,
"login": "ykirillova#gmail.com",
"phone": "+7 (922) 472-9240",
"role": "user",
"passport": {
"series": 1762,
"number": 384282,
"date_of_issue": "1991-11-27",
"issued": "magni",
"division_code": 3,
"scan": "*photo link*",
"secondname": "Kilikova",
"firstname": "Olga",
"thirdname": "Anisimova",
"birthday": "1973-05-13",
"sex": "W"
}
},
"group": {
"id": 2,
"group_code": "4433",
"speciality": {
"id": 2,
"specialty_title": "Programming in computer systems",
"faculty": "SPO IKTZI"
}
}
},
{
"id": 5,
"receipt_date": "2002-07-05",
"user": {
"id": 5,
"login": "tester#mail.ru",
"phone": "+7 (800) 555-3535",
"role": "user",
"passport": {
"series": 5521,
"number": 866521,
"date_of_issue": "1980-06-05",
"issued": "quos",
"division_code": 33,
"scan": "*photo link*",
"secondname": "Pavlov",
"firstname": "Denis",
"thirdname": "Artemev",
"birthday": "2009-04-03",
"sex": "W"
}
},
"group": {
"id": 2,
"group_code": "4433",
"speciality": {
"id": 2,
"specialty_title": "Programming in computer systems",
"faculty": "SPO IKTZI"
}
}
}
],
"per_page": "2",
"total": 75
}
The data presented is irrelevant as it is randomly generated.
Removing data elements in a loop with group equal to null does not solve the problem, since pagination gets lost.
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.
Suppose I have stored bellow data and want to search for term xy in old_value and new_value fields of those documents that their field_name is curriculum_name_en or curriculum_name_pr:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 98,
"max_score": 1,
"hits": [
{
"_index": "my_index",
"_type": "audit_field",
"_id": "57526c197e83c",
"_score": 1,
"_source": {
"session_id": 119,
"trans_seq_no": 1,
"table_seq_no": 1,
"field_id": 2,
"field_name": "curriculum_id",
"new_value": 118,
"old_value": null
}
},
{
"_index": "my_index",
"_type": "audit_field",
"_id": "57526c197f2c3",
"_score": 1,
"_source": {
"session_id": 119,
"trans_seq_no": 1,
"table_seq_no": 1,
"field_id": 3,
"field_name": "curriculum_name_en",
"new_value": "Test Index creation",
"old_value": null
}
},
{
"_index": "my_index",
"_type": "audit_field",
"_id": "57526c198045c",
"_score": 1,
"_source": {
"session_id": 119,
"trans_seq_no": 1,
"table_seq_no": 1,
"field_id": 4,
"field_name": "curriculum_name_pr",
"new_value": null,
"old_value": null
}
},
{
"_index": "my_index",
"_type": "audit_field",
"_id": "57526c1981512",
"_score": 1,
"_source": {
"session_id": 119,
"trans_seq_no": 1,
"table_seq_no": 1,
"field_id": 5,
"field_name": "curriculum_name_pa",
"new_value": null,
"old_value": null
}
}
]
}
}
and many more fields may be there, now user may select one or more of those fields and define a search term across those fields that he/she selected, the challenge is here, how we can say elastic that consider field_name to match those fields that user selected, then search in old_value, and new_value.
for example if user select curriculum_name_en and curriculum_name_pr and then want to search for xy inside old_value and new_value fields of those documents that their field_name is above fields.
how we can do that?
The idea with this requirement is that you need to make something like: the query needs to match new_value and/or old_value only if field_name matches a certain value as well. There is no programmatic-like way of saying if this then that.
What I'm suggesting is something like this:
{
"query": {
"bool": {
"must": [
{
"terms": {
"field_name": [
"curriculum_name_en",
"curriculum_name_pr"
]
}
},
{
"multi_match": {
"query": "Test Index",
"fields": ["new_value","old_value"]
}
}
]
}
}
}
So, your if this then that condition is a must statement from a bool query where your if and then branches live inside the must.
This may solve your problem
{
"query": {
"filtered": {
"filter": {
"and": [
{
"query" : {
"terms" : {
"field_name" : [
"curriculum_name_en",
"curriculum_name_pr"
],
"minimum_match" : 1
}
}
},
{
"query" : {
"terms" : {
"new_value" : [
"test", "index"
],
"minimum_match" : 1
}
}
}
]
}
}
}
}
}
The situation is I have two arrays that is collecting JSON data via the API :
$players = getAPI("http://xx.xxx.xxx.xx:xxxxx/players.json?apiKey=xxxxxxxxxxxxxxxxxxxxxxxx");
$recents = getAPI("xx.xxx.xxx.xx:xxxxx/recent.json?apiKey=xxxxxxxxxxxxxxxxxxxxxxxx");
The method is getting the contents and decoding the JSON into an array.
For the players array we have this data in an array:
$players
[
{
"id": "76561198033377272",
"name": "PitMonk",
"position": {
"x": -339,
"y": 26,
"z": 191
},
"rotation": 128,
"time": 418310,
"ip": "",
"inventory": {
"main": [],
"belt": [
{
"name": "rock",
"amount": 1,
"blueprint": false,
"condition": 100
},
{
"name": "torch",
"amount": 1,
"blueprint": false,
"condition": 100
}
],
"wear": []
}
},
{
"id": "76561198088638439",
"name": "Pippa",
"position": {
"x": -337,
"y": 25,
"z": 177
},
"rotation": 73,
"time": 419136,
"ip": "",
"inventory": {
"main": [
{
"name": "arrow.wooden",
"amount": 12,
"blueprint": false
},
{
"name": "bow.hunting",
"amount": 1,
"blueprint": false,
"condition": 93
},
{
"name": "blueprint_fragment",
"amount": 25,
"blueprint": false
},
{
"name": "metal.fragments",
"amount": 1366,
"blueprint": false
},
{
"name": "metal.refined",
"amount": 48,
"blueprint": false
},
{
"name": "charcoal",
"amount": 1120,
"blueprint": false
},
{
"name": "lowgradefuel",
"amount": 738,
"blueprint": false
}
],
"belt": [
{
"name": "rock",
"amount": 1,
"blueprint": false,
"condition": 100
},
{
"name": "torch",
"amount": 1,
"blueprint": false,
"condition": 100
},
{
"name": "pickaxe",
"amount": 1,
"blueprint": false,
"condition": 76
},
{
"name": "pickaxe",
"amount": 1,
"blueprint": false,
"condition": 17
},
{
"name": "pickaxe",
"amount": 1,
"blueprint": false,
"condition": 100
},
{
"name": "pickaxe",
"amount": 1,
"blueprint": false,
"condition": 100
}
],
"wear": [
{
"name": "burlap.shirt",
"amount": 1,
"blueprint": false
},
{
"name": "attire.hide.skirt",
"amount": 1,
"blueprint": false
}
]
}
}
]
$recents
[
{
"id": "76561198039206786",
"name": "JakeGroves"
},
{
"id": "76561198088638439",
"name": "Pippa"
},
{
"id": "76561198033377272",
"name": "PitMonk"
},
{
"id": "76561198146864439",
"name": "YepWellDone"
},
{
"id": "76561198164836207",
"name": "Baz"
},
{
"id": "76561198076406281",
"name": "xwalnutx"
},
{
"id": "76561197985716090",
"name": "Darkflame134"
},
{
"id": "76561198263423842",
"name": "XitaikiznerX"
},
{
"id": "76561198129952244",
"name": "NatanGamer"
},
{
"id": "76561198071842055",
"name": "Baha Bey"
}
]
As you can see the players is the people connected, and recents is the total list of people who have connected recently.
I have attempted this:
foreach ($players as $player) {
echo $players->name;
}
echo "</br></br>";
foreach ($recent as $rec) {
if ($rec->name != $player->name) {
echo $rec->name . "</br>";
}
}
and it produces the result:
PitMonk Pippa
JakeGroves
PitMonk
YepWellDone
Baz
xwalnutx
Darkflame134
XitaikiznerX
NatanGamer
Baha Bey
So it is only ignoring 'pippa', I am not sure if it is possible to interact with two arrays as such for unique values?
You are interested in listing all names out of $users which don't exist in $players or $recents, right?
Assuming you are only interested in the name:
// First, let's get a new array with all names from both arrays (can contain dups)
$pcNames = array_map($players + $recents, function($playerObject) {
return $playerObject->name;
});
// Next, let's remove all dups
$pcNames = array_unique($pcNames);
// === At this point you have an array with all names from `$players` and `$recents` ===
// === You may do something else with those, but I'll now create another array with ===
// === all users not in the players/recents lists. ===
// Now let's also get a list of names of users in the `$users` variable
$userNames = array_map($users, function($playerObject) {
return $playerObject->name;
});
// And finally let's get all names which are not in players or recents
$diffNames = array_diff($userNames, $pcNames);
// Let's output those to see whether it worked
var_dump($diffNames);
Of course there are other ways depending on your use case. We could for example extract the names of all three arrays and then just use array_diff with 3 arguments (but then you don't have the $pcArray side product), or if you actually want to compare IDs but print names, we would have to change all the inline functions to extract ID instead of name and further down reference the users array to get the actual name, etc.