简体   繁体   中英

How to select distinct employees with LINQ based on ID from an employee collection where employee 's salary is 2nd highest?

Look at the below example. Here Employee ID =2 aka Steve has 3 duplicates. I want only one record of each ID. I want to pick the record which has 2nd highest salary. So in case of Steve the choice will be either one of the two Steves hat have 160 as Salary.

       public class Employee
        {
            public int ID { get; set; }
            public string Name { get; set; }
            public int Salary { get; set; }

            public static List<Employee> GetAllEmployees()
            {
                return new List<Employee>()
        {
            new Employee { ID = 1, Name = "Mark", Salary = 100 },
            new Employee { ID = 2, Name = "Steve", Salary = 150 },
            new Employee { ID = 2, Name = "Steve", Salary = 160 },
            new Employee { ID = 2, Name = "Steve", Salary = 160 },
            new Employee { ID = 2, Name = "Steve", Salary = 165 },
            new Employee { ID = 3, Name = "Ben", Salary = 140 }                

        };
            }
        }

Desired Output:

1 Mark 100
1 Steve 160 //2nd highest salary of steve( there are two such steves so pick any)
1 Mark 100
1 Ben 140 

I know how to get distinct records based on a property:

var result = Employee.GetAllEmployees().GroupBy(x=>x.ID).Distinct();

But I am lost on the other part.

Please note I am looking for LINQ lambda/extension syntax answers only. Thanks!

One way is to use Select after GroupBy . This will transform each group into an employee.

Employee.GetAllEmployees().GroupBy(x=>x.ID).Select(x => FindSecondHighest(x));

where FindSecondHighest should be something like this:

private static Employee FindSecondHighest(IEnumerable<Employee> employees) {
    var list = employees.ToList();
    if (list.Count == 1) { return list[0]; }
    return list.OrderByDescending(x => x.Salary).Skip(1).First();
}

You can rewrite the method to a lambda if you like, but I feel like it's more readable this way.

EDIT:

I realised that this doesn't actually get the second highest salary if there are two highest salaries. To actually get the second highest salary, you can use a second GroupBy :

private static Employee FindSecondHighest(IEnumerable<Employee> employees) {
    var list = employees.ToList();
    if (list.Count == 1) { return list[0]; }
    return list.GroupBy(x => x.Salary).OrderByDescending(x => x.Key).Skip(1).First();
}

First of all, lose the Distinct() . The groupings are by definition unique.

As per your requirement, you need to order the results of your groupings by salary (descending) and take the second one. Do this only when the grouping has more than 1 item. Resulting from that, this should work:

    var result = Employee.GetAllEmployees()
        .GroupBy(x => x.ID)
        .Select(x => (x.Count() > 1 ? x.GroupBy(y => y.Salary).Skip(1).Select(y => y.First()) : x).First()
        );

Edit: updated my answer based on comments, we need to group by salary and then skip 1 to mitigate the situation in which the second record also has the top salary value.

The following should work:

Employee.GetAllEmployees().Where(x => {
    recordsWithID = Employee.GetAllEmployees().Where(y => y.ID == x.ID).OrderByDescending(y => y.Salary);

    recordToReturn = recordsWithID.Count > 1 ? recordsWithID.Skip(1).First() : recordsWithID.First();

    return x.ID == recordToReturn.ID;
});

First, in the main predicate, we select all users with x's ID. Then, we get the record with the second highest Salary, if there are more than one records with that ID, otherwise, we just select the only record with that ID. Then, if for x's ID group, x is the actually desired record (so x has the only one with its ID or x has the second highest salary among records with x's ID), x is returned, otherwise it is not.

I cannot test this currently as I am not in front of a PC, but this should give you an idea.

You can try something like this using .GroupBy and .Select :

static void Main(string[] args)
{
    List<Employee> secondHighestSalaryPersons = Employee.GetAllEmployees()
         .GroupBy(x => x.Name)
         .Select(x =>
         {
             var group = x.ToList();

             if (group.Count > 1)
                 return group.OrderByDescending(y => y.Salary).Skip(1).FirstOrDefault();
             else
                 return group.FirstOrDefault();
         })
         .ToList();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM