Related
I've got 2 datatables and am trying to summarize the data in them using a left outer join. The join works fine with this code
Dim Journal = From entries In dt.AsEnumerable()
Join inccodes In dtGL.AsEnumerable()
On entries.Field(Of String)("GLCode") Equals inccodes.Field(Of String)("GLCode")
Group By keys = New With {Key .IncomeCode = entries.Field(Of String)("GLCode"), Key .IncomeDesc = .inccodes.Field(Of String)("GLCodeDesc")}
Into ChargeSum = Group, sm = Sum(entries.Field(Of Decimal)("Amount"))
Where sm <> 0
Select New GL_Journal With {.IncomeCode = keys.IncomeCode, .IncomeDesc = keys.IncomeDesc, .LineAmount = sm}
`
However, since I really want a Left Outer Join I want to use Group Join instead of Join.
As soon as I change the Join to Group Join the code in the Group by at ".inccodes.field(Of String)("GLCodeDesc")" has ".inccodes" highlighted with the error "'inccodes' is not a member of 'anonymous type'"
I've reviewed much documentation on Group By and Group Join but there is scant information on them together.
Any ideas? Would I have more options/success with the method syntax?
If i try to reproduce your query using a left outer join, I will do something like this :
Dim dt As New DataTable
dt.Columns.Add("GLCode", GetType(String))
dt.Columns.Add("Amount", GetType(Decimal))
dt.Rows.Add("111", 3251.21)
dt.Rows.Add("222", 125.79)
dt.Rows.Add("999", 10000)
Dim dtGL As New DataTable
dtGL.Columns.Add("GLCode", GetType(String))
dtGL.Columns.Add("GLCodeDesc", GetType(String))
dtGL.Rows.Add("111", "a")
dtGL.Rows.Add("222", "b")
dtGL.Rows.Add("333", "c")
Dim Journal = From entries In dt.AsEnumerable()
Group Join inccodes In dtGL.AsEnumerable()
On entries.Field(Of String)("GLCode") Equals inccodes.Field(Of String)("GLCode")
Into Group
From lj In Group.DefaultIfEmpty()
Group By keys = New With {Key .IncomeCode = entries.Field(Of String)("GLCode"), Key .IncomeDesc = lj?.Field(Of String)("GLCodeDesc")}
Into ChargeSum = Group, sm = Sum(entries.Field(Of Decimal)("Amount"))
Select New With {.IncomeCode = keys.IncomeCode, .IncomeDesc = keys.IncomeDesc, .LineAmount = sm}
I would like to join 4 table, main table in this example is "MAGAZ101" (records on screen).
Table "TOWARY" keep name of items, unit of measurement(kg, quantity), itc.
Table "MAGAZ01" keep info about item in stock (1 in, 0 sold).
Table "PRZYTOW" keep info about how many same items had been take to stock also real weight of item.
Tables "MAGAZ101", "TOWARY", "MAGAZ01" are join by fields "TOWAR".
My problem is join "MAGAZ01" and "PRZYTOW" by column "DOK_PRZYJ", group and sum weight in "PRZYTOW" and also sum stock in "MAGAZ01", sum if item in table "MAGAZ01" is "1".
Here is my Code:
'''''''''
Dim MAGAZYNGLOWNY = (From tab1 In MAGAZ101.AsEnumerable()
Join tab2 In TOWARY.AsEnumerable() On tab1.Field(Of String)("TOWAR") Equals tab2.Field(Of String)("TOWAR")
Join tab3 In MAGAZ01.AsEnumerable() On tab1.Field(Of String)("TOWAR") Equals tab3.Field(Of String)("TOWAR")
Join tab4 In PRZYTOW.AsEnumerable() On tab3.Field(Of String)("DOK_PRZYJ") Equals tab4.Field(Of String)("DOK_PRZYJ")
Select New With {
.Symbol = tab1.Field(Of String)("TOWAR"),
.Nazwa = tab2.Field(Of String)("NAZWA"),
.Jm = tab2.Field(Of String)("JM"),
.Stan = If(IsDBNull(tab3.Item("STANMAG")), 0, tab3.Field(Of Double)("STANMAG")),
.WAGA = If(IsDBNull(tab4.Item("WAGA")), 0, tab4.Field(Of Double)("WAGA"))
}).ToList
Group and sum:
Dim MAGAZYNGLOWNYSUMA = (From A1 In MAGAZYNGLOWNY
Group A1 By Key = New With {.SYMBOL = A1.Symbol, .NAZWA = A1.Nazwa, .JM = A1.Jm, .STAN = A1.Stan, .Waga = A1.WAGA} Into Group
Select New With {
.Symbol = Key.SYMBOL,
.Nazwa = Key.NAZWA,
.Jm = Key.JM,
.Stan = Group.Sum(Function(A) A.Stan),
.WAGA = Group.Sum(Function(A) A.WAGA)
})
.Symbol - ID of item,
.Nazwa - name of item,
.Jm - unit,
.STAN - how many in stock,
.WAGA - weight,
Thanks in advace Adam
If you need, ask for detail.
Here is correct working code.
1st "MAG_GLOW" joined tables 1 and 2.
2nd "dane" joined tables 3 and 4, by "TOWAR" and also "DOK_PRZYJ" in the result the table is sorted and easy to sum.
3rd "Stock" where stock is greater than 0 (sum "STAN") and where stock is greater than 0 (sum weight "WAGA")
4th "MAGAZYNGLOWNY" joined 1st step "MAG_GLOW" and 3rd step "Stock".
Dim MAG_GLOW = (From tab1 In MAGAZ101.AsEnumerable()
Join tab2 In TOWARY.AsEnumerable() On tab1.Field(Of String)("TOWAR") Equals tab2.Field(Of String)("TOWAR")
Select New With {
.TOWAR = tab1.Field(Of String)("TOWAR"),
.Nazwa = tab2.Field(Of String)("NAZWA"),
.Jm = tab2.Field(Of String)("JM")
}).ToList
Dim dane = (From TAB3 In MAGAZ01.AsEnumerable()
Join TAB4 In PRZYTOW.AsEnumerable() On TAB3.Field(Of String)("towar") Equals TAB4.Field(Of String)("towar") And TAB3.Field(Of String)("Dok_przyj") Equals TAB4.Field(Of String)("Dok_przyj")
Select New With
{
.TOWAR = TAB3.Field(Of String)("TOWAR"),
.STAN = If(IsDBNull(TAB3.Item("STANMAG")), 0, TAB3.Field(Of Double)("STANMAG")),
.WAGA = If(IsDBNull(TAB4.Item("WAGA")), 0, TAB4.Field(Of Double)("WAGA"))
})
Dim stock = (From MAG In dane
Group MAG By TOWAR = MAG.TOWAR Into G = Group
Select New With {
.TOWAR = TOWAR,
.Stan = G.Where(Function(x) x.STAN > 0).Sum(Function(x) x.STAN),
.Waga = G.Where(Function(x) x.STAN > 0).Sum(Function(x) x.WAGA)
}
)
Dim MAGAZYNGLOWNY = (From tab1 In MAG_GLOW
Group Join tab2 In stock On tab2.TOWAR Equals tab1.TOWAR Into co = Group
From tab2 In co.DefaultIfEmpty()
Order By tab1.TOWAR
Select New With
{.TOWAR = tab1.TOWAR,
.NAZWA = tab1.Nazwa,
.Jm = tab1.Jm,
.STAN = If(IsNothing(tab2), Nothing, tab2.Stan),
.WAGA = If(IsNothing(tab2), Nothing, tab2.Waga)
})
I have this vb.net LINQ query. Without the join the count and SUM worked perfectly on each. But with the join the count and SUM arent working properly due to the multiple CAMPAIGNID values. I suspect I need another group by statement in there but I am not entirely sure how to do that. IF you could help that would be great.
Dim query =
From t1 In tbl1
Join t2 In tbl2 On t1.CAMPAIGNID Equals t2.CAMPAIGNID
Group By t1.CAMPAIGNID Into Group
Select New With {
.id = CAMPAIGNID,
.CALLS = Group.Sum(Function(a) a.t2.CALLS),
.count = Group.Count(Function(a) a.t1.TERMCD = "Refused")
}
I have made an example which seems to be working properly.
Public Class T1
Public Property CAMPAIGNID As Integer
Public Property TERMCD As String
End Class
Public Class T2
Public Property CAMPAIGNID As Integer
Public Property CALLS As Integer
End Class
Dim tbl1 As New List(Of T1) From {
New T1 With {.CAMPAIGNID = 1, .TERMCD = "Refused"},
New T1 With {.CAMPAIGNID = 1, .TERMCD = "Accepted"},
New T1 With {.CAMPAIGNID = 2, .TERMCD = "Refused"},
New T1 With {.CAMPAIGNID = 2, .TERMCD = "Accepted"},
New T1 With {.CAMPAIGNID = 3, .TERMCD = "Refused"},
New T1 With {.CAMPAIGNID = 3, .TERMCD = "Accepted"}
}
Dim tbl2 As New List(Of T2) From {
New T2 With {.CAMPAIGNID = 1, .CALLS = 2},
New T2 With {.CAMPAIGNID = 1, .CALLS = 4},
New T2 With {.CAMPAIGNID = 2, .CALLS = 8},
New T2 With {.CAMPAIGNID = 2, .CALLS = 16},
New T2 With {.CAMPAIGNID = 3, .CALLS = 32},
New T2 With {.CAMPAIGNID = 3, .CALLS = 64}
}
Dim query =
From t1 In tbl1
Join t2 In tbl2 On t1.CAMPAIGNID Equals t2.CAMPAIGNID
Group By t1.CAMPAIGNID Into Group
Select New With {
.id = CAMPAIGNID,
.CALLS = Group.Sum(Function(a) a.t2.CALLS),
.count = Group.Count(Function(a) a.t1.TERMCD = "Refused")
}
For Each q In query
Console.WriteLine($"ID:{q.id}, CALLS:{q.CALLS}, COUNT:{q.count}")
Next
ID:1, CALLS:12, COUNT:2
ID:2, CALLS:48, COUNT:2
ID:3, CALLS:192, COUNT:2
In the case of ID 3, the CALLS sum of 192 is correct because the sum has no constraint of TERMCD = "Refused", so it counts all. And the count of 2 is accurate because has a constraint where TERMCD = "Refused".
Just looking at the group, without aggregation, let's see what is returned
Dim query =
From t1 In tbl1
Join t2 In tbl2 On t1.CAMPAIGNID Equals t2.CAMPAIGNID
Group By t1.CAMPAIGNID Into Group
For Each q In query
Console.WriteLine($"ID:{q.CAMPAIGNID}")
For Each g In q.Group
Console.WriteLine($"t1.CAMPAIGNID:{g.t1.CAMPAIGNID}, t1.TERMCD:{g.t1.TERMCD}, t2.CAMPAIGNID:{g.t2.CAMPAIGNID}, t2.CALLS:{g.t2.CALLS}")
Next
Next
ID:1
t1.CAMPAIGNID:1, t1.TERMCD:Refused, t2.CAMPAIGNID:1, t2.CALLS:2
t1.CAMPAIGNID:1, t1.TERMCD:Refused, t2.CAMPAIGNID:1, t2.CALLS:4
t1.CAMPAIGNID:1, t1.TERMCD:Accepted, t2.CAMPAIGNID:1, t2.CALLS:2
t1.CAMPAIGNID:1, t1.TERMCD:Accepted, t2.CAMPAIGNID:1, t2.CALLS:4
ID:2
t1.CAMPAIGNID:2, t1.TERMCD:Refused, t2.CAMPAIGNID:2, t2.CALLS:8
t1.CAMPAIGNID:2, t1.TERMCD:Refused, t2.CAMPAIGNID:2, t2.CALLS:16
t1.CAMPAIGNID:2, t1.TERMCD:Accepted, t2.CAMPAIGNID:2, t2.CALLS:8
t1.CAMPAIGNID:2, t1.TERMCD:Accepted, t2.CAMPAIGNID:2, t2.CALLS:16
ID:3
t1.CAMPAIGNID:3, t1.TERMCD:Refused, t2.CAMPAIGNID:3, t2.CALLS:32
t1.CAMPAIGNID:3, t1.TERMCD:Refused, t2.CAMPAIGNID:3, t2.CALLS:64
t1.CAMPAIGNID:3, t1.TERMCD:Accepted, t2.CAMPAIGNID:3, t2.CALLS:32
t1.CAMPAIGNID:3, t1.TERMCD:Accepted, t2.CAMPAIGNID:3, t2.CALLS:64
Now it should be clear why you see what you see.
Sum with no constraint:
t1.CAMPAIGNID:3, t1.TERMCD:Refused, t2.CAMPAIGNID:3, t2.CALLS:32
t1.CAMPAIGNID:3, t1.TERMCD:Refused, t2.CAMPAIGNID:3, t2.CALLS:64
t1.CAMPAIGNID:3, t1.TERMCD:Accepted, t2.CAMPAIGNID:3, t2.CALLS:32
t1.CAMPAIGNID:3, t1.TERMCD:Accepted, t2.CAMPAIGNID:3, t2.CALLS:64
Count where a.t1.TERMCD = "Refused"
t1.CAMPAIGNID:3, t1.TERMCD:Refused, t2.CAMPAIGNID:3, t2.CALLS:32
t1.CAMPAIGNID:3, t1.TERMCD:Refused, t2.CAMPAIGNID:3, t2.CALLS:64
I'm not sure what you want to see, but this should at least show you that your output is accurate.
Now that we got that out of the way, your SQL query is below
SELECT DISTINCT A.CAMPAIGNID
, RFJOIN.RF
, CCJOIN.CC
, CALLSJOIN.CALLS
, CALLSJOIN.CALLS / RFJOIN.RF as Calculated
FROM tbl1 A
inner join (SELECT CAMPAIGNID
, count() as RF
FROM tbl1
WHERE TERMCD='Refused'
GROUP by CAMPAIGNID) RFJOIN on RFJOIN.CAMPAIGNID = a.CAMPAIGNID
inner join (SELECT CAMPAIGNID
, count() as CC
FROM tbl1
WHERE TERMCD LIKE '%Con%'
GROUP by CAMPAIGNID) CCJOIN on CCJOIN.CAMPAIGNID = a.CAMPAIGNID
inner join (SELECT CAMPAIGNID
, SUM(CALLS) as CALLS
FROM tbl2
GROUP by CAMPAIGNID) CALLSJOIN on CALLSJOIN.CAMPAIGNID = a.CAMPAIGNID
I would break it into the subqueries then join them together. You can almost copy the SQL syntax this way
Dim RFJ = From t1 In tbl1
Where t1.TERMCD = "Refused"
Group By t1.CAMPAIGNID Into Group
Select New With {CAMPAIGNID, .RF = Group.Count()}
Dim CCJ = From t1 In tbl1
Where t1.TERMCD.Contains("Con")
Group By t1.CAMPAIGNID Into Group
Select New With {CAMPAIGNID, .CC = Group.Count()}
Dim CAJ = From t2 In tbl2
Group By t2.CAMPAIGNID Into Group
Select New With {CAMPAIGNID, .CALLS = Group.Count()}
Dim query = From A In tbl1
Join RFJOIN In RFJ On RFJOIN.CAMPAIGNID Equals A.CAMPAIGNID
Join CCJOIN In CCJ On CCJOIN.CAMPAIGNID Equals A.CAMPAIGNID
Join CALLSJOIN In CAJ On CALLSJOIN.CAMPAIGNID Equals A.CAMPAIGNID
Select New With {A.CAMPAIGNID, RFJOIN.RF, CCJOIN.CC, CALLSJOIN.CALLS, .Calculated = CALLSJOIN.CALLS / RFJOIN.RF}
Dim queryDistinct = query.Distinct()
' or in one big ugly query :)
Dim fullQuery = (From A In tbl1
Join RFJOIN In From t1 In tbl1
Where t1.TERMCD = "Refused"
Group By t1.CAMPAIGNID Into Group
Select New With {CAMPAIGNID, .RF = Group.Count()} On RFJOIN.CAMPAIGNID Equals A.CAMPAIGNID
Join CCJOIN In From t1 In tbl1
Where t1.TERMCD.Contains("Con")
Group By t1.CAMPAIGNID Into Group
Select New With {CAMPAIGNID, .CC = Group.Count()} On CCJOIN.CAMPAIGNID Equals A.CAMPAIGNID
Join CALLSJOIN In From t2 In tbl2
Group By t2.CAMPAIGNID Into Group
Select New With {CAMPAIGNID, .CALLS = Group.Count()} On CALLSJOIN.CAMPAIGNID Equals A.CAMPAIGNID
Select New With {A.CAMPAIGNID, RFJOIN.RF, CCJOIN.CC, CALLSJOIN.CALLS, .Calculated = CALLSJOIN.CALLS / RFJOIN.RF}).Distinct()
It truly is a case of me making it WAY more complicated then it was and not knowing enough to ask the right questions. I wanted to mimic the subquery concept in TSQL but I didn't explain it well. What threw me was the LINQ syntax which seems to always be my downfall with LINQ. I know what I wanted to do I just didn't know how to do it the LINQ way. Thanks to you all who have helped especially #djv.
Dim query =
From t1 In tbl1 Group t1 By t1.CAMPAIGNID Into grp = Group
Join t2 In (From p In tbl2 Group p By p.CAMPAIGNID Into g = Group
Select New With {
.CAMPAIGNID = CAMPAIGNID,
.CALLS = CType(g.Sum(Function(d) d.CALLS), Integer?).GetValueOrDefault(0),
})
On CAMPAIGNID Equals t2.CAMPAIGNID
Select New With {
.CampaignID = CAMPAIGNID,
.RF = CType(grp.Count(Function(b) b.TERMCD = "Refused"), Integer?).GetValueOrDefault(0),
.CALLS = t2.CALLS
}
Dim ID_Section as Int32 = 10
Dim Query = From Book1 In db.Book1
Group Join Section In db.Section On CInt(Book1.ID_Section) Equals Section.ID_section _
And Section.ID_section Equals (ID_Section) Into Section_join = Group
From Section In Section_join.DefaultIfEmpty()
Select
Book1.ID_Book,
Book1.Name_Book,
ID_section = Section.ID_section,
Name_Section = Section.Name_Section
The error appears in the variable id_Section, since the Linq does not accept values from the outside, as it seems to me of course.
Here Error :
And Section.ID_section Equals (ID_Section)
In SQL Query Use At :
Declare #ID_Section int
SELECT Book.ID_Book, Book.Name_Book, Section.ID_section, Section.Name_Section
FROM Book LEFT OUTER JOIN
Section ON Book.ID_Section = Section.ID_section and Section.ID_section = #ID_Section
where Book.ID_Book =1
Using Where on db.Section with lambda syntax:
Dim Query = From Book1 In db.Book1
Group Join Section In db.Section.Where(Function(s) s.ID_section = ID_Section)
On CInt(Book1.ID_Section) Equals Section.ID_section _
Into Section_join = Group
From Section In Section_join.DefaultIfEmpty()
Select
Book1.ID_Book,
Book1.Name_Book,
ID_section = Section.ID_section,
Name_Section = Section.Name_Section
Alternatively you can apply the Where to the Join results:
Dim Query = From Book1 In db.Book1
Group Join Section In db.Section
On CInt(Book1.ID_Section) Equals Section.ID_section _
Into Section_join = Group
From Section In Section_join.Where(Function(s) s.ID_section = ID_Section).DefaultIfEmpty()
Select
Book1.ID_Book,
Book1.Name_Book,
ID_section = Section.ID_section,
Name_Section = Section.Name_Section
I'm having a heckuva time figuring out how to translate a simple SQL LEFT OUTER JOIN with a two condition where clause into a working Linq-to-Entities query. There are only two tables. I need values for all rows from Table1, regardless of matches in Table2, but the WHERE clause uses fields from Table2. In SQL, the two parameters would be Table2WhereColumn1 and Table2WhereColumn2, and the query (which works) looks like this:
SELECT t1.Table1Id,
t1.FieldDescription,
t2.FieldValue
FROM Table1 t1 WITH (NOLOCK)
LEFT JOIN Table2 t2 WITH (NOLOCK) ON t1.Table1Id = t2.Table1Id
WHERE (t2.Table2WhereColumn1 = #someId OR t2.Table2WhereColumn1 IS NULL)
AND (t2.Table2WhereColumn2 = #someOtherId OR t2.Table2WhereColumn2 IS NULL)
ORDER BY t1.OrderByColumn
I've tried using Group Join with DefaultIfEmpty(), as well as an implicit join (without the actual Join keyword), and I only get rows for items that have values in Table2. I'm sure this won't help, but here's an example of the Linq I've been trying that doesn't work:
Public Shared Function GetProfilePreferencesForCedent(ByVal dc As EntityContext, _
ByVal where1 As Int32, _
ByVal where2 As Int32) _
As IQueryable(Of ProjectedEntity)
Return From t1 In dc.Table1
Group Join t2 In dc.Table2 _
On t1.Table1Id Equals t2.Table1Id _
Into t2g1 = Group _
From t2gx In t2g1.DefaultIfEmpty(Nothing)
Where (t2gx.Table2Where1 = where1 Or t2gx.Table2Where1 = Nothing) _
And (t2gx.Table2Where2 = where2 Or t2gx.Table2Where2 = Nothing)
Order By t1.SortOrder
Select New ProjectedEntity With {
.Table1Id = t1.Table1Id, _
.FieldDescription = t1.FieldDescription, _
.FieldValue = If(t2gx Is Nothing, String.Empty, t2gx.FieldValue) _
}
End Function
Have a go at these queries and tell me if they work for you. I haven't set up the data to test, but they should be fine.
Please excuse my mix of C# & VB.NET. I used to be a VB.NET developer, but in the last couple of years I've mostly worked in C#, so I now feel more comfortable there.
Here are the classes I created for Table1 & Table2:
public class Table1
{
public int Table1Id { get; set; }
public string FieldDescription { get; set; }
public int OrderByColumn { get; set; }
}
public class Table2
{
public int Table1Id { get; set; }
public string FieldValue { get; set; }
public int Table2WhereColumn1 { get; set; }
public int Table2WhereColumn2 { get; set; }
}
Now the query in C# should be:
var query =
from t1 in Table1
join t2 in Table2 on t1.Table1Id equals t2.Table1Id into _Table2
from _t2 in _Table2.DefaultIfEmpty()
where _t2 == null ? true :
_t2.Table2WhereColumn1 == #someId
&& _t2.Table2WhereColumn2 == #someOtherId
orderby t1.OrderByColumn
select new
{
t1.Table1Id,
t1.FieldDescription,
FieldValue = _t2 == null ? "" : _t2.FieldValue,
};
And the translation into VB.NET:
Dim query = _
From t1 In Table1 _
Group Join t2 In Table2 On t1.Table1Id Equals t2.Table1Id Into _Table2 = Group _
From _t2 In _Table2.DefaultIfEmpty() _
Where If(_t2 Is Nothing, True, _t2.Table2WhereColumn1 = someId AndAlso _
_t2.Table2WhereColumn2 = someOtherId) _
Order By t1.OrderByColumn _
Select New With { _
.Table1Id = t1.Table1Id, _
.FieldDescription = t1.FieldDescription, _
.FieldValue = If(_t2 Is Nothing, "", _t2.FieldValue) _
}
Let me know if they work. Fingers crossed. :-)
Personally if there are where conditions for the right hand side of a left join I generally prefer to put them into the join criteria
In this case the SQL would look like:
SELECT t1.Table1Id,
t1.FieldDescription,
t2.FieldValue
FROM Table1 t1 WITH (NOLOCK)
LEFT JOIN Table2 t2 WITH (NOLOCK) ON t1.Table1Id = t2.Table1Id
AND t2.Table2WhereColumn1 = #someId
AND t2.Table2WhereColumn2 = #someOtherId
ORDER BY t1.OrderByColumn
The LINQ code for this (in C#) would look like:
var query =
from t1 in Table1
join t2 in Table2 on new{a = t1.Table1Id, b = someId, c = someotherId}
equals new {a = t2.Table1Id b = t2.Table2WhereColumn1, c = Table2WhereColumn2}
into _Table2
from _t2 in _Table2.DefaultIfEmpty()
orderby t1.OrderByColumn
select new
{
t1.Table1Id,
t1.FieldDescription,
FieldValue = _t2 == null ? "" : _t2.FieldValue,
};
not tested it - but should work
I won't take credit for this answer but it's gorgeous: LINQ to SQL - Left Outer Join with multiple join conditions
Essentially, use extension method where clause on the subquery but you must use it before DefaultIfEmpty():
from p in context.Periods
join f in context.Facts on p.id equals f.periodid into fg
from fgi in fg.Where(f => f.otherid == 17).DefaultIfEmpty()
where p.companyid == 100
select f.value