Avoiding SELECT+1 when working with discriminators - nhibernate

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.

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

wordpress ajax relationship query

Using the relationship field from Advanced Custom Fields I link artists to events. Both these artist and events are custom post types. For each event, the post IDs of the related artists are stored in an array as a custom meta field (lineup_artists).
On each event page I list all the artists. When you click on an artist, I'd like to show all the events where you can find this artist (through an AJAX call which shows the results in a bootstrap modal). I've tested the AJAX call and it's working, but there's something wrong with the query (takes very long to complete).
In my function I have:
$ArtistID = $_POST['ArtistID']; // Gets the ArtistID from the AJAX call
$meta_query = array(
'key' => 'lineup_artists',
'value' => '"' . $ArtistID .'"',
'compare' => 'LIKE'
);
$args = array(
'post_type' => 'events',
'meta_query' => array($meta_query),
'posts_per_page' => 5,
'post_status' => 'publish',
);
If I dump the results of wp_query, I get the following sql query:
SELECT SQL_CALC_FOUND_ROWS yt_posts.ID FROM yt_posts
INNER JOIN yt_postmeta ON (yt_posts.ID = yt_postmeta.post_id)
WHERE 1=1
AND yt_posts.post_type = 'events'
AND (yt_posts.post_status = 'publish')
AND ( (yt_postmeta.meta_key = 'lineup_artists' AND CAST(yt_postmeta.meta_value AS CHAR) LIKE '%\"17497\"%') )
GROUP BY yt_posts.ID ORDER BY yt_posts.post_date DESC LIMIT 0, 5
When I paste this query in phpmyadmin it takes very long to complete (I've never seen it finish because it takes so long).
Is this because the artist IDs are stored as an array? Someone told me that this is not very efficient and that these relations should be stored in a separate table (which I don't know how to do). Is there something wrong with my query or is this a very inefficient way of querying relations?

NHibernate - Eager loading grouped object when aggregating

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());

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} )