Linq to NHibernate generating multiple joins to the same table - sql

When I have a reference to the same table in my select as in my where clause, linq to Nhibernate generates two joins, one for the select and one for the where. i.e.
from child in Session.Query<Child>()
where child.Parent.Name == "Bob"
select new Info
{
ParentAge = child.Parent.Age,
ChildName = child.Name
};
Generates SQL like:
Select this_.Name,
parent1.Age
From Child this_
left join Parent parent1 on child.ParentId = parent1.Id,
Parent parent2
Where child.ParentId = parent2.Id and parent2.Name = 'Bob'
I would have thought I should get SQL more like:
Select this_.Name,
parent1.Age
From Child this_
inner join Parent parent1 on child.ParentId = parent1.Id
Where parent1.Name = 'Bob'
Is there a way to structure the query to get this?
Does it matter?

You can prevent NHibernate from doing this by using a transparent identifier, so that your query looks like this:
from child in Session.Query<Child>()
let p = child.Parent
where p.Name == "Bob"
select new Info {
ParentAge = p.Age,
ChildName = child.Name
};

Have you tried comparing the query execution plan for each in SSMS? If the duplicated join is eliminated in SQL Server, then it doesn't matter. I've found that to be the case in a few instances where I thought the generated query was going to be very inefficient, but after optimization it ends up exactly the same as a query that looks much better.

Related

In SQL, how can I select all parents that have children?

Let's say I have two related tables parents and children with a one-to-many relationship (one parent to many children). Normally when I need to process the information on these tables together, I do a query such as the following (usually with a WHERE clause added in):
SELECT * FROM parents INNER JOIN children ON (parents.id = children.parent_id);
How can I select all parents that have at least one child without wasting time joining all of the children to their parents?
I was thinking of using some sort of OUTER JOIN but I am not sure exactly what to do with it.
(Note that I am asking this question generally, so don't give me an answer that is tied to a specific RDBMS implementation unless there is no general solution.)
As I put earlier in comments:
Solution with LEFT JOIN and GROUP BY:
SELECT p.parents.id FROM parents p
LEFT JOIN children c ON (p.parents.id = c.children.parent_id)
WHERE children.parent_id IS NOT NULL
GROUP BY p.parents_id
The same with DISTINCT:
SELECT DISTINCT p.parents.id FROM parents p
LEFT JOIN children c ON (p.parents.id = c.children.parent_id)
WHERE children.parent_id IS NOT NULL
It should work in most SQL dialects, though some require as when assigning table aliases.
The above is not tested. Hopefully I made no typos.
I think that the simplest solution that avoids a JOIN would be:
SELECT * FROM parents WHERE id IN (SELECT parent_id FROM children);
try this
select parent_id,(select count(1) from children where parent_id = x.parent_id)
from parent x where
(select count(1) from children where parent_id = x.parent_id) > 0

SELECT When child record doesn't exist

I have two tables parent and children. The parent.mopid and children.mopid is the connection between the two tables. How would I write a SELECT that would end result show me just the parent records where there are no children records?
Use the NOT IN function:
SELECT * from parent
where parent.mopid NOT IN (SELECT mopid from children)
This will return all rows from the parent table that do not have a corresponding mopid in the childrens table.
If you have a lot of rows, a LEFT JOIN is often quicker than a NOT IN. But not always - it depends on the data so please try this answer and the one from #aktrazer and see which works best for you.
SELECT parent.*
FROM parent
LEFT JOIN children ON parent.mopid = children.mopid
WHERE children.mopid IS NULL
If there isn't a children row for the mopid, parent.mopid will have a value but child.mopid will be null.
SELECT * from parent p where NOT EXISTS
( select mopid from children c where p.mopid = c.mopid)
This should take care of the nulls as well
This link will explain you the difference between NOT IN and NOT EXISTS
NOT IN vs NOT EXISTS

Why doesn't ICriteria pass variable value to the withClause?

I'm trying to restrict a left outer join with a simple restriction but it fails with an sql exception.
System.Data.SqlClient.SqlException: Must declare the scalar variable "#p1".
My ICriteria:
var r = session.CreateCriteria<Parent>("p")
.CreateCriteria("p.Children", "c", NHibernate.SqlCommand.JoinType.LeftOuterJoin, Restrictions.Eq("c.Name", "John Doe"))
.List();
The desired SQL:
SELECT * FROM Parents p
LEFT OUTER JOIN Children c ON c.ParentID = p.ID AND c.Name = 'John Doe'
Do I have to add the variable values in any way? The SQL generated by NHibernate is correct but the variable just isn't sent to the server.
I think that you may have found a bug in NHibernate ICriteria where you join with LeftOuterJoin on one collection but entity has two or more collections. In this case it will generate joins with 'and' on both collections with two parameters, but only supply one. And database will throw:
Insufficient parameters supplied to the command
It might be a good idea to open a bug. In the mean time, this is supported with HQL:
var query = session.CreateQuery(
"select p from Parent p left outer join p.Children as c with c.Name = :name");
query.SetString("name", "John Doe");
var parents = query.List<Parent>();

SQLite3 and "cascade" SELECTion

I have a parent table and a child table related to the parent table by some REFERENCE.
Suppose I exec a SELECT statement on the child and that it returns the at least one result. Can I arrange for my search to automatically yield all the content of all related parents with this child too?
Or must I always take the reference from the child and put this in a second SELECT statement and exec this myself?
You can use subqueries:
SELECT *
FROM Parent
WHERE Parent.Id IN (SELECT ParentId
FROM Child
WHERE Whatever_was_your_original_query)
Or a good old join:
SELECT Parent.*
FROM Parent INNER JOIN Child ON Parent.Id = Child.ParentId
WHERE Whatever_you_want_to_query
This is the very basic purpose of SQL. You will JOIN the two tables together to create one set of result rows with some or all columns from BOTH tables included.
For more info, see this page.

SQL Query to retrieve data while excluding a set of rows

I have basically four tables (SQL Server):
Objects:
id
ObjectName
Components
id
ComponentName
ObjectsDetails:
ObjectID
ComponentID
ExclusionTable
id
ComponentID
Basically, these tables describe Objects and what Objects are made of (what components)
For example, Object "A" may be made out of component "A" and component "B".
In this case, the tables would be populated this way:
Objects:
id ObjectName
1 A
Components:
id ComponentName
1 A
2 B
ObjectDetails:
ObjectID ComponentID
1 1
1 2
Now, "ExclusionTable" may have a list of components that are to be excluded from a search (therefore, excluding entire objects if the object is made out of at least one of those components).
For example, I would like to ask:
"Give me all the Objects that are not made out of components A and B".
Therefore, my question is:
Is there a way to write a query for that ? No views, no stored procedures please.. my SQL engine does not support that.
I tried something like:
SELECT DISTINCT ObjectName FROM Objects INNER JOIN ObjectsDetails ON Objects.id =
ObjectDetails.ObjectID WHERE ObjectsDetails.ComponentID NOT IN (1,2)
in case ExclusionTable tells us that Components A and B needs to be excluded.
Of course, that doesn't work...
I tried a few variations using WHERE NOT EXISTS (SELECT * FROM ExclusionTable) but I am not proficient enough in SQL to understand how to get it to work using one query only (if it is even possible).
Thanks!
You should avoid doing queries with [not] in (select ...)
SELECT DISTINCT ObjectName
FROM Objects
INNER JOIN ObjectsDetails ON Objects.id = ObjectDetails.ObjectID
LEFT JOIN ExclusionTable on ExclusionTable.ComponentId = ObjectsDetails.ComponentID
where ExclusionTable.ComponentId is null;
This will retrieve only rows for which the ComponentID is not in ExclusionTable.
Update:
SELECT ObjectName
FROM Objects
INNER JOIN ObjectsDetails ON Objects.id = ObjectDetails.ObjectID
LEFT JOIN ExclusionTable on ExclusionTable.ComponentId = ObjectsDetails.ComponentID
group by ObjectName
having count(distinct ObjectsDetails.ComponentID) = sum(case when ExclusionTable.id is null then 1 else 0 end)
New approach, I think the only other way I could do it is basically to compare the number of components per object with the number of components in the object not included on the list. When these number are equal, no component is on the excluded list and we can show the object.
I'm sorry I can't make a test right now, please use EXPLAIN select ... to compare the queries, if they work.
Basically, if you need to get all objects not made from A or B, you need to get all objects EXCEPT those made from A or B.
SELECT DISTINCT Id, ObjectName
FROM Objects
WHERE Id NOT IN (
SELECT DISTINCT ObjectDetails.ObjectID
FROM ObjectDetails
INNER JOIN Components ON ObjectDetails.ComponentID = Components.Id
WHERE Components.ComponentName = 'A' OR Components.ComponentName = 'B'
)
Would that be what you're looking for?
EDIT: Of course, you can omit the join if you already have the component ids - then just put those in the where clause to filter them out.
select id, objectname
from Objects
left outer join
( select objectid from ObjectsDetails od inner join Exclusiontable et
on od.ComponentID= et.ComponentID) excludedid
on Objects.ID = excludedid.ObjectID and excludedid.ObjectID is null