LINQ join query returning null - entity-framework

I've three tables.
Table A
id name des table2 table3
1 xyz TableA_des1 null 1
2 abc TableA_des2 1 2
3 hgd TableA_des2 2 3
Table B
id name des Active
1 xyz TableB_des1 1
2 abc TableB_des2 1
3 hgd TableB_des2 1
Table C
id name des Active
1 xyz TableC_des1 1
2 abc TableC_des2 1
3 hgd TableC_des2 1
LINQ Query
var res = (from a in TableA
where id = 1
join b in TableB on a.table2 equals b.id into ab
from bdata in ab.DefaultIfEmpty()
where bdata.Active = true
join c in TableC on a.table3 equals c.id into ac
from cdata in ac.DefaultIfEmpty()
where cdata.Active = true
select new { data1 = a.name, data2 = bdata?? string.Empty, data3 = cdata?? string.Empty})
The about query is giving null. On debugging variable res has null.

You should avoid putting where conditions on range variables coming from the right side of a left outer join, because doing so effectively turns them into inner join.
Instead, you should either apply the right side filtering before the join:
from a in TableA
where id = 1
join b in TableB.Where(x => a.Active)
on a.table2 equals b.id
into ab
from bdata in ab.DefaultIfEmpty()
join c in TableC.Where(x => x.Active)
on a.table3 equals c.id
into ac
from cdata in ac.DefaultIfEmpty()
...
or include them in the join (when possible):
from a in TableA
where id = 1
join b in TableB
on new { id = a.table2, Active = true } equals new { b.id, b.Active }
into ab
from bdata in ab.DefaultIfEmpty()
join c in TableC
on new { id = a.table3, Active = true } equals new { c.id, c.Active }
into ac
from cdata in ac.DefaultIfEmpty()
...
In order to understand why is that, try to evaluate where bdata.Active == true when bdata is null (i.e. there is no matching record). Actually if this was LINQ to Objects, the above criteria will generate NullReferenceException. But LINQ to Entities can handle that w/o exceptions, since databases naturally support null values in queries for a columns which are normally non nullable. So the above simple evaluates to false, hence is filtering the resulting record and effectively removing the effect of a left outer join which by definition should return the left side record regardless of whether a matching right side record exists.
Which means that actually there is a third way (althought the first two options are preferable) - include an explicit null checks:
from a in TableA
where id = 1
join b in TableB
on a.table2 equals b.id
into ab
from bdata in ab.DefaultIfEmpty()
where bdata == null || bdata.Active
join c in TableC
on a.table3 equals c.id
into ac
from cdata in ac.DefaultIfEmpty()
where cdata == null || cdata.Active
...

Related

How do you use group by and having clause in EF with parent/child relationship?

How can I write a linq to entities query that includes a group by and a having clause?
For example in SQL:
SELECT * FROM dbo.tblParent p
INNER JOIN
(
SELECT a.ID
FROM dbo.tblParent a
join dbo.tblChild c ON a.ID = c.FkParentID
WHERE a.ColValue = 167
GROUP BY A.ID
HAVING COUNT(c.ID) = 1
) t ON p.ID = t.ID
I found my own answer.
// this is far from pretty but it works as far as I can tell
Apps = (from x in context.tblParents
join t in (
from p in context.tblParents
join c in context.tblChilds
on p.ID equals c.FkParentID
where p.ColValue == 167
group c.ID by p.ID into grouped
where grouped.Count() == 1
select new { grouped.Key }) on x.ID equals t.Key
select x);

Replace subquery with appropriate join

how can i remove subquery with a join?
SELECT distinct t."groupId" FROM "contacts" c
INNER JOIN
(
SELECT DISTINCT td.* FROM "groups" g
INNER JOIN
"territory" td
ON
td."groupId" = g.id
WHERE g."orgId" = 3
)
t
ON
ST_Intersects(t.points, c."geoPoint")
WHERE c.id = 33 and c."orgId" = 3
There is nothing wrong with a subquery, but you should get rid of the dreaded DISTINCT:
SELECT td."groupId"
FROM territory AS td
WHERE EXISTS (SELECT 1 FROM contacts AS c
WHERE ST_Intersects(td.points, c."geoPoint")
AND c.id = 33
AND c."orgId" = 3)
AND EXISTS (SELECT 1 FROM groups AS g
WHERE td."groupId" = g.id
AND g."orgId" = 3);
If you insist in having no subqueries, use
SELECT DISTINCT t."groupId"
FROM contacts c
INNER JOIN territory td
ON ST_Intersects(td.points, c."geoPoint")
INNER JOIN groups g
ON td."groupId" = g.id
WHERE g."orgId" = 3
AND c.id = 33
AND c."orgId" = 3;
If you need to make sure that the st_intersects function is only called for rows from territory that match the join with groups, you will have to use a subquery. There is no other way to force a join order.

Making multiple optional joins on same table using json array in postgres

I have two tables users and rooms. I'm trying to join both tables and get list of user names related to the room. I'm getting results only if both columns have values. If any one of the column is null, it's not giving the value of non-null column. Please find the details below.
users schema:
id name
---------
1 X
3 Y
4 Z
rooms schema:
id active_users inactive_users
-----------------------------------
101 [1] [3]
102 [3] null
103 null [4]
Tried query:
SELECT
r.id, json_agg(u.name) as active_users, json_agg(u1.name) as inactive_users
FROM rooms r,
json_array_elements(active_users) as elems
LEFT OUTER JOIN users u
ON u.id = elems::TEXT::INT,
json_array_elements(inactive_users) as elems1
LEFT OUTER JOIN users u1 ON
u1.id = elems1::TEXT::INT
GROUP BY r.id
Query output:
id active_users inactive_users
----------------------------------
101 ["X"] ["Y"]
Expected output:
id active_users inactive_users
----------------------------------
101 ["X"] ["Y"]
102 ["Y"] NULL
103 NULL ["Z"]
Your main issue here is that you're doing an inner join with the json_array_elements(active_users) function, which produces null when active_users is null. Therefore, that join is excluding those lines you want to include. It will work if you make those joins into outer joins as well:
SELECT
r.id,
json_agg(u.name) FILTER (WHERE u.name IS NOT NULL) as active_users,
json_agg(u1.name) FILTER (WHERE u1.name IS NOT NULL) as inactive_users
FROM rooms r
LEFT JOIN json_array_elements(active_users) as elems
ON TRUE
LEFT JOIN json_array_elements(inactive_users) as elems1
ON TRUE
LEFT JOIN users u
ON u.id = elems::TEXT::INT
LEFT JOIN users u1 ON
u1.id = elems1::TEXT::INT
GROUP BY r.id;
You'll notice that I also added FILTERs in the select, that's so null usernames are excluded from the aggregation.

Handle Null in jsonb_array_elements

I have 2 tables a and b
Table a
id | name | code
VARCHAR VARCHAR jsonb
1 xyz [14, 15, 16 ]
2 abc [null]
3 def [null]
Table b
id | name | code
1 xyz [16, 15, 14 ]
2 abc [null]
I want to figure out where the code does not match for same id and name. I sort code column in b b/c i know it same but sorted differently
SELECT a.id,
a.name,
a.code,
c.id,
c.name,
c.code
FROM a
FULL OUTER JOIN ( SELECT id,
name,
jsonb_agg(code ORDER BY code) AS code
FROM (
SELECT id,
name,
jsonb_array_elements(code) AS code
FROM b
GROUP BY id,
name,
jsonb_array_elements(code)
) t
GROUP BY id,
name
) c
ON a.id = c.id
AND a.name = c.name
AND COALESCE (a.code, '[]'::jsonb) = COALESCE (c.code, '[]'::jsonb)
WHERE (a.id IS NULL OR c.id IS NULL)
My answer in this case should only return id = 3 b/c its not in b table but my query is returning id = 2 as well b/c i am not handling the null case well enough in the inner subquery
How can i handle the null use case in the inner subquery?
demo:db<>fiddle
The <# operator checks if all elements of the left array occur in the right one. The #> does other way round. So using both you can ensure that both arrays contain the same elements:
a.code #> b.code AND a.code <# b.code
Nevertheless it will be accept as well if one array contains duplicates. So [42,42] will be the same as [42]. If you want to avoid this as well you should check the array length as well
AND jsonb_array_length(a.code) = jsonb_array_length(b.code)
Furthermore you might check if both values are NULL. This case has to be checked separately:
a.code IS NULL and b.code IS NULL
A little bit shorter form is using the COALESCE function:
COALESCE(a.code, b.code) IS NULL
So the whole query could look like this:
SELECT
*
FROM a
FULL OUTER JOIN b
ON a.id = b.id AND a.name = b.name
AND (
COALESCE(a.code, b.code) IS NULL -- both null
OR (a.code #> b.code AND a.code <# b.code
AND jsonb_array_length(a.code) = jsonb_array_length(b.code) -- avoid accepting duplicates
)
)
After that you are able to filter the NULL values in the WHERE clause

GROUP and SUM in Entity Framework

I want to select sum of all (paid) prices of an order item for each customer.
Here is SQL command:
SELECT c.name,SUM(oi.price * oi.count) from customer c
JOIN order o ON c.id=o.customer_id
JOIN order_item oi ON o.id=oi.order_id
JOIN bill b ON b.id=oi.bill_id
WHERE b.payment_id is NOT null
GROUP by c.name;
I don't know how to do this in EF.
Example result:
John Smith 1500,2
Allan Babel 202,0
Tina Crown 3500,78
(comma is used as decimal point..because price is decimal value)
Your example result doesn't seem to match your SQL command, but i think you are looking for something like this:
var query = from c in context.Customers
join o in context.Orders on c.id equals o.customer_id
join oi in context.OrderItems on o.id equals oi.order_id
join b in context.bill on oi.bill_id equals b.id
where b.payment_id != null
group oi by c.name into g
select new
{
Name = g.Key,
Sum = g.Sum(oi => oi.price * oi.count),
}