Linq query with GROUP BY on multiple fields, SUM and COUNT - vb.net

I'm trying to use a LINQ SQL to query my database.
Let's say I have some data that looks like this:
var1 var2 var3 qty
1 a 1a 50
1 a 1a 25
2 b 2b 10
2 b 2b 15
2 b 2b 10
3 a 3a 25
I know how to format my query in SQL. It would be something like this:
SELECT var1, var2, var3, count(*) AS count, sum(qty) As quantity
FROM MyTable
GROUP BY var1, var2, var3
In this case the output would be something like this:
var1 var2 var3 Count Qty
1 a 1a 2 75
2 b 2b 3 35
3 a 3a 1 25
How can I do the same thing with LINQ in vb.net
Dim groups = From j In MyTable
Group By j.var1, j.var2, j.var3 Into g
Select new {var1 = g.var1,
var2 = g.var2,
var3 = g.var3,
quantity = sum(g.qty),
count = count(*)}
That's not quite right, but I think it's close. I don't understand the syntax of the group by in VB.NET.

You want to group by an anonymous type, this is the syntax in VB.NET (note the Key):
Dim groups =
From j In MyTable
Group By x = New With {Key .var1 = j.var1, Key .var2 = j.var2, Key .var3 = j.var3} Into g = Group
Select New With {
.var1 = x.var1,
.var2 = x.var2,
.var3 = x.var3,
.quantity = g.Sum(Function(r) r.qty),
.count = g.Count()
}

You will have to use anonymous type like this:-
Group j By Key = New With { Key .Var1 = j.var1,
Key .Var2 = j.var2,
Key .Var3 = j.var3 } Into Group
Select New With { .var1 = Key.Var1,
.var2 = Key.Var2,
.var3 = Key.Var3,
.quantity = Group.Sum(Function(x) x.qty),
.count = Group.Count()
}

Related

datatable sum column and concatenate rows using LINQ and group by on multiple columns

I Have a datatable with following records
ID NAME VALUE CONTENT
1 AAA 10 SYS, LKE
2 BBB 20 NOM
1 AAA 15 BST
3 CCC 30 DSR
2 BBB 05 EFG
I want to write a VB.NET/LINQ query to have a output like below table: -
ID NAME SUM CONTENT (as CSV)
1 AAA 25 SYS, LKE, BST
2 BBB 25 NOM, EFG
3 CCC 30 DSR
Please provide me LINQ query to get the desired result. Thanks.
I have tried concatenation using below query
Dim grouped = From row In dtTgt.AsEnumerable() _
Group row By New With {row.Field(Of Int16)("ID"), row.Field(Of String)("Name")} _
Into grp() _
Select ID, Name, CONTENT= String.Join(",", From i In grp Select i.Field(Of String)("CONTENT"))
This query will give you the expected output:-
Dim result = From row In dt.AsEnumerable()
Group row By _group = New With {Key .Id = row.Field(Of Integer)("Id"),
Key .Name = row.Field(Of String)("Name")} Into g = Group
Select New With {Key .Id = _group.Id, Key .Name = _group.Name,
Key .Sum = g.Sum(Function(x) x.Field(Of Integer)("Value")),
Key .Content = String.Join(",", g.Select(Function(x) x.Field(Of String)("Content")))}
Thanks for your answers.
However, I have managed to get the desired result using simple code (Without LINQ): -
Dim dt2 As New DataTable
dt2 = dt.Clone()
For Each dRow As DataRow In dt.Rows
Dim iID As Integer = dRow("ID")
Dim sName As String = dRow("Name")
Dim sContt As String = dRow("Content")
Dim iValue As Integer = dRow("Value")
Dim rwTgt() As DataRow = dt2.Select("ID=" & iID)
If rwTgt.Length > 0 Then
rwTgt(0)("Value") += iValue
rwTgt(0)("Content") += ", " & sContt
Else
rw = dt2.NewRow()
rw("ID") = iID
rw("Name") = sName
rw("Value") = iValue
rw("Content") = sContt
dt2.Rows.Add(rw)
End If
Next

Linq GROUP BY or manually grouping in a loop?

Yesterday I asked this question on stackoverflow. Today I realize that if I do a GROUP BY I also need to create a new type of object.
Let's say I have some data that looks like this:
var1 var2 var3 qty
1 a 1a 50
1 a 1a 25
2 b 2b 10
2 b 2b 15
2 b 2b 10
3 a 3a 25
Here is my working LinQ query
From j In MyTable
Where j.var1 = "xxx"
Group j By Key = New With {Key .var1 = j.var1, Key .var2= j.var2, Key .var3 = j.var3} Into Group
Select New With {.var1 = Key.var1, .var2 = Key.var2, .var3 = Key.var3, .qty = Group.Sum(Function(x) x.qty)}
Actually I use Entity Framework and the code look more like this
Dim foo = (From j In dbContext.MyTable
Where j.var1 = anotherVariable
Group j By Key = New With {Key .var1 = j.var1, Key .var2= j.var2, Key .var3 = j.var3} Into Group
Select New With {.var1 = Key.var1, .var2 = Key.var2, .var3 = Key.var3, .quantity = Group.Sum(Function(x) x.Qty)}).ToArray()
foo is a new type that doesn't exist in my generated Entities. But I have an entity generated by my entity framework that can contains these. It's MyTable itself. I use a GROUP BY only to sum a column of MyTable. I query a MyTable entities and I can put the result in a MyTable entity too.
My question are
1) Can I write something like this
Dim foo = (From j In dbContext.MyTable
Where j.var1 = anotherVariable
Group j By Key = New With {Key .var1 = j.var1, Key .var2= j.var2, Key .var3 = j.var3} Into Group
Select New MyTable With {.var1 = Key.var1, .var2 = Key.var2, .var3 = Key.var3, .qty = Group.Sum(Function(x) x.qty)}).ToArray()
In this case do I need to explicitely write all the mappings ?
2) Should I change my mind. Do a simpler query without GROUP BY and try to group and sum in a VB.NET loop (For Each). Or two queries ? On to get all MyTable with a WHERE clause and another to group ?
Dim foo = dbContext.MyTable.Where(Function(p As MyTable) p.var1 = anotherVariable).ToArray()
For Each bar In foo
'Code to group and sum or another query
Next
You won't be able to instantiate MyTable in an LINQ to Entities query but you can simply enumerate the results of the projection with ToArray and then construct the entities with another Select call.

Grouping identical rows and returning the id of the row with a maximum value

I have a datatable like below:
id desc amt value 1 value 2 value 3 value 4 count Consolidated ID
1 test 23 78 98 30 13 5
2 sample 14 25 45 36 12 24
3 test 23 78 98 30 13 30
4 sample 14 25 45 36 12 20
5 test 23 78 98 30 13 11
I need to
group by the columns desc, amt, value1, value2, value3 and value4, and
return the consolidated ID as the id which has maximum in the count column for each group. Result set should be like below:
id desc amt value 1 value 2 value 3 value 4 count Consolidated ID
1 test 23 78 98 30 13 5 3
3 test 23 78 98 30 13 30 3
5 test 23 78 98 30 13 11 3
2 sample 14 25 45 36 12 24 2
4 sample 14 25 45 36 12 20 2
I know this can be done looping through datatable. But is there a simpler way to do it with LINQ in 1 or 2 statements?
DataTable - dt
Result will be of type IEnumerable of anonymous type in the Select statement, which has same columns as your DataTable
var final = dt.AsEnumerable().GroupBy(x=>
new {
amt = x["amt"],
value1 = x["value1"],
value2 = x["value2"],
value3 = x["value3"],
value4 = x["value4"],
})
.ToDictionary(p=>p.Key,
p=>new {id = p.Select(s=>s["id"]),
cid = p.OrderByDescending(n=>Convert.ToInt32((n["count"]))).Select(s=>s["id"]).First()})
.SelectMany(a=>a.Value.id.Select(h=>new {
h,
a.Key.amt,
a.Key.value1,
a.Key.value2,
a.Key.value3,
a.Key.value4,
ConsolidatedID = a.Value.cid
}));
Also pasting the VB.Net version (using Telerik Code Converter) as that has been the point of contention, though needs verification, as I do not work in VB.Net
Dim final = dt.AsEnumerable().GroupBy(Function(x) New With { _
Key .amt = x("amt"), _
Key .value1 = x("value1"), _
Key .value2 = x("value2"), _
Key .value3 = x("value3"), _
Key .value4 = x("value4") _
}).ToDictionary(Function(p) p.Key, Function(p) New With { _
Key .id = p.[Select](Function(s) s("id")), _
Key .cid = p.OrderByDescending(Function(n) Convert.ToInt32((n("count")))).[Select](Function(s) s("id")).First() _
}).SelectMany(Function(a) a.Value.id.[Select](Function(h) New With { _
h, _
a.Key.amt, _
a.Key.value1, _
a.Key.value2, _
a.Key.value3, _
a.Key.value4, _
Key .ConsolidatedID = a.Value.cid _
}))
If you want to fill the consolidated id column in the datatable, I suggest you first define a local function that returns the "key" of the row -- the values important to grouping:
Dim keyGetter = Function(row As DataRow) New With {
Key .desc = row("desc"),
Key .amt = row("amt"),
Key .value1 = row("value 1"),
Key .value2 = row("value 2"),
Key .value3 = row("value 3"),
Key .value4 = row("value 4")
}
Then, for each group, you can get the id of the row with the maximum count:
Dim results = dt.AsEnumerable.GroupBy(keyGetter, Function(key, grp)
Dim maxCount = grp.Max(Function(row) x("count"))
Return grp.First(Function(row) row("count") = maxCount)
End Function).ToDictionary(Function(x) x.Key, Function(x) x.First)
Then you can iterate over the datatable and fill the column. For each row, generate the key using the keyGetter, and use that to get the consolidated id:
For Each row In dt.AsEnumerable
row("consolidated id") = results(keyGetter(row))
Next

SQL: Variable to update a set of items

SO, each item has an id, a setId, and a name.
Current data may only have one NAME field filled out in an entire set.
EX>
>ID = 1 >NAME = 'Bob' >SETID = 5
>ID = 2 >NAME = NULL >SETID = 5
>ID = 3 >NAME = NULL >SETID = 5
>ID = 4 >NAME = NULL >SETID = 5
I am looking for an SQL script that GET the one record that is not null in a "SET"
SET the remaining items in the "SET" for that "NAME" record.
The end result I am aiming for would look like this:
>ID = 1 >NAME = 'Bob' >SETID = 5
>ID = 2 >NAME = 'Bob' >SETID = 5
>ID = 3 >NAME = 'Bob' >SETID = 5
>ID = 4 >NAME = 'Bob' >SETID = 5
Assuming there is only one Name in a set that is not null, you could use a CTE and do something like:
;WITH NameHelper AS
(
SELECT
Name,
SetID
FROM MyTable
WHERE Name IS NOT NULL
GROUP BY Name, SetID
)
UPDATE MyTable SET Name = NameHelper.Name
FROM MyTable
INNER JOIN NameHelper ON MyTable.SetID = NameHelper.SetID

LINQ Group by and Sum syntax

i have the following vb.net LINQ Query
Dim q = From row In dx.AsEnumerable
Group row By G = New With {.Cat = row.Field(Of Integer)("catalogid")}.Cat,
New With {.Backorder = row.Field(Of Integer)("backorder")}.Backorder Into pg = Group
Select New With
{
.Cat = pg.Cat,
.Backorder = pg.Sum(Function(x) x.Backorder)
}
i have this datatable called dx
catalogid backorder
1 5
1 10
2 1
2 5
i want to Sum backorder column where catalogid is the same so the result is the following
catalogid backorder
1 15
2 6
in the Select new with part, what is wrong?
Try following...
var result = dx.AsEnumerable()
.GroupBy(row => row.Field<int>("catalogid"))
.Select(group => group.Sum(item => item.Field<int> ("backorder")).CopyToDataTable();
or
var result= from s in dx.AsEnumerable()
group s by s.Field<int>("catalogid") into grp
select new {
CatalogId= grp.Key,
Back Order = grp.Sum(r => r.Field<int>("backorder"))
};
DataTable datatbl = result.CopyToDataTable();