Yii2's mongodb collection - get all content of nested arrays - php

I have a Yii2 mongodb's collection that looks like this:
{
"_id" : "123",
"books" : [
{
"author" : "John",
"title" : "The Book"
},
{
"author" : "Smith",
"title" : "Something!"
}
]
}
{
"_id" : "321",
"books" : [
{
"author" : "John",
"title" : "The Book"
}
]
}
...
And I want to get an array of all books (an array of arrays basically):
[
{
"author" : "John",
"title" : "The Book"
},
{
"author" : "Smith",
"title" : "Something!"
},
{
"author" : "John",
"title" : "The Book"
}
...
]
I saw answers to close questions, but they all achieve something a bit different.
Also tried $collection->distinct('books', [], []) and it worked, but it removed duplicates which is unacceptable.

Use this MongoDB query to get this resultset
db.collection.aggregate([
{ $unwind : "$books"},
{ $group: {
_id: null,
items:
{ $push: "$books" }
}}
]);

Let's try like this way using foreach()?
<?php
$json = '[{"_id":"123","books":[{"author":"John","title":"The Book"},{"author":"Smith","title":"Something!"}]},{"_id":"321","books":[{"author":"John","title":"The Book"}]}]';
$array = json_decode($json, 1);
$ids = [];
foreach($array as $v) {
foreach($v['books'] as $book){
$books[] = $book;
}
}
echo json_encode($books);
?>
Output:
[{"author":"John","title":"The Book"},{"author":"Smith","title":"Something!"},{"author":"John","title":"The Book"}]
DEMO: https://3v4l.org/hdJ8H

Related

Elasticsearch - excluding children from documents with join field

So I've set up an index with the following mapping:
PUT test_index
{
"mappings": {
"doc": {
"properties": {
"title": {
"type": "text"
},
"author": {
"type": "text"
},
"reader_stats": {
"type": "join",
"relations": {
"book": "reader"
}
}
}
}
}
}
each parent document represents a book and its children represent a reader of that book. However, if I was to run:
GET test_index/_search
{
"query":{"match_all":{}}
}
The results would be populated with both books and readers like so:
"hits" : [
{
"_index" : "test_index",
"_type" : "doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"title" : "my second book",
"author" : "mr author",
"reader_stats" : {
"name" : "book"
}
}
},
{
"_index" : "test_index",
"_type" : "doc",
"_id" : "7",
"_score" : 1.0,
"_routing" : "2",
"_source" : {
"name" : "michael bookworm",
"clicks" : 1,
"reader_stats" : {
"name" : "reader",
"parent" : 2
}
}
}
]
Is there some way I can exclude reader documents and only show books? I already used match_all in my app to grab books so it would be good if I can avoid having to change that query but I guess that's not possible.
Also I'm a bit confused as to how mappings work with join fields as there is no definition for what fields are required of child documents. For example, in my mapping there's nowhere to specify that 'reader' documents must have 'name' and 'clicks' fields. Is this correct?
You need to use has_child (to search only parent docs) and has_parent (to search only child docs) keywords in your query.
Is there some way I can exclude reader documents and only show books?
YES
Your query will be:
GET test_index/_search
{
"query": {
"has_child": {
"type": "reader",
"query": {
"match_all": {}
}
}
}
}
For more detail info you can take a look at here:
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-has-child-query.html

Parse json with PHP. Getting Undefined property: stdClass errors

I am having some issues parsing a json file from Jenkins using PHP
{
"actions" : [
{
"causes" : [
{
"shortDescription" : "Started by an SCM change"
}
]
},
{
},
{
},
{
"buildsByBranchName" : {
"origin/release_5.6.0" : {
"buildNumber" : 242,
"buildResult" : null,
"marked" : {
"SHA1" : "fde4cfd86b8511d328037b9e9c55876007bb6e67",
"branch" : [
{
"SHA1" : "fde4cfd86b8511d328037b9e9c55876007bb6e67",
"name" : "origin/release_5.6.0"
}
]
},
"revision" : {
"SHA1" : "fde4cfd86b8511d328037b9e9c55876007bb6e67",
"branch" : [
{
"SHA1" : "fde4cfd86b8511d328037b9e9c55876007bb6e67",
"name" : "origin/release_5.6.0"
}
]
}
},
"origin/release_5.7.0" : {
"buildNumber" : 315,
"buildResult" : null,
"marked" : {
"SHA1" : "ae2cbf69a25e0632e0f1d3eeb27a907b154efce0",
"branch" : [
{
"SHA1" : "ae2cbf69a25e0632e0f1d3eeb27a907b154efce0",
"name" : "origin/release_5.7.0"
}
]
},
"revision" : {
"SHA1" : "ae2cbf69a25e0632e0f1d3eeb27a907b154efce0",
"branch" : [
{
"SHA1" : "ae2cbf69a25e0632e0f1d3eeb27a907b154efce0",
"name" : "origin/release_5.7.0"
}
]
}
},
I have tried doing the following
//Read in JSON object
$json_file2 = file_get_contents('url.com/json');
//Decode JSON file
$test = json_decode($json_file2); //object
//print_r($json_file2);
echo $test->causes;
I am also trying to access the different sections in "buildsByBranchName". I have tried many different variations of the code above, but I keep getting "Undefined property: stdClass" errors.
You are not accessing that value properly. causes resides under actions which is an array. Your code also won't work because causes is an array.
// This is an array so you can't use echo here.
$causes = $test->actions[0]->causes;
// echo out the shortDescription
echo $causes[0]->shortDescription;
or
echo $test->actions[0]->causes[0]->shortDescription;

Immense term error in elasticsearch

I'm working on a membership administration program, for wich we want to use Elasticsearch as search engine. At this point we're having problems with indexing certain fields, because they generate an 'immense term'-error on the _all field.
Our settings:
curl -XGET 'http://localhost:9200/my_index?pretty=true'
{
"my_index" : {
"aliases" : { },
"mappings" : {
"Memberships" : {
"_all" : {
"analyzer" : "keylower"
},
"properties" : {
"Amount" : {
"type" : "float"
},
"Members" : {
"type" : "nested",
"properties" : {
"Startdate membership" : {
"type" : "date",
"format" : "dateOptionalTime"
},
"Enddate membership" : {
"type" : "date",
"format" : "dateOptionalTime"
},
"Members" : {
"type" : "string",
"analyzer" : "keylower"
}
}
},
"Membership name" : {
"type" : "string",
"analyzer" : "keylower"
},
"Description" : {
"type" : "string",
"analyzer" : "keylower"
},
"elementId" : {
"type" : "integer"
}
}
}
},
"settings" : {
"index" : {
"creation_date" : "1441310632366",
"number_of_shards" : "1",
"analysis" : {
"filter" : {
"my_char_filter" : {
"type" : "asciifolding",
"preserve_original" : "true"
}
},
"analyzer" : {
"keylower" : {
"filter" : [ "lowercase", "my_char_filter" ],
"tokenizer" : "keyword"
}
}
},
"number_of_replicas" : "1",
"version" : {
"created" : "1040599"
},
"uuid" : "nn16-9cTQ7Gn9NMBlFxHsw"
}
},
"warmers" : { }
}
}
We use the keylower-analyzer, because we don't want the fullname to be split on whitespace. This is because we want to be able to search on 'john johnson' in the _all field as well as in the 'Members'-field.
The 'Members'-field can contain multiple members, wich is where the problems start. When the field only contains a couple of members (as in the example below), there is no problem. However, the field may contain hundreds or thousands of members, wich is when we get the immens term error.
curl 'http://localhost:9200/my_index/_search?pretty=true&q=*:*'
{
"took":1,
"timed_out":false,
"_shards":{
"total":1,
"successful":1,
"failed":0
},
"hits":{
"total":1,
"max_score":1.0,
"hits":[
{
"_index":"my_index",
"_type":"Memberships",
"_id":"15",
"_score":1.0,
"_source":{
"elementId":[
"15"
],
"Membership name":[
"My membership"
],
"Amount":[
"100"
],
"Description":[
"This is the description."
],
"Members":[
{
"Members":"John Johnson",
"Startdate membership":"2015-01-09",
"Enddate membership":"2015-09-03"
},
{
"Members":"Pete Peterson",
"Startdate membership":"2015-09-09"
},
{
"Members":"Santa Claus",
"Startdate membership":"2015-09-16"
}
]
}
}
]
}
}
NOTE: The above example works! It's only when the field 'Members' contains (a lot) more members that we get the error. The error we get is:
"error":"IllegalArgumentException[Document contains at least one
immense term in field=\"_all\" (whose UTF8 encoding is longer than the
max length 32766), all of which were skipped. Please correct the
analyzer to not produce such terms. The prefix of the first immense
term is: '[...]...', original message: bytes can be at most 32766 in
length; got 106807]; nested: MaxBytesLengthExceededException[bytes can
be at most 32766 in length; got 106807]; " "status":500
We only get this error on the _all-field, not on the original Members-field. With ignore_above, it's not possible to search in the _all field on fullname anymore. With the standard analyzer, i would find this document if i would search on 'Santa Johnson', because the _all-fields has a token 'Santa' and 'Johnson'. That's why i use keylower for these fields.
What i would like is an analyzer that tokenizes on field, but doesn't break up the values in the fields itself. What happens now, is that the entire field 'Members' is being fed as one token, including the childfields. (so, the token in the example above would be:
John Johnson 2015-01-09 2015-09-03 Pete Peterson 2015-09-09 Santa Claus 2015-09-16
Is it possible to tokenize these fields in such a way that every field is being fed to _all as separate tokens, but without breaking up the values in the fields themself? So that the tokens would be:
John Johnson
2015-01-09
2015-09-03
Pete Peterson
2015-09-09
Santa Claus
2015-09-16
Note: We use the Elasticsearch php library.
There is a much better way of doing this. Whether or not the phrase search can span multiple field values is determined by position_offset_gap (in 2.0 it will be renamed into position_increment_gap). This parameter basically specifies how many words/positions should be "inserted" between the last token of one field and the first token of the following fields. By default, in elasticsearch prior to 2.0 position_increment_gap has value of 0. That's is what causing the issues that you describe.
By combining copy_to feature and specifying position_increment_gap you can create an alternative my_all field that will not have this issue. By setting this new field in index.query.default_field setting you can tell elasticsearch to use this field by default instead of _all field when no fields are specified.
curl -XDELETE "localhost:9200/test-idx?pretty"
curl -XPUT "localhost:9200/test-idx?pretty" -d '{
"settings" :{
"index": {
"number_of_shards": 1,
"number_of_replicas": 0,
"query.default_field": "my_all"
}
},
"mappings": {
"doc": {
"_all" : {
"enabled" : false
},
"properties": {
"Members" : {
"type" : "nested",
"properties" : {
"Startdate membership" : {
"type" : "date",
"format" : "dateOptionalTime",
"copy_to": "my_all"
},
"Enddate membership" : {
"type" : "date",
"format" : "dateOptionalTime",
"copy_to": "my_all"
},
"Members" : {
"type" : "string",
"analyzer" : "standard",
"copy_to": "my_all"
}
}
},
"my_all" : {
"type": "string",
"position_offset_gap": 256
}
}
}
}
}'
curl -XPUT "localhost:9200/test-idx/doc/1?pretty" -d '{
"Members": [{
"Members": "John Johnson",
"Startdate membership": "2015-01-09",
"Enddate membership": "2015-09-03"
}, {
"Members": "Pete Peterson",
"Startdate membership": "2015-09-09"
}, {
"Members": "Santa Claus",
"Startdate membership": "2015-09-16"
}]
}'
curl -XPOST "localhost:9200/test-idx/_refresh?pretty"
echo
echo "Should return one hit"
curl "localhost:9200/test-idx/doc/_search?pretty=true" -d '{
"query": {
"match_phrase" : {
"my_all" : "John Johnson"
}
}
}'
echo
echo "Should return one hit"
curl "localhost:9200/test-idx/doc/_search?pretty=true" -d '{
"query": {
"query_string" : {
"query" : "\"John Johnson\""
}
}
}'
echo
echo "Should return no hits"
curl "localhost:9200/test-idx/doc/_search?pretty=true" -d '{
"query": {
"match_phrase" : {
"my_all" : "Johnson 2015-01-09"
}
}
}'
echo
echo "Should return no hits"
curl "localhost:9200/test-idx/doc/_search?pretty=true" -d '{
"query": {
"query_string" : {
"query" : "\"Johnson 2015-01-09\""
}
}
}'
echo
echo "Should return no hits"
curl "localhost:9200/test-idx/doc/_search?pretty=true" -d '{
"query": {
"match_phrase" : {
"my_all" : "Johnson Pete"
}
}
}'

ElasticSearch PHP - get list of 10 (most recent) items in type/index

I have a index called publications_items and type called "publication".
I'd like to have the 10 most recent added publications. (some matchAll principle)
I'm using ElasticSearch PHP (http://www.elasticsearch.org/guide/en/elasticsearch/client/php-api/current/_quickstart.html)
Basically i'm just doing a default get but i would not know how to do this in ElasticSearchPHP.
In sense.qbox.io i do :
POST /publications_items/_search { "query": {
"match_all": {} } }
and it works fine
mapping:
PUT /publications_items/ { "mappings": {
"publication": {
"properties": {
"title": {
"type": "string"
},
"url": {
"type": "string"
},
"description": {
"type": "string"
},
"year": {
"type": "integer"
},
"author": {
"type": "string"
}
}
} } }
You need to enable "_timestamp" mapping:
PUT /test/doc/_mapping
{
"_timestamp": {
"enabled": "true",
"store": "true"
}
}
And in your search query you need to sort by it and retrieve the first 10 documents:
GET /test/_search
{
"sort" : {
"_timestamp" : { "order" : "desc" }
},
"from" : 0, "size" : 10
}
And specifically in Elasticsearch PHP:
mappings change:
require 'vendor/autoload.php';
$client = new Elasticsearch\Client();
$params = array();
$params2 = [
'_timestamp' => [
'enabled' => 'true',
'store' => 'true'
]
];
$params['index']='test';
$params['type']='doc';
$params['body']['doc']=$params2;
$client->indices()->putMapping($params);
query:
require 'vendor/autoload.php';
$client = new Elasticsearch\Client();
$json = '{
"sort" : {
"_timestamp" : { "order" : "desc" }
},
"from" : 0, "size" : 10
}';
$params['index'] = 'test';
$params['type'] = 'doc';
$params['body'] = $json;
$results = $client->search($params);
echo json_encode($results, JSON_PRETTY_PRINT);

Parsing JSON results

I understand how to parse json with PHP, however I don't understand how to read it with the eye. Can someone please help me understanad this?
Here is my code
<?php
$json = file_get_contents('json.txt');
$json_output = json_decode($json);
foreach ( $json_output->query as $stf )
{
echo "{$stf->response->domains->name}\n";
}
?>
Here is a sample of the json result
{ "query" : { "host" : "test.com",
"tool" : "pro"
},
"response" : { "domain_count" : "13",
"domains" : [ { "last_resolved" : "2012-01-11",
"name" : "test1.com"
},
{ "last_resolved" : "2012-01-11",
"name" : "test2.com"
},
As you can see I tried query->response->domains->name and it didn't work.
How would I tried name?
Thank you in advance
query->response->domains is an indexed array, so you need to get an index, say [0], and then get the ->name from that.
echo $stf->response->domains[0]->name."\n";
foreach ( $json_output->query->response->domains as $domain )
{
echo $domain->name;
}
Study this http://json.org/
If you're trying to read it by eye, it might help to reformat:
{
"query" : {
"host" : "test.com",
"tool" : "pro"
},
"response" : {
"domain_count" : "13",
"domains" : [{
"last_resolved" : "2012-01-11",
"name" : "test1.com"
},{
"last_resolved" : "2012-01-11",
"name" : "test2.com"
}]
}
}

Categories