Удалить дубликаты по полю из одной таблицы, используя другую с помощью LINQ - PullRequest
0 голосов
/ 14 февраля 2010

Мне нужно оставить в DataTable только записи с датами, которых в настоящее время нет в базе данных.

Итак, я прочитал все существующие даты, используя хранимую процедуру (это правильно?):

SELECT DISTINCT CAST(S.[date] AS DATE) -- original date is DATETIME2(0)
FROM ...
WHERE ...

и загрузите его в DataTable:

var tableDate = new DataTable();
new SqlDataAdapter(command).Fill(tableDate);

Как убрать теперь из другой таблицы все ненужные строки? Я думаю LINQ может помочь, но я не знаю, как ..

Ответы [ 4 ]

2 голосов
/ 28 февраля 2010

Я смотрю на ваш ответ, который, по вашему мнению, работает, и вы просто хотите узнать, как это сделать в «одном запросе LINQ». Помните, что все эти запросы имеют отложенное выполнение, поэтому следующие два запроса функционально эквивалентны:

var q =
    from d in dates
    select d.Field<DateTime>("date");
return
    (from r in records
     where !q.Contains(r.Field<DateTime>("date"))
     select r).CopyToDataTable();

И

return
    (from r in records
     where !dates
         .Select(d => d.Field<DateTime>("date"))
         .Contains(r.Field<DateTime>("date"))
     select r).CopyToDataTable();

Вторая версия намного сложнее для чтения, но, тем не менее, это "один запрос".


Сказав это, ни один из этих примеров действительно не соответствует названию вашего вопроса, что говорит о том, что вы пытаетесь удалить дублирующиеся строки . Если это действительно то, что вы пытаетесь сделать, вот метод, который сделает это:

static DataTable RemoveDuplicates(DataTable dt)
{
    return
        (from row in dt.Rows.OfType<DataRow>()
         group row by row.Field<string>("date") into g
         select g
            .OrderBy(r => r.Field<int>("ID"))
            .First()).CopyToDataTable();
}

Если вас не волнует , который удаляет дубликаты, вы можете просто удалить строку OrderBy. Вы можете проверить это следующим образом:

static void Main(string[] args)
{
    using (DataTable original = CreateSampleTable())
    using (DataTable filtered = RemoveDuplicates(original))
    {
        DumpTable(filtered);
    }
    Console.ReadKey();
}

static DataTable CreateSampleTable()
{
    DataTable dt = new DataTable();
    dt.Columns.Add("ID", typeof(int));
    dt.Columns.Add("Code", typeof(string));
    dt.Columns.Add("Name", typeof(string));
    dt.Rows.Add(1, "123", "Alice");
    dt.Rows.Add(2, "456", "Bob");
    dt.Rows.Add(3, "456", "Chris");
    dt.Rows.Add(4, "789", "Dave");
    dt.Rows.Add(5, "123", "Elen");
    dt.Rows.Add(6, "123", "Frank");
    return dt;
}

static void DumpTable(DataTable dt)
{
    foreach (DataRow row in dt.Rows)
    {
        Console.WriteLine("{0},{1},{2}",
            row.Field<int>("ID"),
            row.Field<string>("Code"),
            row.Field<string>("Name"));
    }
}

(просто замените «date» на «Code» в методе RemoveDuplicates для этого примера)

Надеюсь, один из них ответит на ваш вопрос. В противном случае, я думаю, вам придется более четко разобраться с вашими требованиями.

1 голос
/ 01 марта 2010

Как я понимаю, проблема заключается в том, что вы пытаетесь восстановить данные, поступающие из какого-либо импорта. Возможно, вам не нужно делать это с помощью LINQ. Несмотря на то, что заголовок публикации предлагает LINQ, вы позже задаетесь вопросом, может ли LINQ быть лучшим решением, и, учитывая то, что мы знаем, я думаю, вы могли бы сделать это с помощью одного оператора Insert.

Во-первых, я бы предложил массовое копирование данных во временную папку в БД (если вы этого еще не сделали), например:

Create Table TempBulkCopyData
(
    Id int not null identity(1,1)
    , Date DateTime2 not null
    , ...
)

Одним из преимуществ массового копирования во временное местоположение является то, что вы можете добавлять индексы и тому подобное для ускорения процесса очистки. Чтобы де-дуплицировать данные, вы можете выполнить запрос следующим образом:

Insert DestinationData(...)
Select ...
From BulkCopyData As BCD
Where Id = (
            Select Min(BCD2.[Id])
            From BulkCopyData As BCD2
            Where Cast(BCD2.[Date] As Date) = Cast(BCD.[Date] As Date)
            )

Или

Insert DestinationData(...)
Select ...
From BulkCopyData As BCD
Where Id = (
            Select Min(BCD2.[Id])
            From BulkCopyData As BCD2
            Where DateDiff(d, BCD.[Date], BCD2.[Date]) = 0
            )

Это потянет первую найденную дату (ту, у которой самый низкий идентификатор). Это, очевидно, несколько произвольно, но для большей уточнения нам нужно больше узнать о структуре данных и требованиях.

1 голос
/ 28 февраля 2010

Ваш оператор SQL выглядит хорошо. Насколько я понимаю, вы применяете значение времени по умолчанию, начиная с полуночи. Поэтому даты в другой сравниваемой таблице также должны соответствовать этому формату, чтобы сравнивать даты с нейтральным временем. Если это не так, вы все равно можете использовать приведенный ниже код, но вы должны добавить свойство .Date везде, где есть ссылка на поле строки tableResult. Также я использовал Field<DateTime>(0), но в зависимости от вашего запроса и вашего предыдущего примера вам может понадобиться Field<DateTime>("date").

Нет необходимости в пользовательском компараторе. Чтобы объединить ваши запросы LINQ в один запрос, вы можете просто использовать ключевое слово let и передавать промежуточный результат через запрос и ссылаться на него.

Дайте попробовать:

var tableDate = new DataTable();
new SqlDataAdapter(command).Fill(tableDate);

// this is the other table that has other dates, so populate as needed
var tableResult = new DataTable();

var newTable =
    (from row in tableResult.AsEnumerable()
    let uniqueRows = tableResult.AsEnumerable().Select(r => r.Field<DateTime>(0))
                                .Except(tableDate.AsEnumerable().Select(r => r.Field<DateTime>(0)))
    where uniqueRows.Contains(row.Field<DateTime>(0))
    select row).CopyToDataTable();

В точечной нотации запрос будет:

var newTable = tableResult.AsEnumerable()
    .Select(row => new
    {
        Row = row,
        UniqueRows =  tableResult.AsEnumerable()
                                 .Select(r => r.Field<DateTime>(0))
                                 .Except(tableDate.AsEnumerable().Select(r => r.Field<DateTime>(0)))
    })
    .Where(item => item.UniqueRows.Contains(item.Row.Field<DateTime>(0)))
    .Select(item => item.Row)
    .CopyToDataTable();

Вместо tableResult.AsEnumerable() вы можете использовать tableResult.Rows.Cast<DataRow>() или tableResult.Rows.OfType<DataRow>(). Результаты одинаковы для всех этих подходов.

Если вы хотите удалить дубликаты из существующей таблицы (а не скопировать ее в новую таблицу), вы можете удалить элементы, возвращенные методом Intersect из таблицы:

var commonDates = tableDate.AsEnumerable().Select(row => row.Field<DateTime>(0))
                           .Intersect(tableResult.AsEnumerable().Select(row => row.Field<DateTime>(0)));

for (int index = tableResult.Rows.Count - 1; index >= 0; index--)
{
    if (commonDates.Contains(tableResult.Rows[index].Field<DateTime>(0)))
    {
        tableResult.Rows.RemoveAt(index);
    }
}
1 голос
/ 18 февраля 2010

Вы можете использовать Except()

return records.Except(dates);

ОБНОВЛЕНИЕ: Если ваш DataTable имеет типизированные поля, он должен выглядеть следующим образом:

var excluded = arbDates.Rows.OfType<System.Data.DataRow>().Select(a => a[0]) .Except(excDates.Rows.OfType<System.Data.DataRow>().Select(e => e[0]));

в противном случае вы можете разыграть его:

var excluded = arbDates.Rows.OfType<System.Data.DataRow>() .Select(a => Convert.ToDateTime(a[0].ToString())) .Except( excDates.Rows.OfType<System.Data.DataRow>() .Select(e => Convert.ToDateTime(e[0].ToString())));

...