Exclude rows not participating in an optional relationship - nhibernate

I have a DB schema as
Episode(id INTEGER)
Study(id INTEGER, id_episode INTEGER (NOT NULL), FOREIGN KEY (id_episode) REFERENCES Episode(id))
Episode table used to have a relationship toward Study and had in code a Criteria query like
sess.CreateCriteria<Episode>.
[…] # multiple filtering criteria added here
.Add(Expression.IsNull("Study"))
Now that the Episode.Study column is gone, how do I request only rows from Episode that are not in relationship with a Study row?
By reading the documentation and the web I have the impression I have to do an outer join onto Study and then filter there, not sure if that is the case or how to express that in the Criteria API.
I am using 5.x.

var criteriaResult = session.CreateCriteria<Episode>("episode")
// more filter
.Add(Subqueries.NotExists(DetachedCriteria.For<Study>().Add(Restrictions.EqProperty("Episode.Id", "episode.Id")).SetProjection(Projections.Id)))
.List();
or
var linqResult = session.Query<Episode>()
// more filter
.Where(e => !session.Query<Study>().Where(s => s.Episode == e).Any())
.ToList();

Related

Rails ActiveRecord query where relationship does not exist based on third attribute

I have an Adventure model, which is a join table between a Destination and a User (and has additional attributes such as zipcode and time_limit). I want to create a query that will return me all the Destinations where an Adventure between that Destination and the User currently trying to create an Adventure does not exist.
The way the app works when a User clicks to start a new Adventure it will create that Adventure with the user_id being that User's id and then runs a method to provide a random Destination, ex:
Adventure.create(user_id: current_user.id) (it is actually doing current_user.adventures.new ) but same thing
I have tried a few things from writing raw SQL queries to using .joins. Here are a few examples:
Destination.joins(:adventures).where.not('adventures.user_id != ?'), user.id)
Destination.joins('LEFT OUTER JOIN adventure ON destination.id = adventure.destination_id').where('adventure.user_id != ?', user.id)
The below should return all destinations that user has not yet visited in any of his adventures:
destinations = Destination.where('id NOT IN (SELECT destination_id FROM adventures WHERE user_id = ?)', user.id)
To select a random one append one of:
.all.sample
# or
.pluck(:id).sample
Depending on whether you want a full record or just id.
No need for joins, this should do:
Destination.where(['id not in ?', user.adventures.pluck(:destination_id)])
In your first attempt, I see the problem to be in the usage of equality operator with where.not. In your first attempt:
Destination.joins(:adventures).where.not('adventures.user_id != ?'), user.id)
you're doing where.not('adventures.user_id != ?'), user.id). I understand this is just the opposite of what you want, isn't it? Shouldn't you be calling it as where.not('adventures.user_id = ?', user.id), i.e. with an equals =?
I think the following query would work for the requirement:
Destination.joins(:adventures).where.not(adventures: { user_id: user.id })
The only problem I see in your second method is the usage of destinations and adventures table in both join and where conditions. The table names should be plural. The query should have been:
Destination
.joins('LEFT OUTER JOIN adventures on destinations.id = adventures.destination_id')
.where('adventures.user_id != ?', user.id)
ActiveRecord doesn't do join conditions but you can use your User destinations relation (eg a has_many :destinations, through: adventures) as a sub select which results in a WHERE NOT IN (SELECT...)
The query is pretty simple to express and doesn't require using sql string shenanigans, multiple queries or pulling back temporary sets of ids:
Destination.where.not(id: user.destinations)
If you want you can also chain the above realation with additional where terms, ordering and grouping clauses.
I solved this problem with a mix of this answer and this other answer and came out with:
destination = Destination.where
.not(id: Adventure.where(user: user)
.pluck(:destination_id)
)
.sample
The .not(id: Adventure.where(user: user).pluck(:destination_id)) part excludes destinations present in previous adventures of the user.
The .sample part will pick a random destination from the results.

Ruby on Rails: Select from database where column equals a value from array

If I have an array of IDs from a table (table1) in my database. Is there a way of querying another table (table2) to select all the records where a column equals a value equal to one of the IDs from table1.
My code so far is:
LabQuestion.where("product_id=#{Product.where(:brand_id => brand_id).pluck(:id)}")
In this code, I am trying to retrieve all the Lab Questions which are linked to all the products from a brand. This code does not work, but I've tried to demonstrate my needs.
Assuming you have setup your relations properly, you can use joins to join the two tables and query them like this:
LabQuestion.joins(:product).where(:products => { :brand_id => brand_id })
You can use includes instead of joins as below
LabQuestion.includes(:product).where(:products => { :brand_id => brand_id })

NHibernate.QueryException: not an association: Id

I wrote a joint query using NHiberNate, but I am getting a NHibernate.QueryException:not an association: Id
This is what it looks like with NHibernate library
TicketRequest ticketAlias = null;
Show showAlias = null;
IList<TicketRequest> results = UnitOfWork.CurrentSession.QueryOver<TicketRequest>(() => ticketAlias)
.JoinAlias(() => ticketAlias.ShowId, () => showAlias.Id)
.Where(() => showAlias.ShowDate >=DateTime.Now)
.List();
return results;
I just want a simple joint statement, and this is what it would have been in SQL
select * from TicketRequest as a join Show as b
on a.Show_id = b.Id
where ShowDate >=GETDATE()
Can someone help and let me know why I am getting a "not an association:Id" error. I have id in the "Show" table, it is a primary key.
Please advise. All helps are greatly appreciated.
You need to specify a many-to-one relation in joins. In your case that is the Show property.
IList<TicketRequest> results = UnitOfWork.CurrentSession.QueryOver<TicketRequest>(() => ticketAlias)
.JoinAlias(() => ticketAlias.Show, () => showAlias)
.Where(() => showAlias.ShowDate >= DateTime.Now)
.List();
PS: You shouldn't map both a many-to-one relation (Show) and an foreign key property (ShowID). Usually you only work with object relations when using an ORM. Only map the plain ID if you really need it for something, but even then only map it as read-only.
You don't have to specify the foreign keys / primary keys when querying with NHibernate. It's an ORM. You write object oriented queries. The keys and relations are specified in the mapping file.
A Join in an NHibernate query is simply specified by the property name with which you navigate to the other property.
That's what the error message means. Id is not an association.

NHibernate - Filtered ManyToMany relationship returns null records

I have two entities (Job and Location) that are connected through a many-to-many relationship.
Recently we implemented soft delete logic on the Location entity and then added a filter on the job Mapping like this:
HasManyToMany(x => x.Locations)
.Table("JobLocation")
.ParentKeyColumn("JobId")
.ChildKeyColumn("LocationId")
.ApplyChildFilter<ExcludeDeletedFilter>("IsDeleted = :condition")
.Cascade.None();
The query for the many-to-many relationship looks like this
select ...
from Job job
left outer join JobLocation jl
on jl.JobId = job.Id
left outer join Location loc
on loc.Id = jl.LocationId and IsDeleted = 0
The problem is that now, when fetching a Job that has some deleted locations, the Locations collection on the job entity contains a null entry for each deleted Location.
What is the best way to handle this soft delete records when they are fetched through a many-to-many relationship. (for one to many, this filter does a great job)
What other alternatives should I consider to do this?
Items in a list are associated with an index. When persisted using a list-style mapping, these indices will be preserved (lists normally don't suddenly rearrange themselves).
If you filter some items, to make the visible items have stable positions, it follows that there must be null items for the hidden elements.
Consider using a different mapping, such as a set, bag or map.

How to build custom PLINQO query by multiple id's?

Here's my table structure
Places
PlaceId PK
Name
...
PlaceCategories
CatId PK
Name
...
PlaceCats
PlaceId PK
CatId PK
Here's my query that pulls Places based on category id (table join)
public static IQueryable<Places> ByPlaceCat(this Table<Places> table, Expression<Func<PlaceCats, bool>> predicate) {
var db = (DataContext)table.Context;
var innerBizBase = db.PlaceCats.Where(predicate);
return db.Places.Join(innerBizBase, a => a.PlaceId, ab => ab.PlaceId, (a, ab) => a);
}
I use it like this:
places = Db.Places.ByPlaceCat(a => a.CatId == 5);
But I want to be able to pull based on a List<int> of category id's. Looking through the generated PLINQO code, a query that pulls by multiple PlaceId's (but not using a joined table) looks like this:
public static IQueryable<Places> ByPlaceId(this IQueryable<Places> queryable, IEnumerable<long> values)
{
return queryable.Where(p => values.Contains(p.PlaceId));
}
How could I essentially merge those two queries, to let me pass in a List<int> of CatId's to query by? This LINQ/PLINQO query is melting my brain. Thanks in advance!
You would need to write a extension method like this:
public static IQueryable<Places> ByPlaceCats(this Table<Places> table, IEnumerable<int> catIds)
{
var db = (TestDataContext)table.Context;
var places = (from placeCat in db.PlaceCats
join place in db.Places on placeCat.PlaceId equals place.PlaceId
where catIds.Contains(placeCat.CatId)
select place);
return places;
}
Please note that the PlaceCats table could be made into a ManyToMany relationship by adding two foreign keys to the proper tables. Once this change has been made than PLINQO will automatically generate the correct code and will create a link between the two tables skipping the intermediary table. So you could get a collection of PlaceCategories associated to the current Places entity by accessing a property on the Places entity.
Please remember to contact us if you have any questions and be sure to check out the community forums located here and PLINQO forums here.
Thanks
-Blake Niemyjski (CodeSmith Support)