Linq involving groupby orderby and join - c#

I have two tables
tblEquipment
Id1 Id2 Version1 Version2
1 1 - 0
2 1 A 1
3 1 B 1
4 1 B 2
5 2 - 0
6 2 A 0
and another table
tblHistory
IdParent Version1 Version2 Date
1 - 0 1/01/14
1 A 1 2/01/14
1 B 1 3/01/14
1 B 2 4/01/14
2 - 0 4/01/14
2 A 0 6/01/14
2 A 0 8/01/14
I am trying to write a query that fetches the record which has the maximum Version1 and Version2 corresponding to the maximum version1. For e.g. I want the following records from the above table -
Id2 = 1, Version1 = B, Version2 = 2 and Date = 4/01/14
Id2 = 2, Version1 = A, Version2 = 0 and Date = 8/01/14
Can anyone help me with the linq that gives me the above result.

Fetching data according the rule you described would be like this:
var result = history
.GroupBy( h => h.IdParent )
.Select( h => h.OrderBy( h1 => h1.Version1 )
.ThenBy( h2 => h2.Version2 )
.Last() )
.Select(h => new {
Id2 = h.IdParent,
Version1 = h.Version1,
Version2 = h.Version2,
Date = h.Date
}
);

Related

LINQ select from join table where the same foreign key has records for two different IDs

Ok, so the title is a little bit confusing I guess. Basically I have those 3 tables:
Line
id | Name
---------
1 | "A-B"
2 | "A-D"
Stop
id | Name
---------
1 | A
2 | B
3 | C
4 | D
LineStop
Id | LineId | StopId | Order
----------------------------
1 | 1 | 1 | 0
2 | 1 | 2 | 1
3 | 2 | 1 | 0
4 | 2 | 2 | 1
5 | 2 | 3 | 3
4 | 2 | 4 | 4
So this is some sort of bus ticketing system which I work on of personal improvement.
As an input I get the departure StopId (Stop.Id) and the arrival StopId (Stop.Id). I want to select all lines that has those two stops in their routes (This would mean that in LineSop table for the same LineId I'll have records with both the departuring and arrival stops, ultimately I would also like to consider the Order column which tells in what order the bus is going through those Stops, because even if the line have the two stops I'm interested in, if they are in reversed order I'm still not interested.
I know that is highly desirable to show what I've done so far but I struggle with the where conditions which seems to be the key factor here. For some reason I decided to join Line with LineStop:
var lines = _context.Lines.Join(
_context.LineStop,
line => line.Id,
lineStop => lineStop.LineId,
(line, lineStop) => lineStop)
But then.. I need to check if for the same LineId I have records in LineStop table with the start and end StopId and ultimately when I found such records the the starting StopId Order i less than the end StopId Order.
I hope this can help you out:
First I get the trip from the traveler: "I want go from Stop: 2 to Stop:4".
Once I know the line that has both stops I build the stops and its order.
var lines = new List<Line>()
{
new Line() { Id = 1, Name = "A-B" },
new Line() { Id = 2, Name = "A-D" }
};
var stops = new List<Stop>() {
new Stop() { Id = 1, Name = "A" },
new Stop() { Id = 2, Name = "B" },
new Stop() { Id = 3, Name = "C" },
new Stop() { Id = 4, Name = "D" }
};
var lineStops = new List<LineStop>()
{
new LineStop() { Id = 1, LineId = 1, StopId = 1, Order = 0 },
new LineStop() { Id = 2, LineId = 1, StopId = 2, Order = 1 },
new LineStop() { Id = 3, LineId = 2, StopId = 1, Order = 0 },
new LineStop() { Id = 4, LineId = 2, StopId = 2, Order = 1 },
new LineStop() { Id = 5, LineId = 2, StopId = 3, Order = 3 },
new LineStop() { Id = 4, LineId = 2, StopId = 4, Order = 4 },
};
var result = (from trip in (from l in lines
join d in lineStops on l.Id equals d.LineId
join a in lineStops on l.Id equals a.LineId
where d.StopId == 2 && a.StopId == 4
select new { d.LineId })
join l in lines on trip.LineId equals l.Id
join ls in lineStops on l.Id equals ls.LineId
select new { l.Name, ls.StopId, ls.Order }).OrderBy(x => x.Order);
Expected result
Name StopId Order
A-D 1 0
A-D 2 1
A-D 3 3
A-D 4 4

Count Group By Many-to-Many table

I am using ASP.NET MVC and have a many-to-many table that as follows:
custID | objID
================
1 2
1 3
2 5
2 2
3 2
Both userID and objID are Foreign Keys linking to other tables. What I would like to do is get the highest count based on objID. The table above will yield the following results:
objID | objName | Price
=======================
2 | Chicken | 10
The custID does not matter in this scenario as I just want to get the objID with the highest count.
I've tried the following but i'm stuck here:
//retrieve many-to-many table
var retrieved = db.Customer.Include(c => c.Objects)
var topID = retrieved.GroupBy(q => q.objID)
.OrderByDescending(g => g.Count())
Should do the trick.
var topID = retrieved.GroupBy(q => q.objID)
.Select(g => new { Id = g.Key, Total = g.Count() })
.OrderByDescending(g => g.Total).First().Id;
List<int> lst = new List<int>() { 1, 3, 4, 5, 1, 2, 3, 4, 5, 2, 3, 2 };
var count = lst.GroupBy(q => q).OrderByDescending(s => s.Count())
.First().Key;
var lstKeyWithCount lst.GroupBy(i => i).Select(g => new { Key=g.Key,Count=g.Count() });
in lstKeyWithCount variable you get key with count.
in count variable you get most times repeated value.

Pivot Table Using Linq

I have a table like this .(VisitType is dynamic)
PersonelId VisitDate VisitTypeId
1 2015-02-24 A
2 2015-02-23 S
2 2015-02-24 D
4 2015-02-22 S
2 2015-02-22 A
2 2015-02-22 B
3 2015-02-23 A
1 2015-02-23 A
1 2015-02-24 D
4 2015-02-24 S
4 2015-02-22 S
2 2015-02-22 S
3 2015-02-24 D
I want to get a pivot of this using linq as below.
VisitDate PersonelId A S D B
2015-02-22 4 0 2 0 0
2015-02-22 2 1 1 0 0
2015-02-23 2 0 1 0 0
2015-02-23 3 1 0 0 0
2015-02-23 1 1 0 0 0
2015-02-24 1 1 0 1 0
2015-02-24 2 0 0 1 0
2015-02-24 4 0 1 0 0
2015-02-24 3 0 0 1 0
I use this linq
var d = (from f in _db.Visits
group f by new {f.VisitDate, f.PersonnelId }
into myGroup
where myGroup.Count() > 0
select new
{
myGroup.Key.VisitDate,
myGroup.Key.PersonnelId,
subject = myGroup.GroupBy(f => f.VisitTypeId).Select
(m => new { Sub = m.Count(), Score = m.Sum(c => c.Amount) })
}).ToList();
It is grouped by date and personel id, but don't count items from every VisitType.
Try this:
//static headers version
var qry = Visits.GroupBy(v=>new{v.VisitDate, v.PersonelId})
.Select(g=>new{
VisitDate = g.Key.VisitDate,
PersonelId = g.Key.PersonelId,
A = g.Where(d=>d.VisitTypeId=="A").Count(),
B = g.Where(d=>d.VisitTypeId=="B").Count(),
D = g.Where(d=>d.VisitTypeId=="D").Count(),
S = g.Where(d=>d.VisitTypeId=="S").Count()
});
//dynamic headers version
var qry = Visits.GroupBy(v=>new{v.VisitDate, v.PersonelId})
.Select(g=>new{
VisitDate = g.Key.VisitDate,
PersonelId = g.Key.PersonelId,
subject = g.GroupBy(f => f.VisitTypeId)
.Select(m => new { Sub = m.Key, Score = m.Count()})
});
first fetch your dynamic Visit Type for person like following
var VisTyp= (from VT in _db.VisitType select new{VisType=VT.VisitType} ).ToList();
then use following.
var d = (from f in _db.Visits
group f by new {f.VisitDate, f.PersonnelId } into myGroup
where myGroup.Count() > 0
select new
{
VisitDate = myGroup.Key.VisitDate,
PersonnelId = myGroup.Key.PersonnelId,
subject=myGroup.Count(t => VisTyp.Contains(t.VisitType))
}).ToList();

lambda expression to sort on min values in a group

my Table data is"
ExpenseID PersonID SeqNumb HistorySeqNumb HistoryCode
1 3 1 1 9
2 1 1 1 9
3 2 1 1 0
4 1 2 1 0
5 1 1 2 0
6 5 1 1 0
7 3 1 2 0
ExpenseID is primary Key column .
If a record is inserted for a personID it has a sequence of 1 and History Code indicating 0 is active record.If the record is edited a new row is inserted with current row historyCode changed to 9 and new row History Code 0and history Sequence 2.
If another new record is inserted for the same person it has a new row with incremented sequence number.
My resultSet should contain the active records and the order the records were inserted:
I need lambda expression
Output should be
ExpenseID PersonID SeqNumb HistorySeqNumb HistoryCode
7 3 1 2 0
5 1 1 2 0
3 2 1 1 0
4 1 2 1 0
6 5 1 1 0
Your desired result seems wrong to me...
You only want active records, ok check
The order they were inserted...
3 > 4 > 5 > 6 > 7 not 7 > 5 > 3 > 4 > 6
In the top table Person ID was added again giving it an ExpenseID of 7 which is fine, it should be first (if you are sorting in descending). But then you have PersonID 1 with expense ID 5 when PersonID 5 with expense ID 6 was the previous record added before PersonID 3 with expense id 7
In short, your input data doesn't match your example desired output, making it rather difficult for anyone to answer you here.
From what I can see it looks like they should be sorted where HistoryCode = 0 Orderd By ExpenseID (bad database design comments aside)...
In such case it would be
var sortedItems = rawItems.Where(w => (w.HistoryCode == 0)).ToList();
sortedItems.Sort((emp1, emp2) => emp2.ExpenseID.CompareTo(emp1.ExpenseID));
//where rawItems is a List<Object> where object has properties for ExpenseID, PersonID... etc
Here is the entire Console App I made to experiment with this (bored), Add an Xml file called Data with the used Node/Attributes in it and set it to copy to output directory,
static void Main(string[] args)
{
var rawItems = new[] { new { ExpenseID = 0, PersonID = 0, SeqNumb = 0, HistorySeqNumb = 0, HistoryCode = 0 } }.ToList();
using (FileStream fs = new FileStream(Environment.CurrentDirectory + "\\data.xml", FileMode.Open, FileAccess.Read, FileShare.None))
{
XmlReader reader = XmlReader.Create(fs);
XDocument doc = XDocument.Load(reader);
fs.Flush();
doc.Root.Elements("Item").ToList().ForEach(i =>
{
var xExpenseID = Convert.ToInt32(i.Attribute("ExpenseID").Value);
var xPersonID = Convert.ToInt32(i.Attribute("PersonID").Value);
var xSeqNumb = Convert.ToInt32(i.Attribute("SeqNumb").Value);
var xHistorySeqNumb = Convert.ToInt32(i.Attribute("HistorySeqNumb").Value);
var xHistoryCode = Convert.ToInt32(i.Attribute("HistoryCode").Value);
rawItems.Add(new { ExpenseID = xExpenseID, PersonID = xPersonID, SeqNumb = xSeqNumb, HistorySeqNumb = xHistorySeqNumb, HistoryCode = xHistoryCode });
});
}
//sort
var sortedItems = rawItems.Where(w => (w.HistoryCode == 0)).ToList();
sortedItems.Sort((emp1, emp2) => emp2.ExpenseID.CompareTo(emp1.ExpenseID));
Console.Write("ExpenseID".PadRight(16, ' '));
Console.Write("PersonID".PadRight(16, ' '));
Console.Write("SeqNumb".PadRight(16, ' '));
Console.Write("HistorySeqNumb".PadRight(16, ' '));
Console.WriteLine("HistoryCode".PadRight(16, ' '));
foreach (var item in sortedItems)
{
Console.Write(item.ExpenseID.ToString().PadRight(16, ' '));
Console.Write(item.PersonID.ToString().PadRight(16, ' '));
Console.Write(item.SeqNumb.ToString().PadRight(16, ' '));
Console.Write(item.HistorySeqNumb.ToString().PadRight(16, ' '));
Console.WriteLine(item.HistoryCode.ToString().PadRight(16, ' '));
}
Console.ReadKey(true);
}

Create an algorithm to alternate list items based on item type

I have to create a List alternating the items type.
In my original list, i have this:
Group - Type
Group1 - 1
Group2 - 2
Group3 - 1
Group4 - 1
Group5 - 1
Group6 - 2
Group7 - 3
And I want to reorganize the items in this way:
Group1 - 1
Group2 - 2
Group7 - 3
Group3 - 1
Group6 - 2
Group7 - 3
Group4 - 1
Group2 - 2
Group7 - 3
Group5 - 1
Group6 - 2
Group7 - 3
Understand? Based on the count of the type with more items, i need to alternate with the other items.
This algorithm need to englobe n types.
I'm a little bit lost, anyone can help me? What kind of algorithm is this an how to do it?
I don't know how much can this be performing, but should be functional:
Create an array of array in which you put the element of group I in array[i][j], so you should have something like
array[0]: Group1, Group3, Group4, Group5
array[1]: Group2, Group2
array[2]: Group3
then save for each array an index that points to the element just added
Finally, cycle between your row of array and the "columns", making an index restart from 0 when you reach the end of a sub-array. It's not beautiful but should work fine.
Sorry for my english, I hope you'll understand :)
Here's my fist stab at this, though I'm sure this can be improved:
public static IEnumerable<TSource> AlternateGroups<TSource, TKey>(this IEnumerable<TSource> list, Func<TSource, TKey> keySelector)
{
var groups = list.GroupBy(keySelector).OrderByDescending(g => g.Count());
var largestGroup = groups.First();
var arrays = groups.Skip(1).Select(g => g.ToArray());
var index = new int[arrays.Count()];
foreach(var item in largestGroup)
{
yield return item;
var i = 0;
foreach(var a in arrays)
{
yield return a[index[i++]++ % a.Length];
}
}
}
This is written as an extension method, which means you can call it like this:
var input = new[]
{
new { Group = "Group1", Type = 1 },
new { Group = "Group2", Type = 2 },
new { Group = "Group3", Type = 1 },
new { Group = "Group4", Type = 1 },
new { Group = "Group5", Type = 1 },
new { Group = "Group6", Type = 2 },
new { Group = "Group7", Type = 3 },
};
var results = input.AlternateGroups(x => x.Type);
// Group1 1
// Group2 2
// Group7 3
// Group3 1
// Group6 2
// Group7 3
// Group4 1
// Group2 2
// Group7 3
// Group5 1
// Group6 2
// Group7 3
Something like the following works, although I am sure there are more elegant solutions.
EDIT:
As pointed out in the comments, I didn't realise that elements should be re-used from shorter lists to make up the length of the longest grouped list. Here's a revised version.
void Main()
{
var list = new List<Group>{
new Group { Name = "Group1", Type = 1 },
new Group { Name = "Group2", Type = 2 },
new Group { Name = "Group3", Type = 1 },
new Group { Name = "Group4", Type = 1 },
new Group { Name = "Group5", Type = 1 },
new Group { Name = "Group6", Type = 2 },
new Group { Name = "Group7", Type = 3 }
};
var groups = list.GroupBy(g => g.Type).ToList();
var groupCounts = groups.Select(g => g.Count()).ToArray();
var biggestGroup = groupCounts.Max();
var newList = new List<Group>();
for (int i = 0; i < biggestGroup; i++)
{
for (int j = 0; j < groups.Count; j++)
{
var element = groups[j].ElementAt(i % groupCounts[j]);
newList.Add(element);
}
}
// newList contains the ordered items
}
public class Group
{
public string Name { get;set; }
public int Type { get;set; }
}
outputs
Group1 1
Group2 2
Group7 3
Group3 1
Group6 2
Group7 3
Group4 1
Group2 2
Group7 3
Group5 1
Group6 2
Group7 3
You could make this into an extension method to return IEnumerable<Group> and yield return the items.

Categories

Resources