Grouping troubles in LINQ - c#

I have some list of structures like this:
struct va_data
{
public int item_id;
public int type_id;
public double item_value;
public DateTime value_date;
}
I trying group the list by type_id and take items where value_date is maximum then group by item_id and take items only where item_value is minimum
There is my syntax
from x in dataList
group x by x.type_id into grouped
select grouped.Where(x => x.value_date == grouped.Max(y => y.value_date))
.GroupBy(x => x.item_id) // and here i was stuck.
Example
var dataList = new []
{
new va_data {item_id = 1, type_id = 1, item_value = 0, value_date = "2013.07.29"},
new va_data {item_id = 1, type_id = 1, item_value = 1, value_date = "2013.07.30"},
new va_data {item_id = 2, type_id = 1, item_value = 0, value_date = "2013.07.29"},
new va_data {item_id = 2, type_id = 1, item_value = 1, value_date = "2013.07.29"},
new va_data {item_id = 4, type_id = 2, item_value = 5, value_date = "2013.07.29"},
new va_data {item_id = 4, type_id = 3, item_value = 9, value_date = "2013.07.30"},
};
The result must be
var dataListResult = new []
{
new va_data {item_id = 1, type_id = 1, item_value = 1, value_date = "2013.07.30"},
new va_data {item_id = 2, type_id = 1, item_value = 0, value_date = "2013.07.29"},
new va_data {item_id = 4, type_id = 2, item_value = 5, value_date = "2013.07.29"},
}

Given the following class
class va_data
{
public int item_id;
public int type_id;
public double item_value;
public DateTime value_date;
}
and your example data, you can use a query like this:
from data in dataList
group data by new {data.item_id, data.type_id} into g
let max_value_date = g.Max(x => x.value_date)
from i in g.Where(x => x.value_date == max_value_date)
group i by i.item_id into g2
let min_item_value = g2.Min(x => x.item_value)
from x in g2
where x.item_value == min_item_value
select x;
to get the following result:

Just split your query into two parts - getting latest of each type, and then getting minimal of each item:
var latestOfEachType =
from d in dataList
group d by d.type_id into typeGroup
select typeGroup.OrderByDescending(x => x.value_date).First();
var result = from d in latestOfEachType
group d by d.item_id into itemGroup
select itemGroup.OrderBy(x => x.item_value).First();
This query will be executed as single query. But in this case it looks much more readable to me. Also don't use mutable structs!. Use classes instead.
EDIT: Thus you have several items for max date, then query needs two small tweaks - select all items where date is max, and use SelectMany to iterate over them:
var latestOfEachType =
from d in dataList
group d by d.type_id into typeGroup
let maxDate = typeGroup.Max(x => x.value_date)
select typeGroup.Where(x => x.value_date == maxDate);
var result = from d in latestOfEachType.SelectMany(g => g)
group d by d.item_id into itemGroup
select itemGroup.OrderBy(x => x.item_value).First();

Related

LINQ Query for sum and aggregate data

I have two model classes of which I have provided the sql table structure
CREATE TABLE [DBO].[TBL_PRODUCTION] ( -- class ProductionModel
PRODUCTION_ID INT IDENTITY(1, 1) NOT NULL
,PRODUCTION_NAME NVARCHAR(200) NOT NULL
,PRODUCTION_TYPE INT NOT NULL
,PRODUCTION_QUANTITY INT
,CONSTRAINT PK_PRODUCTION PRIMARY KEY (PRODUCTION_ID)
)
INSERT INTO [DBO].[TBL_PRODUCTION] VALUES ('SGU',1, 100)
INSERT INTO [DBO].[TBL_PRODUCTION] VALUES ('BGU',1, 100)
INSERT INTO [DBO].[TBL_PRODUCTION] VALUES ('CCGU',2, 150)
CREATE TABLE [DBO].[TBL_DISTRIBUTOR] ( class DistributorModel
DISTRIBUTOR_ID INT IDENTITY(1, 1) NOT NULL
,PRODUCTION_ID INT NOT NULL
,QUARTER_TYPE INT NOT NULL
,DEMAND_QUANTITY INT
,CONSTRAINT PK_DISTRIBUTOR PRIMARY KEY (DISTRIBUTOR_ID)
)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (1,555,1,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (1,555,2,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (1,655,3,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (2,555,1,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (2,745,2,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (3,745,3,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (1,745,3,10)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (2,745,3,50)
I need to show data about total distribution on quarterly basis. I mean how many total productions are distributed in each quarter and the remaining production quantity. I have made the sql query but how to design LINQ query for the same. I have excluded the remaining quantity in SQL Query but need to show in LINQ Query.
SQL Query
SELECT
A.PRODUCTION_ID
,B.PRODUCTION_NAME
,A.QUARTER_TYPE
,B.PRODUCTION_QUANTITY
,SUM(A.DEMAND_QUANTITY) [TOTAL DISTRIBUTED]
FROM [DBO].[TBL_DISTRIBUTOR] A
INNER JOIN [DBO].[TBL_PRODUCTION] B ON A.PRODUCTION_ID = B.PRODUCTION_ID
GROUP BY
A.PRODUCTION_ID
,B.PRODUCTION_NAME
,B.PRODUCTION_QUANTITY
,A.QUARTER_TYPE
ORDER BY A.PRODUCTION_ID
I am just learning LINQ query so do not have idea how to express this in sql query in LINQ. I have the POCO Class too, now how to proceed further.
public class DistributorViewModels
{
public int PRODUCTION_ID { get; set; }
public string PRODUCTION_NAME { get; set; }
public int QUARTER_TYPE { get; set; }
public int PRODUCTION_QUANTITY { get; set; }
public int TOTAL_DISTRIBUTED { get; set; }
public int REMAINING_QUANTITY { get; set; }
}
Please see the attached image for what is my desired output.
Hoping for positive response. Thank You to all!!!
firstly some thoughts:
Does it make sense to group by production quanitity?
Also does it make sense to group by production name if you have already grouped by production id?
Based on those thoughts I would created following linq statements (the beginning is only data from your desired output):
List<DISTRIBUTOR> tbl_distribution = new List<DISTRIBUTOR>(){
new DISTRIBUTOR{PRODUCTION_ID = 1, QUARTER_TYPE = 1, DEMAND_QUANTITY = 15},
new DISTRIBUTOR{PRODUCTION_ID = 1, QUARTER_TYPE = 1, DEMAND_QUANTITY = 10},
new DISTRIBUTOR{PRODUCTION_ID = 1, QUARTER_TYPE = 2, DEMAND_QUANTITY = 25},
new DISTRIBUTOR{PRODUCTION_ID = 1, QUARTER_TYPE = 3, DEMAND_QUANTITY = 35},
new DISTRIBUTOR{PRODUCTION_ID = 2, QUARTER_TYPE = 1, DEMAND_QUANTITY = 25},
new DISTRIBUTOR{PRODUCTION_ID = 2, QUARTER_TYPE = 2, DEMAND_QUANTITY = 25},
new DISTRIBUTOR{PRODUCTION_ID = 2, QUARTER_TYPE = 3, DEMAND_QUANTITY = 50},
new DISTRIBUTOR{PRODUCTION_ID = 3, QUARTER_TYPE = 3, DEMAND_QUANTITY = 25},
};
List<PRODUCTION> tbl_production = new List<PRODUCTION>{
new PRODUCTION{ PRODUCTION_ID = 1, PRODUCTION_NAME = "SGU", PRODUCTION_QUANTITY = 100},
new PRODUCTION{ PRODUCTION_ID = 2, PRODUCTION_NAME = "BGU", PRODUCTION_QUANTITY = 100},
new PRODUCTION{ PRODUCTION_ID = 3, PRODUCTION_NAME = "CCGU", PRODUCTION_QUANTITY = 150},
};
var temp = from a in tbl_distribution
join b in tbl_production on a.PRODUCTION_ID equals b.PRODUCTION_ID
let totalProductDistributed = tbl_distribution.Where(t => t.PRODUCTION_ID == a.PRODUCTION_ID ).Sum(t => t.DEMAND_QUANTITY)
select new {
PRODUCTION_ID = a.PRODUCTION_ID,
PRODUCTION_NAME = b.PRODUCTION_NAME,
QUARTER_TYPE = a.QUARTER_TYPE,
PRODUCTION_QUANTITY = b.PRODUCTION_QUANTITY,
TOTAL_DISTRIBUTED = totalProductDistributed,
REMAINING_QUANTITY = b.PRODUCTION_QUANTITY - totalProductDistributed
};
var ViewModel = from s in temp group s by new{s.PRODUCTION_ID, s.QUARTER_TYPE} into g
select g.FirstOrDefault();
But you can also group by production id and have a table of quarterly infos. That would be just a single linq statement and you can achive your "merged" rows in one column:
var ViewModel2 = from a in tbl_distribution group a by a.PRODUCTION_ID into g
join b in tbl_production on g.FirstOrDefault().PRODUCTION_ID equals b.PRODUCTION_ID
let quarterInfos = from c in g
group c by c.QUARTER_TYPE into d
select new
{
Quarter = d.FirstOrDefault().QUARTER_TYPE,
DemandQuantity = d.Sum(t => t.DEMAND_QUANTITY),
}
select new{
g.Key,
b.PRODUCTION_NAME,
b.PRODUCTION_QUANTITY,
QuarterInfos = quarterInfos,
RemainingQuantity = b.PRODUCTION_QUANTITY - quarterInfos.Sum(i => i.DemandQuantity)
};

how can I write this linq group by as a non-query expression?

I have a collection of group users which has a GroupId and UserId. I need to filter out any duplicate GroupId/UserId objects which may exist in the collection. How can I write a non-query expression GroupBy to filter out the duplicate rows? The following example is adapted from a group by example that I found online but I'm not quite clear on how to refine this code for my particular scenario:
var groupByResults =
groupUsers.GroupBy(
x => x.GroupId,
x => x.UserId,
(key, g) => new
{
[?] = key,
[?] = g.ToList()
}
);
If your data looks like the list below you can group by the compound key then take the first value in each group. The OrderBy is optional
var groupUsers = new List<dynamic>() {
new { groupId = 1, userId = 1, name = "a" },
new { groupId = 1, userId = 1, name = "b" },
new { groupId = 1, userId = 2, name = "c" }
};
var result = groupUsers
.GroupBy(u => new { u.groupId, u.userId} )
.Select(g => g.OrderBy(u => u.name).FirstOrDefault());
To find out the duplicated userId, groupId.
GroupBy userId, groupId
Count if any group item >=2
SelectMany the collection
Code:
var duplicatedUsers = groupUsers
.GroupBy(gu => new { gu.UserId, gu.GroupId })
.Where(g => g.Count() >= 2)
.SelectMany(g => g)
Following code will be helpful to you,
class GroupUsers
{
public int GroupId {get;set;}
public int UserId {get;set;}
}
public class Program
{
public static void Main()
{
var groupUsers = new List<GroupUsers>() {
new GroupUsers{ GroupId = 1, UserId = 1},
new GroupUsers{ GroupId = 1, UserId = 1},
new GroupUsers{ GroupId = 1, UserId = 2},
new GroupUsers{ GroupId = 1, UserId = 2},
new GroupUsers{ GroupId = 1, UserId = 3},
new GroupUsers{ GroupId = 1, UserId = 4},
new GroupUsers{ GroupId = 1, UserId = 5},
new GroupUsers{ GroupId = 1, UserId = 3}
};
var result1 = groupUsers
.GroupBy(u => new { u.GroupId, u.UserId} )
.Where(g => g.Count()>=2) // check for duplicate value by checking whether the count is greater than or equal to 2.
.SelectMany(g=>g); // flatten the list
foreach(var user in result1) // Iterate over the result
{
Console.WriteLine(user.GroupId +" "+user.UserId);
}
// Or
var result2 = from a in groupUsers
group a by new{a.GroupId, a.UserId} into grp
where grp.Count()>=2
from g in grp select new{g}
foreach(var user in result2)
{
Console.WriteLine(user.g.GroupId +" "+user.g.UserId);
}
}
}

How to group by in LINQ?

I need to return the last 30 days of a speciefic user daily appointments and check if the user made at least 8 hours of appointments for each day.
in sql i can do that with this command:
select IDAppointment,IDUser, SUM(DurationInHours) from Note where AppointmentDate > *lastmonth and IDUser = #userID group by IDUser,IDAppointment,AppointmentDate
and after that i get the result and validate the DurationInHours(double type).
Is it possible to do it using LINQ?
Get the list of the last month user appointments and validate it day by day
Thanks!
This should be roughly there although this is off the top of my head as not at an IDE.
var result = context.Notes
.Where(n => [Your where clause])
.GroupBy(n => new { n.IDUser, n.IDAppointment, n.AppointmentDate})
.Select(g => new {
g.Key.IDAppointment,
g.Key.IDUser,
g.Sum(n => n.DurationInHours)});
UPDATE:
For reference your where clause will be something like this... (again off the top of my head)
DateTime lastMonth = DateTime.Today.AddMonths(-1);
int userId = 1 // TODO: FIX
var result = context.Notes.Where(n => n.AppointmentDate > lastMonth
&& n.IDUser = userId)
Resulting in....
DateTime lastMonth = DateTime.Today.AddMonths(-1);
int userId = 1 // TODO: FIX
var result = context.Notes
.Where(n => n.AppointmentDate > lastMonth
&& n.IDUser = userId)
.GroupBy(n => new { n.IDUser, n.IDAppointment, n.AppointmentDate})
.Select(g => new {
g.Key.IDAppointment,
g.Key.IDUser,
g.Sum(n => n.DurationInHours)});
Here is a solution which I tested.
DateTime lastMonth = DateTime.Today.AddMonths(-1);
int selectedUserId = 2;
var notes = new List<Note>(
new Note[] {
new Note() {
AppointmentDate = new DateTime(2013,7,30){},
IDAppointment = 1, IDUser = 1, DurationInHours = 1
},
new Note() {
AppointmentDate = new DateTime(2013,7,30){},
IDAppointment = 1, IDUser = 1, DurationInHours = 2
},
new Note() {
AppointmentDate = new DateTime(2013,7,30){},
IDAppointment = 1, IDUser = 1, DurationInHours = 3
},
new Note() {
AppointmentDate = new DateTime(2013,7,28){},
IDAppointment = 2, IDUser = 2, DurationInHours = 2
},
new Note() {
AppointmentDate = new DateTime(2013,7,28){},
IDAppointment = 2, IDUser = 2, DurationInHours = 3
},
new Note() {
AppointmentDate = new DateTime(2013,7,27){},
IDAppointment = 2, IDUser = 2, DurationI nHours = 4
},
new Note() {
AppointmentDate = new DateTime(2013,7,26){},
IDAppointment = 3, IDUser = 3, DurationInHours = 3
},
new Note() {
AppointmentDate = new DateTime(2013,7,25){},
IDAppointment = 3, IDUser = 3, DurationInHours = 4
},
new Note() {
AppointmentDate = new DateTime(2013,7,24){},
IDAppointment = 3, IDUser = 3, DurationInHours = 5
}
}
);
var results = from n in notes
group n by new {n.IDUser, n.IDAppointment, n.AppointmentDate}
into g
where g.Key.AppointmentDate > lastMonth &&
g.Key.IDUser == selectedUserId
select new {
g.Key.IDAppointment,
g.Key.IDUser,
TotalHours = g.Sum(n => n.DurationInHours)
};
The summation property needed to be given a name explicitly (i.e. TotalHours) or else you get error CS0746: Invalid anonymous type member declarator. Anonymous type members must be declared with a member assignment, simple name or member access.

Use the Linq Count

I want to show OrderDetails Count near the orders information in the grid but in the select Unit i can only select the Key and Count. What is the way to select the orders information?
var Q = from Data in Context.Orders
join D2 in Context.OrderDetails on Data.OrderID equals D2.OrderID
group Data by Data.OrderID into grouped
select new
{
grouped=g.Key,
Count = grouped.Count()
};
You can group it by whole order entity like
var Q = from Data in Context.Orders
join D2 in Context.OrderDetails on Data.OrderID equals D2.OrderID
group Data by Data into grouped
select new
{
OrderId = grouped.Key.OrderId,
OrderDate = grouped.Key.OrderDate
Shipping = grouped.Key.Shipping
.
.
.
Count = grouped.Count()
};
EDIT Linqpad program for similar query on in memory collection of objects
void Main()
{
var orders = new List<Order>{
new Order{OrderId = 1, DeliverIn = 5},
new Order{OrderId = 2, DeliverIn = 6},
new Order{OrderId = 3, DeliverIn = 5},
};
var lines = new List<OrderLine>{
new OrderLine{LineId = 1, OrderId = 1, ProductId = 1},
new OrderLine{LineId = 2, OrderId = 1, ProductId = 2},
new OrderLine{LineId = 3, OrderId = 1, ProductId = 3},
new OrderLine{LineId = 4, OrderId = 2, ProductId = 1},
new OrderLine{LineId = 5, OrderId = 2, ProductId = 3},
new OrderLine{LineId = 6, OrderId = 2, ProductId = 4},
};
var query = from o in orders join l in lines on
o.OrderId equals l.OrderId
group o by o into grouped
select new
{
Count = grouped.Count(),
grouped.Key.OrderId,
grouped.Key.DeliverIn
};
Console.WriteLine(query);
}
// Define other methods and classes here
public class Order
{
public int OrderId{get;set;}
public int DeliverIn{get;set;}
}
public class OrderLine
{
public int LineId{get;set;}
public int OrderId{get;set;}
public int ProductId{get;set;}
}
and if you don't have linq pad simply go and grab it from their site. It is simply awesome.
Check out IGrouping documentation on MSDN.
public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>,
IEnumerable
Pay attention to IEnumerable. Count is just an extension method of IEnumerable. You can easily Select from grouping or loop through it.
For example:
var Q = from Data in Context.Orders
join D2 in Context.OrderDetails on Data.OrderID equals D2.OrderID
group Data by Data.OrderID into grouped
select new
{
grouped=g.Key,
Count = grouped.Count(),
Orders = grouped.ToArray()
//you can also just return grouped itself to support lazy queries
};
Just flatten them into array or list and then get its count.
select new
{
Key = g.Key,
Orders = grouped.ToArray()
};
Then get count:
int count = result.Orders.Count; // Property of an array.

LINQ to SQL group by with take

I have a table that looks like this:
Id GroupId Value
and it has about 100 rows
How can I return the top 10 rows for value but with no duplicating GroupId?
This should do it:
var results = table
.GroupBy(x => x.GroupId)
.Select(x => new { Row = x, Value = x.Max(y => y.Value) })
.OrderByDescending(x => x.Value)
.Select(x => x.Row)
.Take(10);
Edit: Modified to return the entire object.
Not sure if this translates to LINQ-to-SQL, but here's an idea from L2Obj
var query = (from foo in foos
group foo by foo.GroupId into fg
select fg.OrderByDescending(f => f.Value).First())
.OrderByDescending(f => f.Value)
.Take(10);
In english, it groups on the GroupId and then selects the Foo with the highest Value from each group, orders those, and then takes 10. If anything, you could get a concrete list of your objects from L2SQL and then perform the grouping in memory, should not be a performance/memory issue since you say there are only 100 rows.
For LINQ-to-SQL, you might try something like this
var sqlQuery = (from foo in foos
join y in
(from f2 in foos
join x in
(from f1 in foos
group f1 by f1.GroupId into vg
select new { GroupId = vg.Key, MaxVal = vg.Max(f => f.Value) })
on f2.GroupId equals x.GroupId
where f2.Value == x.MaxVal
group f2 by f2.GroupId into mg
select new { GroupId = mg.Key, MinId = mg.Min(f => f.Id) })
on foo.Id equals y.MinId
orderby foo.Value descending
select foo).Take(10);
This is based on a SQL query to perform the same operation
Select top 10 f.*
From Foos f
Inner Join
(Select f.GroupID, min(f.Id) as MinId
From Foos f
Inner Join
(Select GroupId, Max(Value) as MaxVal
From Foos
Group By GroupId) x
on f.GroupId = x.GroupId
and f.Value = x.MaxVal
Group By f.GroupId) y
on f.Id = y.MinId
order by f.Value desc
It basically performs two groupings. The first gets the max value for each group, the second gets the min ID for each record from each group that has the max value (in case 2 records in a group have the same value), and then selects the top 10 records.
This one will get the full row values (it's working for me with the sample data I show bellow):
static void Main(string[] args)
{
Whatever one = new Whatever() {GroupId = 1, Id = 1, Value = 2};
Whatever two = new Whatever() { GroupId = 1, Id = 2, Value = 8 };
Whatever three = new Whatever() { GroupId = 2, Id = 3, Value = 16 };
Whatever four = new Whatever() { GroupId = 2, Id = 4, Value = 7 };
Whatever five = new Whatever() { GroupId = 3, Id = 5, Value = 21 };
Whatever six = new Whatever() { GroupId = 3, Id = 6, Value = 12 };
Whatever seven = new Whatever() { GroupId = 4, Id = 7, Value = 5 };
Whatever eight = new Whatever() { GroupId = 5, Id = 8, Value = 17 };
Whatever nine = new Whatever() { GroupId = 6, Id = 9, Value = 13 };
Whatever ten = new Whatever() { GroupId = 7, Id = 10, Value = 44 };
List<Whatever> list = new List<Whatever>();
list.Add(one);
list.Add(two);
list.Add(three);
list.Add(four);
list.Add(five);
list.Add(six);
list.Add(seven);
list.Add(eight);
list.Add(nine);
list.Add(ten);
var results = (from w in list
group w by w.GroupId into g
select new { GroupId = g.Key,
Value = g.Max(w => w.Value),
Id = g.OrderBy(w=>w.Value).Last().Id }).
OrderByDescending(w=>w.Value).Take(5);
foreach (var r in results)
{
Console.WriteLine("GroupId = {0},
Id = {1},
Value = {2}",
r.GroupId, r.Id, r.Value);
}
}
Output:
GroupId = 7, Id = 10, Value = 44
GroupId = 3, Id = 5, Value = 21
GroupId = 5, Id = 8, Value = 17
GroupId = 2, Id = 3, Value = 16
GroupId = 6, Id = 9, Value = 13

Categories

Resources