#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 удалось преобразовать эту часть.