NHibernate - Eager loading grouped object when aggregating - nhibernate

I have the following code where I am aggregating auction statistics from a statistics table and returning the Auction as well as the summed counts (there may be multiple rows in the statistics table per auction...)
var stats = _session.QueryOver<AuctionStatistic>()
.Select(
Projections.Group<AuctionStatistic>(s => s.Auction),
Projections.Sum<AuctionStatistic>(s => s.BidCount),
Projections.Sum<AuctionStatistic>(s => s.ViewCount),
Projections.Sum<AuctionStatistic>(s => s.SearchCount)
)
.OrderBy(Projections.Sum<AuctionStatistic>(s => s.ApplicationCount)).Desc
.Fetch(x => x.Auction).Eager
.Take(take)
.List<object[]>();
The query itself seems to work fine - except that the Auction that is returned is evaluated lazily, causing a SELECT N+1 scenario. Can anyone provide suggestions as to how this field can be evaluated eagerly?
Thanks in advance
JP

The brute force way would be (similar to a sub-select):
_session.QueryOver<Auction>().WhereRestrictionOn(a => a.Id).IsIn(stats.SelectMany(s => s).OfType<Auction>().Select(a => a.Id).ToArray());

Related

Yii1 - prevent pagination (dataProvider) from counting, or cache it

Is it possible to prevent counting in the data provider, or at least cache the result?
This is my current code, and the count is done on every call:
$dataProvider = new CActiveDataProvider(PProjectJob::model()->cache(3600, null, 2), array(
'criteria' => $searchForm->search(),
'pagination' => array(
'pageSize' => Yii::app()->user->getState('pageSize', Yii::app()->params['defaultPageSize']),
),
'sort' => RedeliverySearchForm::getSort(),
'totalItemCount' => null
));
Initial query is always the same, and caching a count result will for 24 hours will have a great impact on performance.
You can pass a 'totalItemCount' value into the CActiveDataProvider constructor, which you have passed in as null in your current code
The data provider will only run a count query if its 'totalItemCount' is null
You could happily calculate, or fetch from cache, the count result yourself, based on the $searchForm->search() criteria, and pass it in
A second option is to create your own extension of CActiveDataProvider, override calculateTotalItemCount(), and provide a caching option for the count result where it currently runs the count() query

Need help creating a linq select

I need some help creating an LINQ select, i have a table with some columns in it, but only 2 of interest to this problem.
userid, type
Now this table have many thousands entries, and I only want the top, let’s say 50. So far so good, but the hard part is that there a lot of rows in success that should only be counted as 1.
Example
Type UserId
============
Add 1
Add 1
Add 1
Add 2
I would like this to only be counted as 2 in the limit of rows I am taking out, but I would like all the rows to be outputted still.
Is this possible with a single SQL request, or should I find another way to do this?
Edit: I can add columns to the table, with values if this would solve the problem.
Edit2: Sotred procedures are also an solution
Example 2: This should be counted as 3 rows
Type UserId
============
Add 1
Add 1
Add 2
Add 1
Are you stuck on LINQ?
Add a PK identity.
Order by PK.
Use a DataReader and just count the changes.
Then just stop when the changes count is at your max.
If you are not in a .NET environment then same thing with a cursor.
Since LINQ is deferred you might be able to just order in LINQ and then on a ForEach just exit.
I'm not close to a computer right now so I'm not sure is 100% correct syntax wise, but I believe you're looking for something like this:
data.Select(x => new {x.Type, x.UserId})
.GroupBy(x => x.UserId)
.Take(50);
You could do it with Linq, but it may be a LOT slower than a traditional for loop. One way would be:
data.Where((s, i) => i == 0 ||
!(s.Type == data[i-1].Type && s.UserId == data[i-1].UserId))
That would skip any "duplicate" items that have the same Type and UserID as the "previous" item.
However this ONLY works if data has an indexer (an array or something that implements IList). An IEnumerable or IQueryable would not work. Also, it is almost certainly not translatable to SQL so you'd have to pull ALL of the results and filter in-memory.
If you want to do it in SQL I would try either scanning a cursor and filling a temp table if one of the values change or using a common table expression that included a ROW_NUMBER column, then doing a look-back sub-query similar to the Linq method above:
WITH base AS
(
SELECT
Type,
UserId,
ROW_NUMBER() OVER (ORDER BY ??? ) AS RowNum
FROM Table
)
SELECT b1.Type, b1.UserId
FROM base b1
LEFT JOIN base b2 ON b1.RowNum = b2.RowNum - 1
WHERE (b1.Type <> b2.Type OR b1.UserId <> b2.UserId)
ORDER BY b1.RowNum
You can do this with LINQ, but I think it might be easier to go the "for(each) loop" route...
data.Select((x, i) => new { x.Type, x.UserId, i })
.GroupBy(x => x.Type)
.Select(g => new
{
Type = g.Key,
Items = g
.Select((x, j) => new { x.UserId, i = x.i - j })
})
.SelectMany(g => g.Select(x => new { g.Type, x.UserId, x.i }))
.GroupBy(x => new { x.Type, x.i })
.Take(50);
.SelectMany(g => g.Select(x => new { x.Type, x.UserId }));

Avoiding SELECT+1 when working with discriminators

I'm using discriminators to deal with item states. I'm using the NHibernate Profiler and noticing that my application is going quite bonkers over selecting my states. I have 8 states in total and NHibernate keeps querying like so:
SELECT state0_.State_id as State1_10_0_,
state0_.Name as Name10_0_
FROM States state0_
WHERE state0_.State_id = **3** /* #p0 */
What triggers this SELECT+1 is when I select all items where the state is for example equal to 1, 2, 3, 4 or 5. This would issue one query for my items as well 5 queries for each state. Since I'm querying for items (with state) very often I'm executing an awful lot of queries.
Now, looking at the profiler I'm not sure it's much of an issue because the first state I query takes 6ms to query and 6ms to materialize. All other states display as being taken 0ms to query and 0ms to materialize. NHibernate Profiler still displays this as the notorious SELECT+1 problem.
From my aggregate root mapping I have:
References(x => x.State)
.Not.Nullable()
.Not.LazyLoad();
For my state discriminator mapping I have:
Id(x => x.Id, "State_id").GeneratedBy.Assigned();
Map(x => x.Name).Not.Nullable();
DiscriminateSubClassesOnColumn("State_id");
Is there something I'm not understanding regarding discriminators?
Here is the query causing trouble:
var items =
session.QueryOver(() => itemAlias)
.SelectList(x => x
.Select(xx => xx.Id).WithAlias(() => sentTo.Id)
.Select(xx => xx.State).WithAlias(() => sentTo.State)
.Select(xx => xx.Status).WithAlias(() => sentTo.Status)
)
.JoinAlias(() => itemAlias.State, () => stateAlias)
.WhereRestrictionOn(() => stateAlias.Id).IsInG(filters)
.Where(() => itemAlias.Status == Status.InProgress)
.TransformUsing(Transformers.AliasToBean<SentToDto>())
.List<SentToDto>();
Solution
The culprit was
.WhereRestrictionOn(() => stateAlias.Id).IsInG(filters)
To resolve my SELECT+1 issue I simply need to query by a list of discriminator objects instead of individual id's.
.Where(() => itemAlias.State.IsIn(new []{State.FooState, State.BarState}))
It sounds like you eager load the states themselves, so your first query is something like:
SELECT * FROM Items where StateID IN (1,2,3,4,5)
And then you have five queries one for each state:
SELECT Name FROM State WHERE StateID = 1
SELECT Name FROM State WHERE StateID = 2
....
It does that to eager load the name of the state, so to avoid it you should in your first query specify a join from your "items" to the "states" so you get the name there, otherwise set the State object to be lazy loaded if you don't need other info than the identifier in your result.

NHibernate take the top 20 from results

I am using WCF calls to the server to retrive records.
This is done through Nhibernate.
How do I limit the query result that I am getting to say only return the first 20 records.
Depending on how you query, with QueryOver:
var rates = session
.QueryOver<ExchangeRate>()
.OrderBy(r => r.ExchangeDate).Desc
.ThenBy(r => r.CurrencyId.CurrencyId).Asc
.Take(20)
.List();
With the ICreateria interface you can do it by making a createria

RoR table join with "where" needed on both tables

I'm having trouble wrapping my head around joining tables. I have a one_to_many relationship between Locations and Listings. Locations have a longitude and latitude, as well as it's primary key: zipcode. Listings have a title and zipcode.
Listing.where("title LIKE ?", "%#{params[:search2]}%")
Location.where(:lat => #min_lat..#max_lat, :lon => #min_lon..#max_lon)
I'd basically Like to combine these two statements so that I can get all Listings within a given range of a zipcode, which is determnied by using a range of longitudes and latitudes. I can't figure out how to do this in Ruby on Rails in a fashion where I don't have to do separate SQL finds and loop through the data in my code to find the correct data.
Edit Updated code:
#results=Location.joins(:listings).where(:listings => ["title LIKE ?", "%#{params[:search2]}%"], :locations => {:zipcode => params[:searchZip]})
SQL output:SELECT "locations".* FROM "locations" INNER JOIN "listings" ON "listings"."location_id" = "locations"."zipcode" WHERE ("locations"."listings" IN ('title LIKE ?', '%fish%')) AND ("locations"."zipcode" = 44012)
I don't know why it is doing "locations"."listings" (which gives an error), or where the IN is coming from.
I suggest using ARel as opposed to writing your own SQL:
Listing.joins(:location).
where(:listings => {:lat => #min_lat..#max_lat, :lon => #min_lon..#max_lon},
:location => ["title LIKE ?", "%#{params[:search2]}%"])
If I am understanding correctly that Location has_many :listings, then this query will return all of the listings in the location range with matching titles:
Listing.joins(:location).where(
'listings.title LIKE ? AND
locations.lat BETWEEN ? AND ? AND
locations.lon BETWEEN ? AND ?',
"%#{params[:search2]}%", #min_lat, #max_lat, #min_lon, #max_lon)
Location.joins(:listings).where(:listings => {:lat => #min_lat..#max_lat, :lon => #min_lon..#max_lon} )