Получить одну строку из повторяющихся столбцов на основе другого столбца

#c# #sql #linq

#c# #sql #linq

Вопрос:

Допустим, у меня есть эта таблица / IQueryable :

  ------ ------ ------ ------------ ------------- 
| col1 | col2 | col3 | grouperCol | selectorCol |
 ------ ------ ------ ------------ ------------- 
|    1 | John | Doe  | mail1      |             |
|    1 | John | Doe  | mail2      |           1 |
|    1 | John | Doe  | mail3_x    |             |
|    2 | Bob  | Ross | mail1      |           1 |
|    2 | Bob  | Ross | mail2_x    |             |
|    2 | Bob  | Ross | mail3_x    |             |
|    3 | Jane | Doe  | mail1      |             |
|    3 | Jane | Doe  | mail2      |             |
|    3 | Jane | Doe  | mail3      |             |
 ------ ------ ------ ------------ ------------- 
  

И я хочу получить этот результат:

  ------ ------ ------ ------------ ------------- 
| col1 | col2 | col3 | grouperCol | selectorCol |
 ------ ------ ------ ------------ ------------- 
|    1 | John | Doe  | mail2      |           1 |
|    2 | Bob  | Ross | mail1      |           1 |
|    3 | Jane | Doe  | mail1      |             |
 ------ ------ ------ ------------ ------------- 
  

В принципе, мне нужно сохранить одну строку, выбрав строки, в которых selectorCol не равен null, ИЛИ первую строку.

Как мне это сделать на c #?

Вероятно, мне нужно будет сделать что-то вроде

 var filtered =  context.table.GroupBy(x => x.col1).Where(... 
  

Но тогда я уже застрял, чтобы написать это коротко.

Я мог бы создать новый список с помощью foreach или чего-то еще, но я думаю, это можно сделать с помощью 1 единственной строки?

Спасибо!

Комментарии:

1. Вы ищете ответ на SQL или Linq?

2. var filtered = context.table. Где(x=> x.selectorCol != null). GroupBy(x => x.col1), таким образом, вы можете получить все регистры, которые не равны null, я не понял ваше условие OR..

3. Используйте OrderBy в каждой группе, чтобы отсортировать ненулевое selectorCol значение как первое, затем выберите первую строку в каждой группе.

Ответ №1:

Если вы хотите сделать это только на основе col1 , то:

 var result = context.table.GroupBy(x => x.col1)
    .Select(g => g.FirstOrDefault(x =>selectorCol != null)??g.First());
  

для firstname и lastname ( col1 , col2 );

 var result = context.table.GroupBy(x => {x.col1, x.col2})
    .Select(g => g.FirstOrDefault(x =>selectorCol != null)??g.First());
  

Ответ №2:

В принципе, мне нужно сохранить одну строку, выбрав строки, в которых selectorCol не равен null, ИЛИ первую строку.

Вы явно этого не сказали, но я предполагаю, что если две строки имеют одинаковое значение Col1 , то они также имеют одинаковое значение Col2 и Col3

Требование Учитывая последовательность MyRows , создайте результирующую последовательность, созданную из групп MyRows с одинаковым значением для Col1 . Из каждой группы я хочу, чтобы первый элемент имел ненулевое значение SelectorCol

Если вы точно напишете требование, это не кажется очень сложным. Единственная проблема в том, что является первым элементом группы? Это тот, у которого самый низкий индекс?

Поскольку GroupBy ничего не гарантирует в отношении сохранения исходного порядка, мы должны помнить индекс исходных элементов.

  • Выберите там, где вы помните индекс исходного элемента
  • Затем создайте группы элементов с одинаковым значением для Col1
  • Из каждой группы сохраняйте элементы, которые имеют ненулевое значение для SelectorCol
  • Затем возьмите ту, у которой наименьший индекс.

.

 // first remember the original index
var result = myRows.Select( (row, index) => new
{
    Index = index
    Row = row,
}
// Then make groups of rows with same value for Col1
.GroupBy(selectResult => selectResult.Row.Col1,

// Parameter resultSelector: get the key of each group (= common Col1 value)
// and all rows that have this Col1 value
// keep only the groupElements that have a non-null value for SelectorCol
(col1, rowsWithThisCol1) => rows.WithThisCol1
     .Where(groupElement => groupElement.Row.SelectorCol != null)

     // from the remaining rows, keep the one with the lowest index
     .OrderBy(groupElement => groupElement.Index)

     // we don't need the Index anymore, select only the Row
     .Select(groupElement => groupElement.Row)

     // and keep the first:
     .FirstOrDefault();
  

Хотя это работает, упорядочивать все элементы группы немного бесполезно, если вам нужен только один с наименьшим индексом. Используйте Aggregate, если вы хотите перечислить только один раз. Итак, вместо OrderBy:

 .Aggregate((groupElementWithLowestIndex, groupElement) =>
    // if the index of groupElement is lower,
    // then that element becomes the one with the lowest index

    (groupElement.Index < groupElementWithLowestIndex.Index) ?
     groupElement : groupElementWithLowestIndex)

// result: the one and only groupElement with the lowest index
// note: you are certain that no group is empty! So there is always one with lowest index
// get rid of the index, keep only the Row
.Row;
  

Ответ №3:

Вот ваш однострочный:

 .GroupBy(x => x.col1, (k, g) => g.FirstOrDefault(x => x.selectorCol == 1) ?? g.FirstOrDefault())
  

Но мне любопытно, какой запрос к БД это сгенерирует. Вероятно, сокращение группы будет выполнено в памяти.

Редактировать: По-видимому, приведенный выше linq генерирует запрос с подзапросами. Было бы лучше разделить ее на 2 метода, чтобы избежать проблем с производительностью:

 .OrderBy(x => x.selectorCol == null)
.GroupBy(x => x.col1, (k, g) => g.FirstOrDefault())
  

Комментарии:

1. В LINQ to SQL это сгенерировало очень странный запрос, в котором был СЛУЧАЙ с двумя похожими условиями, одним с NOT EXISTS и одним с NOT NOT EXISTS , а затем пришлось извлекать ответы с помощью дополнительных запросов, по одному на группу.

2. @NetMage Ouf, подзапросы. Я удивлен, что linq2sql удалось преобразовать эту часть.