How to setup NHibernate criteria to perform such query?
SELECT
COUNT(1) AS CNT,
l.CAMPAIGN_ID AS CAMPAIGN_ID,
MAX(mc.NAME) AS CAMPAIGN_NAME
FROM
SA_LEADS l
INNER JOIN
SA_CAMPAIGNS mc
ON
l.CAMPAIGN_ID = mc.ID
GROUP BY
CAMPAIGN_SOURCED_ID
ORDER BY
CNT DESC
I have mappings for both tables SA_LEADS and SA_CAMPAIGNS.
For me I would start with the element that I am trying to group by so I would start with a
base of campaign. I can't tell from your query but I would assume that your mappings have a way of moving from campaign to sales lead. I have assumed this is called "SALES_LEAD_LIST"
I also like to make a small class to receive the projection result so would create something to hold the result.
public class CAMPAIGN_PERFORMANCE
{
public CAMPAIGN_PERFORMANCE() {}
public int CP_ID {get; set;}
public string CP_NAME {get; set;}
public int CP_NO_OF_SALES_LEADS {get; set;}
}
Once you have something to put your projection result in you can create a standard criteria and just push it into you new class through a projection
ICriteria criteria = base.Session.CreateCriteria(typeof(SA_CAMPAIGNS));
criteria.CreateAlias("SALES_LEADS_LIST", "SA_LEADS", JoinType.InnerJoin);
criteria.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("ID"), "CP_ID")
.Add(Projections.Property("CAMPAIGN_NAME"), "CP_NAME")
.Add(Projections.CountDistinct("SA_LEADS.ID"), "CP_NO_OF_SALES_LEADS")
.Add(Projections.GroupProperty("ID"));
.Add(Projections.GroupProperty("CAMPAIGN_NAME")));
IList<CAMPAIGN_PERFORMANCE> cpProjections = criteria
.SetResultTransformer(
new AliasToBeanResultTransformer(typeof(CAMPAIGN_PERFORMANCE)))
.List<CAMPAIGN_PERFORMANCE>();
Related
I'm using SpringBoot 2.4 with JPA 2.0 and I have a Model like following:
#Entity
#Data
public class Nation {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(mappedBy = "nation")
private List<Country> country;
}
And:
#Entity
#Data
public class Country {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
private Nation nation;
}
Now I would to like to find all Nation filtered by their ID and by country ID. In pure SQL is simply something like:
select * from nation n, country c where n.id = [nation_id] AND c.id = [country_id];
Therefore I thought about doing this way with JPA:
#Query("select n from Nation n JOIN n.country c where n.id = ?1 AND c.id = ?2)
public List<Nation> find(Integer nationID, Integer countryID);
But it doesn't work; it is filtered by Nation but not by countries.
If I print the Hibernate generate SQL by adding:
spring.jpa.show.sql=true
I can see that the query is exactly the same I posted above in pure SQL. The problem occours when I invoke nation.getCountry(), it generates another query that load all country connected to given Nation id.
Is there a way to solve this?
I believe that you can use JPA DTO projections for this case ...
So, in your Nation class create a constructor like this:
/**
* Copy Constructor. It creates a "detached" copy of the
* given nation with only a copy of the provided country.
*/
public Nation(Nation n, Country c) {
super();
this.id = n.id;
// copy other nation values ...
this.country.add( new Country(c) );
}
Modify your query to invoke such constructor ... something like this (assuming that Nation is declared in the java package my.domain):
#Query("select new my.domain.Nation(n, c) from Nation n JOIN n.country c where n.id = ?1 AND c.id = ?2)
Disclaimer:
I have done this using JPA and hibernate. So far, I haven't tested with spring, but I guess this does not matter because your JPA provider is probably hibernate too.
I have done this using only parts (or attributes) of the target entities (as described in the provided link) ... I never have pass the full entities (as I suggest to do in the query). Let me explain, in the cases where I have applied this, I have a constructor like Nation(String name, int population) and in the query I do something like: SELECT new my.domain.Nation(n.name, c.population) ... Try to pass the full entities to see if it works ... if it fails, fallback to create a constructor that receives only the attributes that you require for your business case.
I have two entities, Location and Industry, and a link-table between them. I've configured a many-to-many relationship, in both directions, between the two entities.
In a search query, I'm trying to select Locations that are associated with a list of industries.
After days and days of trying to wrangle the criteria API, I've decided to drop down to HQL and abandon the criteria API. But even that isn't going well for me - it seems, regardless of whether I hand-write this HQL query, or let the criteria API do it, I end up with the same result.
I managed to produce the right result in two ways - like this:
var q = Data.Query("select distinct loc from Location loc join loc.Industries ind where ind in (:ind)");
q.SetParameterList("ind", new Industry[] { Data.GetIndustry(4), Data.GetIndustry(5) });
And (better) like that:
var q = Data.Query("select distinct loc from Location loc join loc.Industries ind where ind.id in (:ind)");
q.SetParameterList("ind", new int[] { 4, 5 });
Unfortunately, both result in a sub-optimal query:
select distinct
location0_.Id as Id16_,
location0_.Name as Name16_,
(etc.)
from Location location0_
inner join LocationIndustry industries1_
on location0_.Id=industries1_.LocationId
inner join Industry industry2_
on industries1_.IndustryId=industry2_.Id
where
industry2_.Id in (? , ?)
Why the extra join?
Is NH not smart enough to know that the Industry.Id property, being the only Industry-property involved in the query, is stored in the LocationIndustry link-table, and there is no need for the extra join to the Industry table itself?
Or am I doing something wrong?
Ideally, the most intuitive thing for me would be to write:
from Location loc where loc.Industries in (:ind)
This does not work - it throws an error and says it does not know about the Industries property. I guess because Industries, being a "property" in programming terms, is actually a "relationship" in terms of DBMS.
What is the simplest and most efficient way to write this query in HQL?
Thanks!
I'm not sure you can avoid this extra join given the mapping strategy you have used.
You could avoid it by using an intermediary class but this would mean you would need a class structure like this:
public class Industry {
//Other stuff
public virtual List<LocationIndustry> LocationIndustries {get; set:;}
}
public class LocationIndustry {
public virtual Location Location {get; set;}
public virtual Industry Industry {get; set;}
}
public class Location {
//normal stuff
public virtual IList<LocationIndustry> LocationIndustries {get; set;}
}
Then you can query on the LocationIndustry class and avoid the join to Location.
Is there a way to achieve SQL like this with NHibernate ICriteria or QueryOver?
select *
from [BlogPost] b
inner join (select blogpost_id, count(*) matchCount
from [Tag]
where name in ('tag X', 'tag Y')
group by blogpost_id
) tagmatch
on tagmatch.blogpost_id = b.Id
order by tagmatch.matchCount desc
The aim is to rank blog posts by the number of matching tags so that a post with both tag X and tag Y comes above posts with just tag X.
I've got this so far:
DetachedCriteria
.For<Tag>("tag")
.Add(Restrictions.In(Projections.Property<Tag>(x => x.Name), tags.ToArray()))
.SetProjection(Projections.Group<Tag>(t => t.BlogPost))
.CreateCriteria("BlogPost")
.SetFetchMode("BlogPost", FetchMode.Eager)
.AddOrder(Order.Desc(Projections.RowCount()));
However, the resulting query doesn't join fetch BlogPost. Instead it returns just the ids, which leads to select n+1 when the BlogPosts are iterated.
public class BlogPost
{
...
ISet<Tag> Tags {get; set;}
}
public class Tag
{
BlogPost BlogPost { get; set; }
string Name { get; set; }
}
This looks like a similar issue.
Is this now possible with NHibernate 3?
If not, is there an alternative solution?
I can change schema & domain model if necessary. I don't want to use SQL or HQL if possible.
I know this question was put some time ago, but I want to do about the same thing, please take a look to my question here, and this guy here, maybe you can use the idea.
public class City
{
virtual public long Id { get; set; }
virtual public string Name { get; set; }
}
City table contains duplicated Names and I want to remove duplicates. I also want the results to be ordered by Id.
First I thought about the following query.
select distinct Name from City order by Id;
But this breaks with 'ORDER BY items must appear in the select list if SELECT DISTINCT is specified.' exception. After seeing http://weblogs.sqlteam.com/jeffs/archive/2007/12/13/select-distinct-order-by-error.aspx I think I should do:
select Name from City group by Name order by min(Id)
So my question is how can I do this query with QueryOver?
This is possible in ICriteria:
var list =
session.CreateCriteria<City>()
.SetProjection(Projections.Group("Name"))
.AddOrder(Order.Asc(Projections.Min("Id")))
.List<string>();
But this is not currently possible in QueryOver, because the .OrderBy(IProjection) overload is missing. Once the missing overload has been added it should look something like:
var list =
s.QueryOver<City>()
.Select(Projections.Group<City>(p => p.Name))
.OrderBy(Projections.Min<City>(c => c.Id)).Asc
.List<string>();
Note that the Projections overloads are there just now, so you can write the following (typesafe) query in ICriteria:
var list =
session.CreateCriteria<City>()
.SetProjection(Projections.Group<City>(c => c.Name))
.AddOrder(Order.Asc(Projections.Min<City>(c => c.Id)))
.List<string>();
So, what I've found is pretty simple...
var query = session.QueryOver<MyModel>()
// Conditions here
.OrderBy(m => m.GetThisDistinctField).Desc() // ...or Asc()...
.SelectList(m => m.SelectGroup(g => g.GetThisDistinctField));
var result = query.List<FieldDataType>().ToList();
return result;
To get an ordered query in queryover, start with a query that includes whatever criteria you need, but then add the SelectList/SelectGroup setup in order to get the distinct list. In other words, it's sort of like NHibernate can take a regular query, and then do special stuff to make it a select distinct query.
This is a solution I'm using in a current project I'm working on; I hope it helps someone else too.
Assuming the following:
public class Order
{
public virtual int OrderId {get;set}
public virtual ISet<Product> Products {get;set}
}
public class Product
{
public virtual int ProductId {get;set}
public virtual string ProductName {get;set}
}
How would you query using the criteria api so that only an order with a specific orderid is returned and its Product collection should also be filtered down to Products whose Name start with the lettter P?
Simplest approach is to use an alias:
var productIdToSelect = 9;
var crit = Session.CreateCriteria(typeof(Order));
crit.CreateAlias("Product", "prod");
crit.Add(Expression.Eq("prod.Id", productIdToSelect));
var result = crit.List<Order>();
I would go about this with a DetachedCriteria:
DetachedCriteria crit = DetachedCriteria.For<Order>();
crit.Add(Restrictions.Eq("OrderId",orderID);
crit.CreateCriteria("Products","products");
crit.Add(Restrictions.Like("products.ProductName","P%");
crit.List();
and then executing the criteria and getting the results.
I don't know the code you would have to write, but a point in the right direction:
http://www.nhforge.org/doc/nh/en/index.html#querycriteria-associations (14.4)
The key seems to be:
.SetResultTransformer(CriteriaUtil.AliasToEntityMap)
The documentation shows an example with cats and kittens.
Note that the kittens collections held by the Cat instances returned by the previous two queries are not pre-filtered by the criteria! If you wish to retrieve just the kittens that match the criteria, you must use SetResultTransformer(CriteriaUtil.AliasToEntityMap).
Set up a filter on the mapping of the collection.
<filter name="letterFilter" condition="ProductName like ':letterSupplied'"/>
Then before running the Order query enable the filter
session.EnableFilter("letterFilter").SetParameter("letterSupplied", "P%");
then run the query
Order ord = session.CreateCriteria<Order>().Add(Restrictions.IdEq(suppliedId)).UniqueResult<Order>();
Note that the single quotes in the filter definition may not be required and also i place the % symbol with the supplied parameter as i don't know how NH would react a filter like
<filter name="letterFilter" condition="ProductName like ':letterSupplied%'"/>
or
<filter name="letterFilter" condition="ProductName like :letterSupplied%"/>