If I have this model :
$result =
[
{
"date":"1/1/2021",
"actions":[
{
"id":1,
"title:"test",
"sub_actions":[
{
"id":1
}
{
"id":2
}
]
}
],
"date":"2/1/2021",
"actions":[
{
"id":2,
"title:"test2",
"sub_actions":[
{
"id":1
}
{
"id":2
}
]
}
]
}
]
I want if in some of these dates the action count be zero for some reason to not display both (date and actions) in the list?
something like this :
if($result->actions->count() == 0)
I'm not sure if that's the output of a query, but for the general case, if you had a json looking like
[
{
"date": "1/1/2021",
"actions": [
{
"id": 1,
"title": "test",
"sub_actions": [
{
"id": 1
},
{
"id": 2
}
]
}
]
},
{
"date": "2/1/2021",
"actions": [
]
}
]
and you decoded into a variable like so
$resultArray = json_decode(
'[{"date":"1/1/2021","actions":[{"id":1,"title":"test","sub_actions":[{"id":1},{"id":2}]}]},{"date":"2/1/2021","actions":[]}]'
);
you could turn that to a collection and then apply filter on it
$recordsWithActions = collect($resultArray)->filter(fn($row)=> count($row->actions))->all();
or
$recordsWithActions = collect($resultArray)->filter(
function($row) {
// it would be $row['actions'] if you did json_decode(...,true)
return count($row->actions)>0;
}
)->all();
I left you the example using arrays instead of objects at Laravel Playground
Edit: Filtering on nested properties
Let's leave the json encoding and decoding aside for a minute. You have an array of soccer fixtures, each of which can have zero or more matches, and each match in turn can have zero or more predects (predictions?).
You want to display only matches with more than zero predects. This will prune matches without said property and if the pruned matches array is empty, then the parent fixture should be pruned from the final result as well.
The Collecttion::filter method evaluates every item for the truthyness of a condition. count($match['predect']) is zero, and zero is a falsy value, same as false, NULL, an empty string or an empty array. A non empty array would satisfy either the truthyness test by itself, or the assertion that count($array) or count($array)>0.
Checking your example, you did the equivalent of (I won't use arrow functions for the sake of clarity):
// show me the items for which the next condition is truthy
$myresults = collect($fixtures)->filter(function ($fixture) {
// matches is a collection of items for which is true that
$matches = collect($fixture['matches'])
->filter(function ($match) {
// their predect property is not empty
return count($match['predect']);
});
// this is the truthyness test to check
return $matches;
})->all();
$matches is a collection, which is not falsy no matter it doesn't have items. However, if you used, instead:
$matches = collect($fixture['matches'])
->filter(function ($match) {
// their predect property is not empty
return count($match['predect']);
})->all();
and $matches was an empty array, it would "bubble up" effectively pruning the parent fixture. If any fixture has at least one match passing the truthyness test, then it will show up in the final result. It's matches key would not be filtered, since you aren't altering their contents on the basis of the nested filter.
If you want to redeclare the matches of each fixture to prune the ones without predect, you would use the Collection::map method like so:
$fixtures_with_passing_matches = collect($fixtures)
->map(function($fixture) {
$fixture['matches'] = collect($fixture['matches'])
->filter(function ($match) {
return count($match['predect']);
})->all();
return $fixture;
})->all();
you can chain your filter method after the mapping, therefore pruning the fixtures that, after redeclaring their matches, have none left
$fixtures_with_passing_matches = collect($fixtures)
->map(function($fixture) {
$fixture['matches'] = collect($fixture['matches'])
->filter(function ($match) {
return count($match['predect']);
})->all();
return $fixture;
})->filter(function($fixture) {
return count($fixture['matches'])>0;
})->all();
So let's look at the following example array:
$fixtures = [
[
"fixture_id" => 1,
"matches" => [
[
"match_id" => 1,
"home" => "Turkey",
"away" => "Italy",
"predect" => [
[
"h_predect" => 1,
"a_predect" => 1
]
]
], [
"match_id" => 3,
"home" => "Denmark",
"away" => "Finland",
"predect" => []
]
]
], [
"fixture_id" => 2,
"matches" => [
[
"match_id" => 4,
"home" => "France",
"away" => "Norway",
"predect" => []
]
]
], [
"fixture_id" => 3,
"matches" => []
]
];
The last fixture doesn't have matches, so it will be filtered out
The second fixture has matches, but none of them has predect, therefore are filtered out, leaving the parent fixture without matchesm therefore filtering it out as well
The first fixture has matches, of which one doesn't have predect so it's filtered out. This leaved the parent fixture with just one match
The final array is
$fixtures = [
[
"fixture_id" => 1,
"matches" => [
[
"match_id" => 1,
"home" => "Turkey",
"away" => "Italy",
"predect" => [
[
"h_predect" => 1,
"a_predect" => 1
]
]
]
]
]
];
See the working example in this Laravel Playground
Related
What does this bug refer to? I have pasted the code below. Kindly have a look. Can anyone let me know what's wrong with the boundary value here? Thanks in advance
db.match_list.aggregate(
[
{
$bucket: {
groupBy: "$competition_id",
boundaries: ["9dn1m1gh41emoep","9dn1m1ghew6moep", "d23xmvkh4g8qg8n","gy0or5jhj6qwzv3"],
default: "Other",
output: {
"data" : {
$push: {
"season_id": "$season_id",
"status_id": "$status_id",
"venue_id": "$venue_id",
"referee_id": "$referee_id",
"neutral":"$neutral",
"note": "$note",
"home_scores":"$home_scores",
"away_scores": "$away_scores",
}
}
}
}
},
{
$sort : { competition_id : 1 }
},
])
mongodb laravel query using raw. Not sure what's going wrong here.new_array vale also has been mentioned
$contents = $query->orderby('competition_id')->pluck('competition_id')->toArray();
$contents = array_unique($contents);
$new_array = array_values($contents);
$data = $query->raw(function ($collection) use ($new_array) {
return $collection->aggregate([
[
'$bucket' => [
'groupBy' => '$competition_id',
'boundaries' => $new_array,
'default' => 'zzzzzzzzzzzzzzzzzzzzzzzzz',
'output' => [
"data" => [
'$push' => [
"id" => '$id',
"season_id" => '$season_id',
"status_id" => '$status_id',
"venue_id" => '$venue_id',
"referee_id" => '$referee_id',
"neutral" => '$neutral',
"note" => '$note',
"home_scores" => '$home_scores',
]
]
]
]
]
]);
});
You have entered Other in the default parameter of the $bucket parameter which
is in between the min-max boundaries you have provided.
I would suggest you try a value of the greater or lesser string than to that provided in the
boundaries array or better enter a different datatype value such as int of 1.
db.match_list.aggregate( [
{
$bucket: {
groupBy: "$competition_id",
boundaries: ["9dn1m1gh41emoep","9dn1m1ghew6moep", "d23xmvkh4g8qg8n","gy0or5jhj6qwzv3"],
default: 1, // Or `zzzzzzzzzzzzzzzzzzzzzzzzz` (any greater or lesser value than provided in `boundaries`
output: {
"data" :
{
$push: {
"season_id": "$season_id",
"status_id": "$status_id",
"venue_id": "$venue_id",
"referee_id": "$referee_id",
"neutral":"$neutral",
"note": "$note",
"home_scores":"$home_scores",
"away_scores": "$away_scores",
}
}
}
}
},
{ $sort : { competition_id : 1 } },
] )
This is a simple example of my starting point :
[
{ 'drink' => [ 'hard', 'soft' ] },
{ 'hard' => [ 'beer', 'wine' ] },
{ 'soft' => [ 'water', 'orange_juice' ] },
{ 'food' => [ 'fruit', 'veg', 'meat' ] },
{ 'fruit' => [ 'apples', 'bananas', 'pears' ] }
{ 'veg' => [ 'cabbages', 'potatoes', 'carrots' ] },
{ 'potatoes' => [ 'king_edward', 'sweet', 'russet' ] }
]
I need to iterate over this, and "fold in" where the key to the values in a hash, is the value in another hash, so I end up with :
[
{ 'drink' => [ { 'hard' => [ 'beer', 'wine' ] }, { 'soft' => [ 'water', 'orange_juice' ] } ] },
{ 'food' => [ { 'fruit' => [ 'apples', 'bananas', 'pears' ] },
{ 'veg' => [ 'cabbages', { 'potatoes' => [ 'king_edward', 'sweet', 'russet' ] }, 'carrots' ] }
]
The nesting could be any level of depth, but the result will always end up being an array of hashes at its outermost ( if that makes sense ), with one or more hash in that array.
Edit: Or as suggested in comment below, the result structure could just be a hash, with 'drink', and 'food' keys pointing to the respective nested values in that hash. ( The items are not necessarily unique, two or more keys could point to the same value/s ).
I can iterate over an array and/or hash with ".each" etc., and apply logic and build the result structure iteratively, but I'm struggling with what I think is the recursive nature of this problem, in terms of making sure that everything that can be nested gets nested to any depth ( if that makes sense ).
I'm not looking for a complete solution, but just a mental nudge in the right direction as to how to go about creating a solution for this, because at the moment I keep venturing down dead-ends..
Edited: this updated solution is recursive as well, as the previous. I added some case in the input to show how can nest at a deepr level (beer for example)
def rec_map(to_map, collection, mapped)
if to_map.empty?
mapped
else
case to_map[0]
when String
current_key = to_map[0]
mapped_key = collection.find(-> {current_key}) { |item_hash| item_hash.has_key?(current_key) }
if current_key == mapped_key
rec_map(to_map[1..-1], collection, mapped << current_key)
else
with_mapped_values = Hash.new
mapped_values = rec_map(mapped_key[current_key], collection, [])
with_mapped_values[current_key] = mapped_values
rec_map(to_map[1..-1], collection, mapped << with_mapped_values)
end
when Hash
key = to_map[0].keys.first
values = to_map[0].values.flatten
with_mapped_values = Hash.new
mapped_values = rec_map(values, collection, [])
with_mapped_values[key] = mapped_values
rec_map(to_map[1..-1], collection, mapped << with_mapped_values)
else
raise "Not supported given #{to_map[0].class} expected String or Hash"
end
end
end
def key_has_match(key, collection)
if collection.empty?
return false
else
case collection[0]
when String
key == collection[0] || key_has_match(key, collection[1..-1])
when Hash
key_has_match(key, collection[0].values.flatten) ||
key_has_match(key, collection[1..-1])
else
raise "Not supported given #{collection[0].class} expected String or Hash"
end
end
end
def rec_fold(to_fold, collection, folded)
if to_fold.empty?
folded
else
key = to_fold[0].keys.first
if key_has_match(key, collection)
rec_fold(to_fold[1..-1], collection, folded)
else
with_mapped_values = Hash.new
values = to_fold[0][key]
with_mapped_values[key] = rec_map(values, collection, [])
rec_fold(to_fold[1..-1], collection, folded << with_mapped_values)
end
end
end
if $PROGRAM_NAME == __FILE__
original = [
{ 'drink' => [ 'hard', 'soft' ] },
{ 'hard' => [ 'beer', 'wine' ] },
{ 'beer' => [ 'lager', 'stout', 'ale' ] },
{ 'lager' => [ 'draught', 'bottle' ] },
{ 'draught' => [ 'pint', 'half' ] },
{ 'soft' => [ {'water' => ['tap', 'bottle']}, 'orange_juice' ] },
{ 'food' => [ 'fruit', 'veg', 'meat' ] },
{ 'fruit' => [ 'apples', 'bananas', {'pears'=> ['anjou', 'bartlett', 'concorde']} ] },
{ 'veg' => [ 'cabbages', 'potatoes', 'carrots' ] },
{ 'potatoes' => [ 'king_edward', 'sweet', 'russet' ] },
{ 'sweet' => [ 'red', 'brown', 'white' ] },
{'bottle' => ['green', 'red', 'blue']},
{'bartlett' => ['Woven baskets', 'Bags', 'Modified atmosphere']}
]
pp rec_fold(original, original, [])
end
# [{"drink"=>
# [{"hard"=>
# [{"beer"=>
# [{"lager"=>
# [{"draught"=>["pint", "half"]},
# {"bottle"=>["green", "red", "blue"]}]},
# "stout",
# "ale"]},
# "wine"]},
# {"soft"=>
# [{"water"=>["tap", {"bottle"=>["green", "red", "blue"]}]},
# "orange_juice"]}]},
# {"food"=>
# [{"fruit"=>
# ["apples",
# "bananas",
# {"pears"=>
# ["anjou",
# {"bartlett"=>["Woven baskets", "Bags", "Modified atmosphere"]},
# "concorde"]}]},
# {"veg"=>
# ["cabbages",
# {"potatoes"=>
# ["king_edward", {"sweet"=>["red", "brown", "white"]}, "russet"]},
# "carrots"]},
# "meat"]}]
Elasticsearch: v7.2
Application: PHP - Laravel v5.7
Hello and good day!
I'm developing a web application that is similar to a search engine, whereas a user will enter words that will be designated to the variable $keywords. Then I have this query to search throughout my index:
$params = [
'index' => 'my_index',
'type' => 'my_type',
'from' => 0,
'size' => 10,
'body' => [
"query" => [
'bool' => [
'must' => [
[
"query_string" => [
"fields" => ['title','content'],
"query" => $keywords
]
]
]
]
]
]
];
$articles = $client->search($params);
Now, in line with my previous post, I was able to count the number of occurrences my $keywords occurred within the documents of my index.
Here's my highlight query that is attached to the $params above:
"highlight" => [
"fields" => [
"content" => ["number_of_fragments" => 0],
"title" => ["number_of_fragments" => 0]
]
'require_field_match' => true
]
Even though that the $keywords are enclosed with double quotation mark ("), the highlighter still chops/separates the $keywords and I already specified them with double quotation mark to strictly follow these words.
For example, my $keywords contains "Ayala Alabang", but as I displayed the output, it goes like this
The $keywords were separated, but according to the output, they're just adjacent to each other.
Is there any other tweaks or revision to my query? I found some related posts or questions in some forums, their last reply was from March 2019, any advice would be an excellent help for this dilemma
After a few days of looking into deep documentation, I found a way to properly segregate keywords that are found in a document
STEP 1
Apply the "explain" => true in your $params
$params = [
'index' = "myIndex",
'type' => "myType",
'size' => 50,
'explain' => true,
'query' => [
'match_all' => [
//your elasticearch query here
]
]
]
STEP 2
Then fetch the result after doing the $client->search($params) code:
$result = $client->search($params);
Then a long literal EXPLANATION will be included in your $result whereas your keywords and their frequency will be displayed in a text format.:
try displaying via dd($result['explanation'])
NOTE the problem here is that a lot of nested arrays will be the contents of the _explanation array key, so we came up with a recursive function to look for the keywords and their frequency
STEP 3
You need to create a function that will get a string IN BETWEEN of repetitive or other strings:
public static function get_string_between($string, $start, $end){
$string = ' ' . $string;
$ini = strpos($string, $start);
if ($ini == 0) return '';
$ini += strlen($start);
$len = strpos($string, $end, $ini) - $ini;
return substr($string, $ini, $len);
}
STEP 4
Then create the recursive function:
public static function extract_kwds($expln,$kwds)
{
foreach($expln as $k=>$v)
{
if($k == 'description' && strpos(json_encode($v),'weight(')!==false)
{
if(isset($kwds[$this->get_string_between($v,':',')')]))
{
$kwds[$this->get_string_between($v,':',')')] += intVal($this->get_string_between($expln['details'][0]['description'],'score(freq=',')'));
}
else
{
$kwds[$this->get_string_between($v,':',')')] = intVal($this->get_string_between($expln['details'][0]['description'],'score(freq=',')'));
}
}
if($k == 'details' && count($v) != 0)
{
foreach($v as $k2=>$v2)
{
$kwds = $this->extract_kwds($v2,$kwds);
}
}
}
return $kwds;
}
FINALLY
I was able to fetch all the keywords together with their frequency or how many times these keywords appeared in the documents.
This is similar to this question here but I can't figure out how to convert it to Mongoid syntax:
MongoDB query based on count of embedded document
Let's say I have Customer: {_id: ..., orders: [...]}
I want to be able to find all Customers that have existing orders, i.e. orders.size > 0. I've tried queries like Customer.where(:orders.size.gt => 0) to no avail. Can it be done with an exists? operator?
I nicer way would be to use the native syntax of MongoDB rather than resort to rails like methods or JavaScript evaluation as pointed to in the accepted answer of the question you link to. Especially as evaluating a JavaScript condition will be much slower.
The logical extension of $exists for a an array with some length greater than zero is to use "dot notation" and test for the presence of the "zero index" or first element of the array:
Customer.collection.find({ "orders.0" => { "$exists" => true } })
That can seemingly be done with any index value where n-1 is equal to the value of the index for the "length" of the array you are testing for at minimum.
Worth noting that for a "zero length" array exclusion the $size operator is also a valid alternative, when used with $not to negate the match:
Customer.collection.find({ "orders" => { "$not" => { "$size" => 0 } } })
But this does not apply well to larger "size" tests, as you would need to specify all sizes to be excluded:
Customer.collection.find({
"$and" => [
{ "orders" => { "$not" => { "$size" => 4 } } },
{ "orders" => { "$not" => { "$size" => 3 } } },
{ "orders" => { "$not" => { "$size" => 2 } } },
{ "orders" => { "$not" => { "$size" => 1 } } },
{ "orders" => { "$not" => { "$size" => 0 } } }
]
})
So the other syntax is clearer:
Customer.collection.find({ "orders.4" => { "$exists" => true } })
Which means 5 or more members in a concise way.
Please also note that none of these conditions alone can just an index, so if you have another filtering point that can it is best to include that condition first.
Just adding my solution which might be helpful for someone:
scope :with_orders, -> { where(orders: {"$exists" => true}, :orders.not => {"$size" => 0}}) }
I have an array like so:
[
[0] {
"Success" => true
},
[1] {
"Success" => true
},
[2] {
"Success" => true
},
[3] {
"Success" => true
}
]
Each number above has a much larger multidimensional array associated with it, but that is not my concern currently. I am wanting to rename the numbers [0..3] with new key values.
So it would look like this
[
["pop"] {
"Success" => true
},
["rock"] {
"Success" => true
},
["country"] {
"Success" => true
},
["soul/r&b"] {
"Success" => true
}
]
Thanks in advance!
if your input is really JSON, you need to first parse the JSON and convert it into a Ruby data structure:
require 'active_support'
json = "[[0,{\"Success\":true}],[1,{\"Success\":true}],[2,{\"Success\":true}],[3,{\"Success\":true}]]"
array = ActiveSupport::JSON.decode( json )
=> [ [0, { "Success" => true }], [1, { "Success" => true }], [2, { "Success" => true }], [3, { "Success" => true }] ]
Now your input is a Ruby Array...
I assume you have the new keys stored in an array somewhere like this:
new_keys = %w{pop rock country soul/r&b}
=> ["pop", "rock", "country", "soul/r&b"]
Next step is to replace the first element of each of the sub-arrays in the JSON array with the new key:
result = Array.new
array.each do |value|
i, rest = value
result << [ new_keys[i], rest ]
end
json = result.to_json
=> "[[\"pop\",{\"Success\":true}],
[\"rock\",{\"Success\":true}],
[\"country\",{\"Success\":true}],
[\"soul/r&b\",{\"Success\":true}]]"
There are two array like data structures in Ruby: the array which stores ordered data accessible by an integer index and the hash, which is a hashmap storing data accessible by some key object (e.g. a string). Although they are both accessed similarly, they are different classes.
From your code (which is not valid ruby btw.) I'm guessing you have the following data structure:
array = [
{ "Success" => true },
{ "Success" => true },
{ "Success" => true },
{ "Success" => true }
]
It can be transferred usiong the following code:
keys = ["pop", "rock", "country", "soul/r&b"]
result = {}
keys.each_with_index {|key, i| result[index] = array[i]}
This results in the following data structure. It is a hash containing the elements of the original array (which are hashes by themselves) as values.
{
"pop" => { "Success" => true },
"rock" => { "Success" => true },
"country" => { "Success" => true },
"soul/r&b" => { "Success" => true }
}
styles = {0: "pop", 1: "rock", 2: "country", 3: "shoul/r&b"}
info2 = Hash[info.map { |index, h| [[styles[index]], h}]