0

I was searching for similar problem on google and stackoverflow for almost 2 hours but did not find any solution.

I have 2 tables with relation 1 to many.

1) [Accounts]
PK Account_Id
int User_ID


2) [Temporary_Accounts]
Fk Account_Id
char IsAccepted   {'1','0',null}
varchar name

And 2 mapped classes

1) Acc
int Id;
User user;
TempAcc Temp; //cause each Account can have 0 or one TempAcc (with IsAccepted == null)

2)TempAcc
int Id;
bool IsAccepted;
string name;

I want to display all accounts for given user_id with additional information(f.e name) for Accounts which has record in [Temporary_Accounts] and IsAccepted == null.

so the SQL should look like:

select acc.Account_Id, acc.User_Id, tempacc.Name 

from Account acc left join Temporary_Account tempacc 
on (acc.Account_ID = tempacc.Account_Id and tempacc.IsAccepted is null)
where (acc.User_Id = 65);

but my IQueryOverquery:

IQueryOver<Acc> query = (...)
query.JoinAlias(f => f.Temp,
                () => Temp,
                JoinType.LeftOuterJoin)
     .Where(f => f.Temp.IsAccepted == null)
     .And(f => f.user.id == userid);

generates such sql:

select acc.Account_Id, acc.User_Id, tempacc.Name 

from Accounts acc left join Temporary_Accounts tempacc 
on (acc.Account_ID = tempacc.Account_Id)
where (acc.User_Id = 65 and tempacc.IsAccepted is null);

so I am getting less results than in first correct query.

Do you have any Idea what should I change or what could I do to obtain results from first query ? My Idea was to leftjoin Accounts table with subquery which selects all IsAccepted=null accounts from Temporary_Accounts table ,but I am not sure how to do it in Iqueryover or Icriteria.

I will be grateful for any advices

LeftyX
  • 35,328
  • 21
  • 132
  • 193
Kostrzak
  • 161
  • 4
  • 19

1 Answers1

5

Since you have a 1-Many between Acc and Temp your sample sql will produce a Cartesian product.

The Queryover you will need uses a Subquery and looks something like the following:

Acc accountAlias = null;
var subQuery = QueryOver.Of<Temp>()
               .Where(x=>x.IsAccepted==null)
               .And(x=>x.Account.Id==accountAlias.Id);

var results = session.QueryOver<Acc>(()=>accountAlias)
              .Where(x=>x.User.Id==65)
              .WithSubquery.WhereExists(subQuery);

Producing SQL like this:

select *
from Accounts a
where a.User_Id=65
and exists (
    select t.Account_Id
    from Temporary_Accounts t
    where t.IsAccepted is null and t.Account_Id=a.Account_Id
)

This article on nhibernate.info is very helpful for figuring out complex queries with QueryOver.

UPDATE:

If you need to also find Accounts which do not have any corresponding rows in Temporary_Accounts then you need two subqueries and a Disjunction.

Acc accountAlias = null;
var hasTempAccount = QueryOver.Of<Temp>()
               .Where(x=>x.IsAccepted==null)
               .And(x=>x.Account.Id==accountAlias.Id);

var doesNotHaveTempAccount = QueryOver.Of<Temp>()
               .And(x=>x.Account.Id==accountAlias.Id);

var results = session.QueryOver<Acc>(()=>accountAlias)
  .Where(x=>x.User.Id==65)
  .Where(Restrictions.Disjunction()
    .Add(Subqueries.WhereExists(hasTempAccount))
    .Add(Subqueries.WhereNotExists(doesNotHaveTempAccount))     
  );

UPDATE 2:

Since NH 3.2 you can add extra conditions to a JOIN. See this answer for further details: Adding conditions to outer joins with NHibernate ICriteria/QueryOver query

Temp tempAlias = null;
Account accountAlias = null;
dto dto = null;
var results = Session.QueryOver<Account>(()=>accountAlias)
  .JoinAlias(x=>x.TempAccounts,()=>tempAlias,JoinType.LeftOuterJoin,
    Restrictions.IsNull(Projections.Property(()=>tempAlias.IsAccepted))
  )
  .Where(x=>x.Account.Id==65)
  .SelectList(list=>list
      .Select(()=>accountAlias.Id).WithAlias(()=>dto.AccountId)
      .Select(()=>accountAlias.User.Id).WithAlias(()=>dto.UserId)
      .Select(()=>tempAlias.Name).WithAlias(()=>dto.TempAccName)
  )
  .SetResultTransformer(Transformers.AliasToBean<dto>())
  .List<dto>();
Community
  • 1
  • 1
Mark Perry
  • 1,705
  • 10
  • 12
  • Thanks Mark. I may be wrong but I think it will create some kind of pseudo-innerjoin, and I need left outer join. It will return only Accounts with Temporary Accounts but won't return Accounts without them (which I also need). – Kostrzak Dec 08 '11 at 14:48
  • So you would like: "A list of Accounts which have an entry in TempAccounts with a IsAccepted of null and also all Accounts that don't have an entry in the TempAccounts table as well"? – Mark Perry Dec 08 '11 at 14:51
  • Unfortunately it is not such easy. For example, if there are 20 accounts for user_id = 56, I want to display exactly 20 accounts , but for these accounts which has record with isaccpted = null in temporary_accounts table, I want to get information about that temporary_account name. Simple left join + where won't work cause for 1 Account_Id there can be a)no records in temporary table or b) 1/many records with isAccepted='1' c)1/many records with isAccpted='0' or d) 0/1 record with isAccepted=null. The first query in my question does exctly what I want, but how to convrt it to IqueryOver? – Kostrzak Dec 08 '11 at 23:15
  • Thx for update Mark but it isn't the solution. That query won't return for example Account(s) which has User_Id= 65 and IsAccepted='0'. As I said I want to display such accounts but without additional information (tempacc.Name). I just need some kind of left join with part of Temporary_Accounts table (accounts with IsAccepted=null). I've tried to find a solution but it looks like QueryOver/ICriteria doesn't have any equivalent for "WITH" clause which HQL has, so it is imposible to add such condition:"on (acc.Account_ID = tempacc.Account_Id and tempacc.IsAccepted is null)" – Kostrzak Dec 12 '11 at 10:06
  • I have updated my answer with an example of adding extra join conditions using QueryOver. – Mark Perry Dec 12 '11 at 10:39
  • Yes. Simple ".JoinAlias(x=>x.TempAccounts,()=>tempAlias,JoinType.LeftOuterJoin, ()=>tempAlias.IsAccepted)" added to query solved my problem! Thx for your help, but please add that such overload of JoinAlias is available since newest 3.2 version cause in our previous 3.1 version JoinAlias was accepting only 3 parameters. – Kostrzak Dec 12 '11 at 22:30