Trouble eliminating separate query for each child item - ruby

I have a scenario where I have a Candidate and a Candidate has many Votes. I want to order the candidates in order of highest votes to lowest. This query does that:
Candidate.joins(:votes).
select(['candidates.*', 'SUM(votes.score) as total_score']).group('candidates.id, candidates.candidate_id, candidates.user_id, candidates.status, candidates.card_id, candidates.created_at, candidates.updated_at').
order('candidates.status desc, total_score desc, candidates.created_at asc').where("candidates.riding_id = ? and candidates.status != ?", 124, CandidateStatus::Eligible ).
having('SUM(votes.score) >= 0')
My problem is that I use this to render it:
render :json => #candidates.to_json(:include => [:votes])
This then causes a query for each candidate to get their votes. I should be able to do this in one query but no matter how I change it, it still grabs each candidates votes separately.

By the time #to_json is called, the votes have to be fetched again, hence each additional query. Using eager-loading, however, it's possible to do this without the additional queries:
Candidate.all(:include => :votes).to_json(:include => [:votes])
Thus, it can be done with #candidates including the eager-loading, such as:
#candidates = Candidate.all(:include => :votes)
#candidates.to_json(:include => [:votes])

Related

In LINQ expression how to get FirstOrDefault with better performance?

In our Xamarin Forms app this code:
return database.GetAllWithChildren<Review>(x => x.ProductId == prodId, true).OrderByDescending(x => x.ReviewId).FirstOrDefault();
is having performance issues and it takes more time the more reviews exist for that product.
With a product with 7 reviews it took about 17 seconds which is unacceptable.
How could I optimize the performance?
After all I don't need all the reviews info, just the latest one.
It seems that this retrieves all 7 reviews and then sorts them descending and then gets the first in the list.
Is there a way to get only that one with the max ID?
It seems using the GetAllWithChildren is the problem. This method recursively loads the info of all the reviews, not just the one. You are then performing the ordering and selection after all the data returned from the DB.
The solution would be to first filter and only then return the Review:
var review = database.Table<Review>().Where(x => x.Product == prodId ).
OrderByDescending( x => x.ReviewId ).FirstOrDefault();
database.GetChildren( review, true );
return element;
database.GetAllWithChildren<Review> returns a List<Review> of fully constructed reviews. This means that the more reviews a product has, the more reviews get discarded by FirstOrDefault() right after being constructed.
Try getting the max id first, then filter reviews on it:
// This assumes that ID is int. Change to another nullable type matching ReviewId
int? maxId = database.Reviews.Where(r => r.ProductId == prodId).Max((int?)r.ReviewId);
return database
.GetAllWithChildren<Review>(x => x.ProductId == prodId && x.ReviewId == maxId, true)
.SingleOrDefault();

Elixir/Phoenix sum of the column

I'm trying to get the sum of the particular column.
I have a schema of orders, with the field total, that stores the total price.
Now I'm trying to created a query that will sum total value of all the orders, however not sure if I'm doing it right.
Here is what i have so far:
def create(conn, %{"statistic" => %{"date_from" => %{"day" => day_from, "month" => month_from, "year" => year_from}}}) do
date_from = Ecto.DateTime.cast!({{year_from, month_from, day_from}, {0, 0, 0, 0}})
revenue = Repo.all(from p in Order, where: p.inserted_at >= ^date_from, select: sum(p.total))
render(conn, "result.html", revenue: revenue)
end
And just calling it like <%= #revenue %> in the html.eex.
As of right now, it doesn't return errors, just renders random symbol on the page, instead of the total revenue.
I think my query is wrong, but couldn't find good information about how to make it work properly. Any help appreciated, thanks!
Your query returns just 1 value, and Repo.all wraps it in a list. When you print a list using <%= ... %>, it treats integers inside the list as Unicode codepoints, and you get the character with that codepoint as output on the page. The fix is to use Repo.one instead, which will return the value directly, which in this case is an integer.
revenue = Repo.one(from p in Order, where: p.inserted_at >= ^date_from, select: sum(p.total))
#Dogbert's answer is correct. It is worth noting that if you are using Ecto 2.0 (currently in release candidate) then you can use Repo.aggregate/4:
revenue = Repo.aggregate(from p in Order, where: p.inserted_at >= ^date_from, :sum, :total)

Linq query with group by clause

My table has following columns:-
LevelId, LevelName, ScenarioId, TeamId.
I want to select LevelName corresponding to minimum LevelId when grouped by teamId
So, you want to:
Group by team ID
Within each group, order by level ID
Take the first result's level name
So that sounds like:
var query = table.GroupBy(x => x.TeamId)
.Select(g => g.OrderBy(x => x.LevelId).First().Name);
Don't just take this query and include it in your code though - make sure you understand it so you can come up with your own solution next time you have something similar to do.
If this is in LINQ to Objects, you may find that MoreLINQ would be useful, with its MinBy method:
var query = table.GroupBy(x => x.TeamId)
.Select(g => g.MinBy(x => x.LevelId).Name);
This avoids ordering the whole group, just to find the entry with the minimum level ID.

Optimising Lambda Linq to SQL query with OrderBy

I have the following lambda expression:
IEnumerable<Order> query
= _ordersRepository.GetAllByFilter(
o =>
o.OrderStatus.OrderByDescending(os => os.Status.Date).First()
.Status.StatusType.DisplayName != "Completed"
||
o.OrderStatus.OrderByDescending(os => os.Status.Date).First()
.Status.Date > sinceDate
).OrderBy(o => o.DueDate);
As you can see, I'm having to order the collection twice within the main query (so three times in total) in order to perform my OR query.
1) Is the query optimiser clever enough to deal with this in an efficient way?
2) If not, how can I rewrite this expression to only order by once, but keeping with lambda syntax?
This is linked to this previous question, which explains the query in a bit more detail if the above code isn't clear.
1) Is the query optimiser clever enough to deal with this in an efficient way?
You can get the SQL for this query (one way is to use the SQL profiler), and then ask SQL Studio for the execution plan. Unless you do this, there is no way to know what the optimizer thinks. My guess is the answer is "no".
2) If not, how can I rewrite this expression to only order by once, but keeping with lambda syntax?
Like this:
IEnumerable<Order> query = _ordersRepository.GetAllByFilter( o =>
o.OrderStatus
.OrderByDescending(os => os.Status.Date)
.Take(1)
.Any(os => os.Status.StatusType.DisplayName != "Completed"
|| os.Status.Date > sinceDate)
})
.OrderBy(o => o.DueDate);
Regarding your first point: You can see the SQL that is generated by subscribing to the output of the DatabaseContext object. This is usually in a property called Log.
As for optimising your query, try the following (I've not tested it so I don't know if it will work)
IEnumerable<Order> query
= _ordersRepository.GetAllByFilter(
o =>
o.OrderStatus.Max(os => os.Status.Date).Any(os =>
os.Status.StatusType.DisplayName != "Completed"
|| os.Status.Date > sinceDate)
).OrderBy(o => o.DueDate);
Hopefully that will only perform the subquery once, and also performs a max rather than an order by with top 1.

conditional include in linq to entities?

I felt like the following should be possible I'm just not sure what approach to take.
What I'd like to do is use the include method to shape my results, ie define how far along the object graph to traverse. but... I'd like that traversal to be conditional.
something like...
dealerships
.include( d => d.parts.where(p => p.price < 100.00))
.include( d => d.parts.suppliers.where(s => s.country == "brazil"));
I understand that this is not valid linq, in fact, that it is horribly wrong, but essentially I'm looking for some way to build an expression tree that will return shaped results, equivalent to...
select *
from dealerships as d
outer join parts as p on d.dealerid = p.dealerid
and p.price < 100.00
outer join suppliers as s on p.partid = s.partid
and s.country = 'brazil'
with an emphasis on the join conditions.
I feel like this would be fairly straight forward with esql but my preference would be to build expression trees on the fly.
as always, grateful for any advice or guidance
This should do the trick:
using (TestEntities db = new TestEntities())
{
var query = from d in db.Dealership
select new
{
Dealer = d,
Parts = d.Part.Where
(
p => p.Price < 100.0
&& p.Supplier.Country == "Brazil"
),
Suppliers = d.Part.Select(p => p.Supplier)
};
var dealers = query.ToArray().Select(o => o.Dealer);
foreach (var dealer in dealers)
{
Console.WriteLine(dealer.Name);
foreach (var part in dealer.Part)
{
Console.WriteLine(" " + part.PartId + ", " + part.Price);
Console.WriteLine
(
" "
+ part.Supplier.Name
+ ", "
+ part.Supplier.Country
);
}
}
}
This code will give you a list of Dealerships each containing a filtered list of parts. Each part references a Supplier. The interesting part is that you have to create the anonymous types in the select in the way shown. Otherwise the Part property of the Dealership objects will be empty.
Also, you have to execute the SQL statement before selecting the dealers from the query. Otherwise the Part property of the dealers will again be empty. That is why I put the ToArray() call in the following line:
var dealers = query.ToArray().Select(o => o.Dealer);
But I agree with Darren that this may not be what the users of your library are expecting.
Are you sure this is what you want? The only reason I ask is, once you add the filter on Parts off of Dealerships, your results are no longer Dealerships. You're dealing in special objects that are, for the most part, very close to Dealerships (with the same properties), but the meaning of the "Parts" property is different. Instead of being a relationship between Dealerships and Parts, it's a filtered relationship.
Or to put it another way, if I pull a dealership out of your results and passed to a method I wrote, and then in my method I call:
var count = dealership.Parts.Count();
I'm expecting to get the parts, not the filtered parts from Brazil where the price is less than $100.
If you don't use the dealership object to pass the filtered data, it becomes very easy. It becomes as simple as:
var query = from d in dealerships
select new { DealershipName = d.Name,
CheapBrazilProducts = dealership.Parts.Where(d => d.parts.Any(p => p.price < 100.00) || d.parts.suppliers.Any(s => s.country == "brazil")) };
If I just had to get the filtered sets like you asked, I'd probably use the technique I mentioned above, and then use a tool like Automapper to copy the filtered results from my anonymous class to the real class. It's not incredibly elegant, but it should work.
I hope that helps! It was an interesting problem.
I know this can work with one single Include. Never test with two includes, but worth the try:
dealerships
.Include( d => d.parts)
.Include( d => d.parts.suppliers)
.Where(d => d.parts.All(p => p.price < 100.00) && d.parts.suppliers.All(s => s.country == "brazil"))
Am I missing something, or aren't you just looking for the Any keyword?
var query = dealerships.Where(d => d.parts.Any(p => p.price < 100.00) ||
d.parts.suppliers.Any(s => s.country == "brazil"));
Yes that's what I wanted to do I think the next realease of Data Services will have the possiblity to do just that LINQ to REST queries that would be great in the mean time I just switched to load the inverse and Include the related entity that will be loaded multiple times but in theory it just have to load once in the first Include like in this code
return this.Context.SearchHistories.Include("Handle")
.Where(sh => sh.SearchTerm.Contains(searchTerm) && sh.Timestamp > minDate && sh.Timestamp < maxDate);
before I tried to load for any Handle the searchHistories that matched the logic but don't know how using the Include logic you posted so in the mean time I think a reverse lookup would be a not so dirty solution

Resources