struggling with a left join in a nHibernate query - nhibernate

I'm struggling to replicate a simple sql left join in a nHibernate query. Other answers on SO have led me to be more confused as to what is the smartest way to tackle left joins in a domain query.
Example:
2 DB Tables:
Customer
CustId INT PK
Orders
OrderId INT PK
CustId INT FK
Status INT
1 SQL Query:
Select c.CustId from Customer c
left join Orders o on o.CustId = c.CustId and o.Status = 2
where o.OrderId is null
This will retrieve a unique list of Customers who don't have an order in status 2,
Note, it also includes customers who don't have an order at all.
This is a contrived example to simplify this question, but this type of query is very useful and not easy to do any other way.
Imagine nh mappings for "Customer" and "Orders" which simply reflect the example tables above.
Is there a simple way to extract my list of unique, non-status-2 customers in nHibernate, in a query, without resorting to a SQL query or ending up in a select n+1 scenario?
Query preferences:
1 linq-to-nhibernate
2 QueryOver
3 HQL
4 Criteria.
Thanks.

See http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/queryhql.html#queryhql-joins. It's the Hibernate reference, and not the nHibernate reference, but I'd assume they work the same (BTW, this post seems to confirm it):
You may supply extra join conditions using the HQL with keyword.
from Cat as cat
left join cat.kittens as kitten
with kitten.bodyWeight > 10.0
So, in your case, it should look like
select c.CustId from Customer c
left join Orders o with o.Status = 2

NHibernate 3.0 has an overload method for ICriteria .CreateAlias which takes 4 params, the last param is withClause.
Here is an example:
DetachedCriteria criteria = DetachedCriteria.For<Models.BO.Customer>("customer")
.CreateAlias(ReflectionHelper.PropertyName<Models.BO.Customer>(x => ((Models.BO.Interfaces.ICustomerQueryOnly) x).Tasks),
"activeTasks", JoinType.LeftOuterJoin, Restrictions.IsNotNull("activeTasks.LockedBy")
)
.CreateAlias(ReflectionHelper.PropertyName<Models.BO.Customer>(x => ((Models.BO.Interfaces.ICustomerQueryOnly) x).Tasks2),
"availableTasks", JoinType.LeftOuterJoin,
availableTasksRestraction
)
.Add(Restrictions.Eq("CustomerBase", _customerBase))
.Add(Restrictions.Eq("IsActive", true));
which endup with something like:
FROM Customers c
left join Tasks t on t.customerId = c.Id and (t.DeletedDate is null and
t.lockedById is null and [etc])
left join Tasks activetasks [etc]
where [...]
In this example I need to extract all customers and number of available tasks and number of active task for each customer.

If the entities are unrelated and you don't wish to map a relation, you can use a theta join. See here
Maybe something like
Select c from Customer c, Order o
where o.CustId = c.CustId and o.Status = 2

Related

How to build a complex sql query?

Database design here
I want to get records from the product_spec_data table that are associated with products whose category_id is 5.
Please help me make a query to the database...
All of the relationship are clearly laid out, a simple join across tables would works.
SELECT psd.*
FROM product_spec_data psd
INNER JOIN product_spec_values psv ON psd.id = psv.product_spec_data_id
INNER JOIN products prod ON psv.product_id = prod.id
INNER JOIN categories cat ON prod.category_id = cat.id
-- with category id = 5
WHERE cat.id = 5;
I am not sure what do you mean by complex query. I guess your question is not complete. I think the query for your question (if it is T-SQL) will be as follows
select d.*
from product_spec_data d
left outer join product_spec_values v on d.id=v.product_spec_data_id
left outer join products p on v.product_id=p.id
where p.category_id=5
(I think in MySQL also above syntax will remain same, you may remove [outer] clause in MySQL)

How to put conditions on left joins

I have two tables, CustomerCost and Products that look like the following:
I am joining the two tables using the following SQL query:
SELECT custCost.ProductId,
custCost.CustomerCost
FROM CUSTOMERCOST Cost
LEFT JOIN PRODUCTS prod ON Cost.productId =prod.productId
WHERE prod.productId=4
AND (Cost.Customer_Id =2717
OR Cost.Customer_Id IS NULL)
The result of the join is:
joins result
What i want to do is when I pass customerId 2717 it should return only specific customer cost i.e. 258.93, and when customerId does not match then only it should take cost as 312.50
What am I doing wrong here?
You can get your expected output as follows:
SELECT Cost.ProductId,
Cost.CustomerCost
FROM CUSTOMERCOST Cost
INNER JOIN PRODUCTS prod ON Cost.productId = prod.productId
WHERE prod.productId=4
AND Cost.Customer_Id = 2717
However, if you want to allow customer ID to be passed as NULL, you will have to change the last line to AND Cost.Customer_Id IS NULL. To do so dynamically, you'll need to use variables and generate the query based on the input.
The problem in the original query that you have posted is that you have used an alias called custCost which is not present in the query.
EDIT: Actually, you don't even need a join. The CUSTOMERCOST table seems to have both Customer and Product IDs.
You can simply:
SELECT
Cost.ProductId, Cost.CustomerCost
FROM
CUSTOMERCOST Cost
WHERE
Cost.Customer_Id = 2717
AND Cost.productId = 4
You seem to want:
SELECT c.*
FROM CUSTOMERCOST c
WHERE c.productId = 4 AND c.Customer_Id = 2717
UNION ALL
SELECT c.*
FROM CUSTOMERCOST c
WHERE c.productId = 4 AND c.Customer_Id IS NULL AND
NOT EXISTS (SELECT 1 FROM CUSTOMERCOST c2 WHERE c2.productId = 4 AND c2.Customer_Id = 2717);
That is, take the matching cost, if it exists for the customer. Otherwise, take the default cost.
SELECT custCost.ProductId,
custCost.CustomerCost
FROM CUSTOMERCOST Cost
LEFT JOIN PRODUCTS prod
ON Cost.productId =prod.productId
AND (Cost.Customer_Id =2717 OR Cost.Customer_Id IS NULL)
WHERE prod.productId=4
WHERE applies to the joined row. ON controls the join condition.
Outer joins are why FROM and ON were added to SQL-92. The old SQL-89
syntax had no support for them, and different vendors added different,
incompatible syntax to support them.

How to avoid coalesce for duplicate tables

I have the following database structure:
tblShelter
ShelterId
PetId
ClientId
ShelterName
Address
tblClient
ClientId
PetId
tblPet
PetId
PetName
So, Pet can belong to a shelter or to a registered shelter client (adopted pet).
So my Shelter table has data as follows:
ShelterId PetId ClientId ShelterName
1 100 NULL Test
1 NULL 101 Test
1 102 NULL Test
So, I need to get all PetNames for each shelter (Direct shelter pets or clients)
Here's my query:
Select Coalesce(p.PetName, pclient.PetName) as PetName
from tblShelter s
Left Join tblPet p
on p.PetId = s.PetId
Left Join (select p2.PetId, p2.PetName
from tblClient c
join tblPet p2
on c.PetId = p2.PetId) pclient
on s.PetId = pclient.PetId
where shelterId=1
Question: Is it possible to optimize this query? Currently, it doesn't look pretty. Please note that database structure is shown for simplicity. Unfortunately, it cannot be modified. Is it possible to get rid of coalesce in a smart way or get rid of sub query?
Yes - change your JOIN order:
Select p.PetName
from tblShelter s
Left Join tblClient c
on s.ClientId = c.ClientId
inner join tblPet p
on p.PetId = s.PetId or
p.PetId = c.PetId
where shelterId=1
JOINs aren't strictly based on joining two tables - the ON clauses can reference any table that has already been joined to form either side of the current join. So here, we allow the join to tblPet to be based on either the direct reference (via s.PetId) or via the optionally joined in tblClient.
Of course, you can further simplify the above query by making a smarter ON condition for the join to tblPet - but the way to do that would be to reintroduce the feature you're seeking to avoid - COALESCE. I've no idea what your reasons are for seeking to avoid that feature though.

What is wrong with my join in this query?

Im practicing basic SQL with this site http://www.sqlishard.com/Exercise
Here is the question:
S5.0 - INNER JOIN
Now that we can pull data out of a single table and qualify column
names, let's take it a step further. JOIN statements allow us to
'join' the rows of several tables together using a condition to define
how they match one another. SELECT [columns] FROM FirstTable INNER
JOIN SecondTable ON FirstTable.Id = SecondTable.FirstTableId
Try using the INNER JOIN syntax to SELECT all columns from the
Customers and Orders tables where the CustomerId column in Orders
matches the Id column in Customers. Since both tables have an Id
column, you will need to qualify the Customers id in the WHERE clause
with either the table name or a table alias.
Here is my answer:
SELECT *
FROM Customers AS c
INNER JOIN Orders AS o ON c.ID = o.ID
WHERE o.CustomerID = c.ID
The site says im wrong? Could anyone explain where i'm going wrong?
EDIT: I see now I dont need the WHERE clause, but the question states..
you will need to qualify the Customers id in the WHERE clause with
either the table name or a table alias.
Hence my confusion. Thanks none the less.
Try this way:
SELECT c.ID,o.ID
FROM Customers AS c
INNER JOIN Orders AS o ON o.CustomerID = c.ID
or using where clause
SELECT *
FROM Customers AS c, Orders AS o
where o.CustomerID = c.ID
If you use JOIN.. ON, you do not need where clause

NHibernate HQL SELECT TOP in sub query

Is there a way of using SetMaxResult() on a sub query? Im writing a query to return all the order items belonging to the most recent order. So I need to limit the number of records on the sub query.
The equivalent sql looks something like:
SELECT i.*
FROM tbl_Orders o
JOIN tbl_OrderItems i on i.OrderId = o.Id
WHERE
o.Id in (SELECT TOP 1 o.Id FROM tbl_Orders o orderby o.Date desc)
Im using hql specifically because criteria api doesnt let you project another domain object (Im querying on orders but want to return order items)
I know that hql doesnt accept "SELECT TOP", but if I use SetMaxResult() it will apply to the outer query, not the subquery.
Any ideas?
From NHibernate 3.2 you could use SKIP n / TAKE n in hql at the end of the query.
You query will be:
SELECT i.*
FROM tbl_Orders o
JOIN tbl_OrderItems i on i.OrderId = o.Id
WHERE
o.Id in (SELECT o.Id FROM tbl_Orders o orderby o.Date desc take 1)
Just query the orders (and use SetMaxResult) and do a 'fetch join' to ensure all orderitems for the selected orders are loaded straight away.
On the returned orders you can then access the order items without this resulting in a new SQL statement being sent to the database.
I encountered this problem too, but didn't found a solution using HQL...
Subqueries with top would be very nice, since this is faster then doing a full join first. When doing a full join first, the SQL Servers join the table first, sort all rows and select the top 30 then. With the subselect, the top 30 column of one table are taken and then joined with the other table. This is much faster!
My query with Subselect takes about 1 second, the one with the join and sort takes 15 seconds! So join wasn't an option.
I ended up with two queries, first the subselect:
IQuery q1 = session.CreateQuery("select id from table1 order by id desc");
q1.SetMaxResults(100);
And then the second query
IQuery q2 = session.CreateQuery("select colone, coltwo from table2 where table1id in (:subselect)");
q2.SetParameterList("subselect", q1.List());