LINQ inner join condition - c#

Suppose I have the following tables:
**Members**
Code Name
001 Sue
002 Peter
003 John
**Sales Info**
MemCode Date Type (A/B) Values
001 17/12/2013 A 100
001 17/11/2013 B 100
002 16/12/2013 A 100
I want to have the following result table
**Member Sales in 2013**
MemCode Jan(A) Jan(B) Feb(A) ... Nov(B) Dec(A) Dec(B)
001 0 0 0 100 100 0
002 0 0 0 0 100 0
I tried to exact some data (The Nov(A) and Nov(B)) first using the query,
var query = from tb in Members
join tb2 in SalesInfo on tb.MemCode equals tb2.MemCode
join tb3 in SalesInfo on tb.MemCode equals tb3.MemCode
where tb2.Type.Equals("A") &&
tb2.Date.Month.Equals(11)
tb3.Type.Equals("B") &&
tb3.Date.Month.Equals(11)
Select ...
However it returns no data as no A record found in November so the whole row is filtered. Is there any suggestion to solve the problem?

The problem is you are asking for (Type==A && Type==B) and it is impossible. You can select A first, and get B value of same date in a subquery.

Indeed, if you want type A and type B, you should select on type == A OR type == B. No row will ever satisfy both conditions :)
Something like this is simpler and probably more effective
var query = from tb in Members
join tb2 in SalesInfo on tb.MemCode equals tb2.MemCode
where (tb2.Type.Equals("A") ||
tb2.Type.Equals("B")) &&
tb2.Date.Month.Equals(11)
Select ...

Related

Dynamic Left join 2 datatable

I have 2 C# datatable:
Datatable A:
id
Name
Age
Height
01
Pauls
22
170
02
Sam
20
175
03
Mike
20
175
04
Jame
23
180
Datatable B:
id
Height
Age
01
175
23
02
190
21
The question here is how could I get this output by join 2 table A & B by id and get output datatable using Linq in C# OutputTable:
id
Name
Age
Height
01
Pauls
23(value in table B)
175 (value in table B)
02
Sam
21(value in table B)
190 (value in table B)
03
Mike
20
175
04
Jame
23
180
My code here:
var results = from ta in tableA.AsEnumerable()
join tb in tableB.AsEnumerable on ta["id"] equals tb["id"]
select new
{
ta["id"],
ta["Name"],
tb["Age"],
tb["Height"]
};
My output so far (not what I expected):
id
Name
Age
Height
01
Pauls
23(value in table B)
175 (value in table B)
02
Sam
21(value in table B)
190 (value in table B)
P/s: above table is just an example data, true data is bigger
INNER JOIN works as filter if record not found on the right side. You have to use LEFT JOIN here, which is implemented in LINQ as GroupJoin with SelectMany:
var results = from ta in tableA
join tb in tableB on ta.id equals tb.id into g
from tb in g.DefaultIfEmpty()
select new
{
ta.id,
ta.Name,
Age = tb != null ? tb.Age : ta.Age,
Height = tb != null ? tb.Height : ta.Height,
};
Could you try something like this:
var results = from ta in tableA
join tb in tableB on ta.id equals tb.id into gj
from subTableA in gj.DefaultIfEmpty()
select new
{
id = ta.id,
Name = ta.Name,
Age = (subTableA == null ? ta.Age : tb.Age + "(value in table B)"),
Height = (subTableA == null ? ta.Height : tb.Height + "(value in table B)")
};
Here is some page about left join using Entity Framework - https://learn.microsoft.com/en-us/dotnet/csharp/linq/perform-left-outer-joins
Idea is to introduce row 'from subTableA in gj.DefaultIfEmpty()' in order to get all rows from the join, even those which don't have matching value in second table.
If I understand the question correctly, you want to get back a DataTable from your LINQ query. However, LINQ returns an IEnumerable<T> or an IQueryable<T> if you're querying a database.
If you want a DataTable, you'll need some additional code. I suggest you refer to the top answer on this StackOverflow question, which provides a solution for converting an IEnumerable to a DataTable.

Compare two datatables and draw those that differ into new DataTable efficiently

In essence I have two datatables
DataTable 1
PirateShipID PirateShipPreference
123 1
122 2
121 3
And DataTable 2 (which has different named columns, but the data types are the same.
RGPirateShipID PirateShipPreferenceType
123 1
122 1
121 3
I want to grab all records where
PirateShipID == RGPirateShipID && PirateShipePreference != PirateShipPreferenceType
Ideally using Linq as I believe that would be my quickest way of accomplishing this
var idsNotinPirates = from r in DataTable1.AsEnumerable()
//Get all records that don't match on preference
where DataTable2.AsEnumerable().Any(r2 => r["PirateShiptID"] == r2["RGPirateShipID"] && r["PirateShipPreference"] != r2["PirateShipPreferenceType"])
select r;
However, DataTable 1 has about 10k pirateships and Datatable2 has 1 million.
It takes the application a long time to complete the above.
How can i make this more efficient?
I believe you should probably be doing something like this:
var query = from r in DataTable1.AsEnumerable()
join r2 in DataTable2.AsEnumerable() on r["PirateShipID"] equals r2["RGPirateShipID"] into joinedTable
where joinedTable["PirateShipPreference"] != joinedTable["PirateShipPreferenceType"]
select r

Problems in Right join in SQl

I have the following table structure also i have mention my expected output
please help me with query as i dont know much about sql query
Table Structure
Table 1 : Emp Details
FName Id
Pratik 1
Praveen 3
Nilesh 2
Table 1 : JoinigDocument
id DocumentName
1 Leaving
2 Exp letter
3 birth cert
Table 2 : EmployeeJoiningDocument
EmpId JoiningDocumentId
1 1
1 2
3 1
3 2
3 3
2 1
2 3
Expected Output :
FName Id JoiningDocumentId DocumentName
Pratik 1 1 Leaving
Pratik 1 2 Exp letter
Pratik 1 null birth cert
Praveen 3 1 Leaving
Praveen 3 2 Exp letter
Praveen 3 3 birth cert
Nilesh 2 1 Leaving
Nilesh 2 null Exp letter
Nilesh 2 3 birth cert
You can write a query as:
select
A.FName,
A.Id,
B.JoiningDocumentId,
c.DocumentName
from #JoinigDocument C
cross join #EmployeeDetail A
Left join #EmployeeJoiningDocument B on B.EmployeeId = A.id and
B.JoiningDocumentId = C.id
order by A.Id
First cross join JoinigDocument and EmployeeDetail table so that you get all possible combinations of Employee and Documents irrespective of the fact that employee has that Joining Document or not. Then you need to do a left join to retain all these matches and find data corresponding to valid entries in EmployeeJoiningDocument.
Demo

FOREACH Recursive SQL Statement

I have what would seem to be a simple problem but the solutions I have tried to date have left me wanting in the execution areas. Speeds seem fine on small (<10000) data sets but quickly take more and more time as the counts go up.
In SQL Server 2008 R2 I have a table with four columns in it: Id, ParentId, ControlNum, ParentControlNum.
The Id and ParentId information is filled in. An Id always has a value, the ParentId is null if the row has no parent, otherwise its the the value of an Id within the table that represents the parent row.
The issue is that the Id and ParentIds are all over the place. All of the Ids are added to the table then they are processed to add the children. This is a set part of the problem and is not something that can be changed.
What I need to be able to do is to generate the ControlNum values in order obeying the Parent Child relationship. My current logic uses a bit of C# and SQL SELECT/UPDATE commands to accomplish this, but as mentioned performance is a great concern.
In Pseudo-Code
Select all Id's where the parent Id is null (All root entries)
Foreach (Id)
GenerateControlNum(Id, CurrentCounterValue, CurrentCounterValue)
GenerateControlNum(Id, CurrentCounterValue, ParentCounterValue)
Set Id's ControlNum to CurrentCounterValue
Set Id's ParentControlNum to CurrentCounterValue
Increment CurrentCounterValue
Select All Id's where ParentId == Id (All my direct children)
Foreach (ChildId)
GenerateControlNum(ChildId, CurrentCounterValue, Id's ControlNum);
Baseline is trying to make this execute faster, idealy completely in SQL would be preffered. I am trying along the lines of a CTE populated with the RootIds and then going through them with a MERGE statement but I can just not seem to get the counter value to work properly for setting the ControlNum values.
Is this even possible in SQL or is this too much of a procedural type of processing?
Example Table Data from how it currently runs: BEFORE
ID ParentId ControlNum ParentControlNum
8C821027-A6F9-E011-AB48-B499BAE13A62 756F981E-A6F9-E011-AB48-B499BAE13A62 0 NULL
D7DB6033-A6F9-E011-AB48-B499BAE13A62 756F981E-A6F9-E011-AB48-B499BAE13A62 0 NULL
D2E36033-A6F9-E011-AB48-B499BAE13A62 C9E36033-A6F9-E011-AB48-B499BAE13A62 0 NULL
8FE66033-A6F9-E011-AB48-B499BAE13A62 58E66033-A6F9-E011-AB48-B499BAE13A62 0 NULL
37EC6033-A6F9-E011-AB48-B499BAE13A62 2FEC6033-A6F9-E011-AB48-B499BAE13A62 0 NULL
41EC6033-A6F9-E011-AB48-B499BAE13A62 2FEC6033-A6F9-E011-AB48-B499BAE13A62 0 NULL
DDED6033-A6F9-E011-AB48-B499BAE13A62 BCED6033-A6F9-E011-AB48-B499BAE13A62 0 NULL
DC69981E-A6F9-E011-AB48-B499BAE13A62 NULL 0 NULL
166A981E-A6F9-E011-AB48-B499BAE13A62 NULL 0 NULL
4D6A981E-A6F9-E011-AB48-B499BAE13A62 NULL 0 NULL
856A981E-A6F9-E011-AB48-B499BAE13A62 NULL 0 NULL
F56A981E-A6F9-E011-AB48-B499BAE13A62 NULL 0 NULL
2E6B981E-A6F9-E011-AB48-B499BAE13A62 NULL 0 NULL
666B981E-A6F9-E011-AB48-B499BAE13A62 NULL 0 NULL
9D6B981E-A6F9-E011-AB48-B499BAE13A62 NULL 0 NULL
AFTER
ID ParentId ControlNum ParentControlNum
8C821027-A6F9-E011-AB48-B499BAE13A62 756F981E-A6F9-E011-AB48-B499BAE13A62 22 21
D7DB6033-A6F9-E011-AB48-B499BAE13A62 756F981E-A6F9-E011-AB48-B499BAE13A62 24 21
D2E36033-A6F9-E011-AB48-B499BAE13A62 C9E36033-A6F9-E011-AB48-B499BAE13A62 58 57
8FE66033-A6F9-E011-AB48-B499BAE13A62 58E66033-A6F9-E011-AB48-B499BAE13A62 69 68
37EC6033-A6F9-E011-AB48-B499BAE13A62 2FEC6033-A6F9-E011-AB48-B499BAE13A62 86 85
41EC6033-A6F9-E011-AB48-B499BAE13A62 2FEC6033-A6F9-E011-AB48-B499BAE13A62 88 85
DDED6033-A6F9-E011-AB48-B499BAE13A62 BCED6033-A6F9-E011-AB48-B499BAE13A62 95 94
DC69981E-A6F9-E011-AB48-B499BAE13A62 NULL 0 0
166A981E-A6F9-E011-AB48-B499BAE13A62 NULL 1 1
4D6A981E-A6F9-E011-AB48-B499BAE13A62 NULL 2 2
856A981E-A6F9-E011-AB48-B499BAE13A62 NULL 3 3
F56A981E-A6F9-E011-AB48-B499BAE13A62 NULL 4 4
2E6B981E-A6F9-E011-AB48-B499BAE13A62 NULL 5 5
666B981E-A6F9-E011-AB48-B499BAE13A62 NULL 6 6
9D6B981E-A6F9-E011-AB48-B499BAE13A62 NULL 7 7
The data set I have is 104 entries right now so this is just the first 15. The objects with out parents are at the bottom, those are examples of root entries and have their control number and parent control number set to the same value. At the top of the table we see a few objects that have the same parents and so have matching parent control numbers and fairly close control numbers themselves (there must be row between ControlNum 22 and 24 also from parent 21 for example. same for the 86 to 88 jump, they are just not in the table next to each other).
Hope this makes it more clear.
EDIT: More clarity based on the answer given by Mikael
Below are ControlNum values displayed in the hierarchy based on their Id and ParentId information. Normally these would be listed 1, 2, 3 ... 8 but its easier not to clutter up the display with (child of) messages all over.
1
4
7
8
5
2
6
3
What I need is
1
2
3
4
5
6
7
8
This is why recursion has been the way that I have been going is I need to assign a ControlNum to the root object, and then the next object needs to be its first child followed by that objects children and so on before going on to the next root object.
I guess what I am saying is this is a breadth first and what I am in need of is a depth first.
Not sure I get all of your requirement but here is a start. Tell me if it does what you want or if the numbers are not generated correctly.
;with C as
(
select ID,
ParentID,
ControlNum,
ParentControlNum,
row_number() over(order by ParentID, ID) - 1 as rn
from YourTable
)
update C1
set ControlNum = C1.rn,
ParentControlNum = case when C1.ParentID is null
then C1.rn
else C2.rn
end
from C as C1
left outer join C as C2
on C1.ParentID = C2.ID
Run it on SE-Data with slightly modified input: https://data.stackexchange.com/stackoverflow/q/115625/
Version 2
First a recursive CTE R,that builds a string to be used as order by when generating values for ControlNum. After that it is pretty much the same as above.
;with R as
(
select ID,
ParentID,
cast(ID as varchar(max)) as Sort
from YourTable
where ParentID is null
union all
select T.ID,
T.ParentID,
R.Sort+cast(T.ID as varchar(max))
from YourTable as T
inner join R
on R.ID = T.ParentID
),
C as
(
select ID,
ParentID,
row_number() over(order by Sort) - 1 as rn
from R
)
update T
set ControlNum = C1.rn,
ParentControlNum = case when C1.ParentID is null
then C1.rn
else C2.rn
end
from YourTable as T
inner join C as C1
on T.ID = C1.ID
left outer join C as C2
on T.ParentID = C2.ID
Test here: https://data.stackexchange.com/stackoverflow/q/115626/
Note: I guess this is a one time thing you will do with some data because you will have a hard time adding new nodes and at the same time uphold the numbering like this. If you for instance add a new child node to the first node you will have to assign all ControlNum += 1 for all nodes "below" and reassign all ParentControlNum.

How to add a Restriction to an inner join?

I have the following NHibernate DetatchedCriteria,
return DetachedCriteria.For<MMFund>()
.CreateCriteria<MMFund>(x => x.DataUniverse)
.Add<DataUniverse>(x => x.SiteId == 100)
.SetProjection(LambdaProjection.Property<MMFund>(x => x.FundId));
which is producing the following SQL:
and
this_.ShareClassReturn_ShareClassId in
(
SELECT f.[Fund_ID] as y0_
FROM
dbo.Fund f inner join CAP.DataUniverse du
on f.[Fund_TypeID] = du.[DataUniverse_TypeId]
and f.[Fund_CountryID] = du.[DataUniverse_CountryID]
WHERE fu.[DataUniverse_SiteId] = 100
)
There are many funds in a DataUniverse.
I need to filter this so that I can select only the funds with a country ID of 'ET', so that my query looks as follows:
and
scr.ShareClassReturn_ShareClassId in
(
/* Get funds in universe */
SELECT f.[Fund_ID] as y0_
FROM dbo.Fund f inner join CAP.DataUniverse du
on f.[Fund_TypeID] = du.[DataUniverse_TypeId]
and f.[Fund_CountryID] = 'ET' // these are the guys I need
WHERE du.[DataUniverse_SiteId] = 100
)
However, I'm not sure what I need to do to the DetachedCriteria in order to make this happen. The problem I'm having is that no matter what I do, it's putting the clause in the wrong place, such as
WHERE du.[DataUniverse_SiteId] = 100 and f.Fund_CountryId = 'ET'
when I add the line .Add(Restrictions.Eq("CountryId", "ET")) as follows
return DetachedCriteria.For<MMFund>()
.Add(Restrictions.Eq("CountryId", "ET"))
.CreateCriteria<MMFund>(x => x.DataUniverse)
.Add<DataUniverse>(x => x.SiteId == 100)
.SetProjection(LambdaProjection.Property<MMFund>(x => x.FundId));
or it attempts to filter on the wrong table entirely when I specify that the Restriction should be part of the second .CreateCriteria, such as
return DetachedCriteria.For<MMFund>()
.CreateCriteria<MMFund>(x => x.DataUniverse)
.Add(Restrictions.Eq("CountryId", "ET"))
.Add<DataUniverse>(x => x.SiteId == 100)
.SetProjection(LambdaProjection.Property<MMFund>(x => x.FundId));
which produces this;
WHERE du.[DataUniverse_SiteId] = 100 and du.[DataUniverse_CountryID] = 'ET'
** note - as I'm using the Criteria API, this is actually the Restriction that I'm using:
.Add<MMFund>(f => f.CountryId == "ET")
I used the Restriction terminology because it's more explicit to what I'm trying to achieve. The Criteria API & the other way both produce the exact same results.
Why do you think Where is the wrong place for the filter? That's where filtering happens.
The generated SQL looks sound. You have two tables joined on their common fields. The Where clause is providing the appropriate filtering information. If your preferred SQL statement was in place, you'd have data joined on TypeID alone, not the CountryID.
For example, let's say your Fund table looks like this
TypeID CountryID
1 1
1 2
2 1
2 2
3 1
4 1
And your DataUniverse table is the following
TypeID CountryID
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
4 1
4 2
If you wrote SQL like you desire, you would produce a join based on TypeID and you would filter Fund.CountryID = 1, for example. What would your product look like?
F.TypeID F.CountryID D.TypeID D.CountryID
1 1 1 1
1 1 1 2
1 1 1 3
2 1 2 1
2 1 2 2
2 1 2 3
3 1 3 1
3 1 3 2
4 1 4 1
4 1 4 2
Is that your desired output? Yes, you've filtered Fund.CountryID, but your join was just on TypeID, so you've got all records from DataUniverse with that matching type for each Fund.
With the join on the two fields and the Where filtering the CountryID, the result will be the following
F.TypeID F.CountryID D.TypeID D.CountryID
1 1 1 1
2 1 2 1
3 1 3 1
4 1 4 1
The question is which set of data is the one you expect?

Categories

Resources