How do I get a nested hash from a join? - ruby

I'm using Sequel to read rows from a SQLite database. No writing is necessary.
I am using a basic join. Consider this:
db = Sequel.sqlite
db[:user].join_table(:left, :photos, user_id: :user_id)
This joins the photo table on the user table (keeping the users that have no photos).
However this multiplies all the lines of the user by the number of its photos:
[
{user_id: 1, name: 'User1', photo_id: 1, photo_name: 'profile.jpg'}
{user_id: 1, name: 'User1', photo_id: 2, photo_name: 'cat.jpg'}
{user_id: 1, name: 'User1', photo_id: 3, photo_name: 'dog.jpg'}
{user_id: 2, name: 'User2', photo_id: 4, photo_name: 'profile.jpg'}
{user_id: 2, name: 'User2', photo_id: 5, photo_name: 'profile.jpg'}
{user_id: 3, name: 'User3', photo_id: nil, photo_name: nil}
]
I would like to have all the photo attributes nested under one key, like this:
[
{user_id: 1, name: 'User1', photos: [{photo_id: 1, photo_name: 'profile.jpg'}, {photo_id: 2, photo_name: 'cat.jpg'}, {photo_id: 3, photo_name: 'dog.jpg'}]}
{user_id: 2, name: 'User2', photos: [{photo_id: 4, photo_name: 'profile.jpg'}, {photo_id: 5, photo_name: 'profile.jpg'}]},
{user_id: 3, name: 'User3', photos: [] }
]
How can I achieve this "mini-relation"? Do I have to specify a model?

Related

How do I remove duplicate items from observable array based on multiple properties?

If I have an array of items like
[
{id: 1, name: 'Sam', gender: 'boy'},
{id: 2, name: 'Mary', gender: 'girl'},
{id: 3, name: 'Sam', gender: 'boy'}
]
Matching on just name and gender, how do I reduce it to the following result?
[
{id: 1, name: 'Sam', type: 'boy'},
{id: 2, name: 'Mary', type: 'girl'}
]
Let try
items$.pipe(map(this.uniqueArray))
uniqueArray(array: any[]): any[] {
return array.filter(
(item, index, self) =>
index === self.findIndex((x) => x.name === item.name)
);
}
https://stackblitz.com/edit/angular-isqjpa?file=src/app/hello.component.ts

Why do aggregating a filtered dataset lose the filters of it?

I have this collection:
// collection
[
{_id: 1, name: 'Luigi', childs: [{name: 'one'}, {name: 'two'}], dad_id: 9]},
{_id: 1, name: 'Mario', childs: [{name: 'four'}, {name: 'five'}], dad_id: 8]},
{_id: 1, name: 'Alessandro', childs: [{name: 'seven'}, {name: 'six'}], dad_id: 9]},
]
and apply this filter to it
result = collection.find({ dad_id: 9 })
Then I want to aggregate the results and get all the childs singularly, I start with unwinding them
(then I 'll make a projection, etc..) but I already encounter a behavior that I do not understand:
the result contains also the documents with dad_id is 8, even if they were already excluded by my query.
result.aggregate([
{ "$unwind"=> "$childs" },
]).each do |e| ... end
// => [
{_id: 1, name: 'Luigi', childs: {name: 'one'}, dad_id: 9]},
{_id: 1, name: 'Luigi', childs: {name: 'two'}, dad_id: 9]},
{_id: 1, name: 'Luigi', childs: {name: 'five'}, dad_id: 8]},
{_id: 1, name: 'Luigi', childs: {name: 'four'}, dad_id: 8]},
{_id: 1, name: 'Luigi', childs: {name: 'seven'}, dad_id: 9]},
{_id: 1, name: 'Luigi', childs: {name: 'six'}, dad_id: 9]},
]
What am I missing?
You can not chain input from one query to another query like that.
Either use search query ex. Model.find(id) or aggregation framework.
Aggregation framework provides you the functionality to create a pipeline (ex. match,unwind,lookup,project).
To utilize mongodb indexing always try to use "$match" first in the pipeline
match = { "$match" => { "dad_id" =>9} }
unwind = {"$uwind"=>"$childs"}
pipeline = [match,unwind]
collection.aggregate(pipeline).each do |obj|
end

Keying an eager-loaded relationship

I have a Business model and an Hour model. The Business model overrides the protected $with method to eager load it's hours() hasMany relationship.
When I ::first() a given business I receive something like this:
App\Business {#770
id: 5,
user_id: 5,
name: "Wehner-Hudson",
slug: "wehner-hudson",
lat: "55.33593500",
lng: "112.34818600",
created_at: "2018-01-04 13:00:48",
updated_at: "2018-01-04 13:00:48",
hours: Illuminate\Database\Eloquent\Collection {#753
all: [
App\Hour {#802
id: 13,
business_id: 5,
weekday_id: 3,
open: 1,
split_shift: 1,
},
App\Hour {#803
id: 14,
business_id: 5,
weekday_id: 5,
open: 0,
split_shift: 1,
},
App\Hour {#804
id: 15,
business_id: 5,
weekday_id: 2,
open: 1,
split_shift: 0,
},
],
},
},
],
}
I would like to key the hours: Illuminate\Database\Eloquent\Collection {#753 by weekday_id to facilitate processing on the client side. Something like this:
Illuminate\Database\Eloquent\Collection {#763
all: [
1 => App\Hour {#796
id: 1,
business_id: 1,
weekday_id: 1,
open: 1,
split_shift: 1,
},
5 => App\Hour {#767
id: 2,
business_id: 1,
weekday_id: 5,
open: 0,
split_shift: 0,
},
2 => App\Hour {#765
id: 3,
business_id: 1,
weekday_id: 2,
open: 1,
split_shift: 1,
},
],
}
I tried to use keyBy on the relationship in the Business model:
public function hours()
{
return $this->hasMany(Hour::class)->keyBy('weekday_id');
}
But it is not working, as I believe that at that point the returned object is a builder, not a collection.
Try to define an accessor, like this:
public function getHoursByWeekdayAttribute()
{
return $this->hours->keyBy('weekday_id');
}
What about using groupby in your controller.
Business::with(['hours' => function($query){ $query->groupBy('weekend_id'); }])->get();

Elastic search : how to query to return certain number products for different users

I have a collection of products which belong to few users, (the system is with ElasicSearch(ES), MySQL, Scala and ES Play Framework APIs link):
[
{ id: 1, user_id: 'jason', product: [...] },
{ id: 2, user_id: 'mike', product: [...] },
{ id: 3, user_id: 'mike', product: [...] },
{ id: 4, user_id: 'dan', product: [...] },
{ id: 5, user_id: 'bill', product: [...] },
{ id: 6, user_id: 'mike', product: [...] },
{ id: 7, user_id: 'dan', product: [...] },
{ id: 8, user_id: 'bill', product: [...] },
{ id: 9, user_id: 'mike', product: [...] },
{ id: 10, user_id: 'dan', product: [...] },
{ id: 11, user_id: 'bill', product: [...] },
...
]
I'd like to retrieve some certain number (for example, top 2 with highest matching score) products of best matching document based upon the user's id:
[
{ id: 2, user_id: 'mike', product: [...], _score: 100},
{ id: 3, user_id: 'mike', product: [...], _score: 95},
{ id: 4, user_id: 'dan', product: [...], _score: 90},
{ id: 5, user_id: 'bill', product: [...], _score: 80},
{ id: 7, user_id: 'dan', product: [...], _score: 70},
{ id: 8, user_id: 'bill', product: [...], _score: 65},
...
]
I tried term facets on user_id, but I cannot find equal number products for each user currently, for example,
[
{ id: 2, user_id: 'mike', product: [...], _score: 100},
{ id: 3, user_id: 'mike', product: [...], _score: 95},
{ id: 4, user_id: 'dan', product: [...], _score: 90},
{ id: 5, user_id: 'bill', product: [...], _score: 80},
{ id: 6, user_id: 'mike', product: [...], _score: 75},
...
]
Term facets pseudo code:
/** query type is com.github.cleverage.elasticsearch.ScalaHelpers.IndexResults[Product]
* filtered is matching requirement filter, i.e. including keyword "fashion"
* limit is the size of returned users with matching document, i.e. 10
* finalQuery return 5 unique users based on tmpQuery result with 10 users
* each user has 2 products finally
*/
tmpQuery = query.withBuilder(filtered).withSize(limit)
finalQuery = tmpQuery.addFacet(FacetBuilders.termsFacet("userId").field("user_id").size(5))
How to ensure everyone has 2 products, rather than mike has 3, dan has 1 and bill has 1?
I mean, addFacet doesn't work now because finalQuery is based on tmpQuery, and tmpQuery returns 10 results which are more from mike due to higher matching score, how to update tmpQuery to reach the limitation of 2?)
Term facet cannot ensure unique users, it only return most frequent users. Actually, in this case, have to match products first, then retrieve its user_id then, so it cannot get users first and then their products.
Appreciate.

Find intersection of two array of hashes depending upon hash contents

I get this two array of hashes after performing join
array 1
[#<State id: 1, name: "Alabama">, #<State id: 1, name: "Alabama">, #<State id: 1, name: "Alabama">, #<State id: 1, name: "Alabama">, #<State id: 2, name: "Alaska">, #<State id: 2, name: "Alaska">, #<State id: 4, name: "Arkansas">, #<State id: 4, name: "Arkansas">, #<State id: 4, name: "Arkansas">, #<State id: 6, name: "Colorado">, #<State id: 6, name: "Colorado">, #<State id: 6, name: "Colorado">, #<State id: 11, name: "Georgia">, #<State id: 14, name: "Illinois">, #<State id: 18, name: "Kentucky">, #<State id: 18, name: "Kentucky">, #<State id: 22, name: "Massachusetts">, #<State id: 48, name: "Washington">]
array 2
[#<City id: 1, name: "Abbeville", state_id: 1>, #<City id: 1, name: "Abbeville", state_id: 1>, #<City id: 1, name: "Abbeville", state_id: 1>, #<City id: 4543, name: "Abingdon", state_id: 14>, #<City id: 8282, name: "Accord", state_id: 22>, #<City id: 3808, name: "Acworth", state_id: 11>, #<City id: 6855, name: "Adairville", state_id: 18>, #<City id: 6855, name: "Adairville", state_id: 18>, #<City id: 18895, name: "Adams County", state_id: 6>, #<City id: 4, name: "Addison", state_id: 1>, #<City id: 4, name: "Addison", state_id: 1>, #<City id: 17510, name: "Addy", state_id: 48>, #<City id: 1054, name: "Adona", state_id: 4>, #<City id: 1054, name: "Adona", state_id: 4>, #<City id: 577, name: "Akiachak", state_id: 2>, #<City id: 1056, name: "Alicia", state_id: 4>, #<City id: 583, name: "Ambler", state_id: 2>, #<City id: 2783, name: "Aspen", state_id: 6>]
I want to make a third array from the above two based on the value of state_id in each array
in this case for example
[#, .... and so on
for your help the first two hashes array i got using join query
#states = State.joins("INNER JOIN property_of_interests ON property_of_interests.state_id = states.id").where(:property_of_interests => {:user_id => current_user.id})
#cities = City.joins("INNER JOIN property_of_interests ON property_of_interests.city_id = cities.id").where(:property_of_interests => {:user_id => current_user.id})
can I work on the query itself to get the desired output ?.
I tried something like
`#states.select("#states.name,#cities.name").joins("INNER JOIN #cities ON #cities.state_id = #states.id")`
but it doesnt work.
More Information
states
id, name
cities
id, name, state_id
property_of_interests
id, user_id, state_id, state_name
states has cities
cities belongs to states
states belongs to property_of_interests
cities belongs to property_of_interests
property_of_interests has cities
property_of_interests has states
Desired output like
State Name City Name
Alabama Abbeville
Alabama Abbeville
Alabama Abbeville
....
You would do something like:
City.all.each do |city|
puts "#{city.state.name} #{city.name}"
end
Alternately, as an array:
arr = City.all.map { |c| [c.state.name, c.name] }
Or as an array of hashes:
arr = City.all.map { |c| {state: c.state.name, city: c.name} }
Or to actually answer the question, since you want to start with the properties_of_interest table:
PropertyOfInterest.all.each do |prop|
prop.state.cities.each do |city|
puts prop.state.name, city.name
end
end

Resources