Сравнить 2 неупорядоченных набора записей в памяти

Ниже приведена таблица моей базы данных приложений, содержащая SQL-запросы, хранящиеся в таблице: QueryStorage

Id           Query           ConnectionString       Rdbms
1            select...        Data Source           Sql Server
2            select...        Data Source           Oracle

SQL-запросы в приведенной выше таблице обновляются через веб-службу, и нам не разрешено обновлять их выше, хотя мы можем добавить что-то поверх запроса примерно так:

Запрос хранится в таблице: select id as LinkedColumn, Amount as CompareColumn from Source

Тонкий запрос из моего приложения С#: select Q.LinkedColumn, Q.CompareColumn from (stored sql query) as Q

Я пытаюсь сравнить 2 неупорядоченных списка, как показано ниже:

Выполняется запрос для Id = 1(Sql server) из записей таблицы QueryStorage:

select Id as LinkedColumn,CompareColumn from Source

Список 1:

LinkedColumn     CompareColumn
1                100
2                200
3                300
4                400
5                500
6                600
7                700
8                800
9                900
10               1000

Запрос, выполненный для Id = 2(Oracle) из QueryStorage, выглядит следующим образом:

select Id as LinkedColumn,CompareColumn from Target

Список 2:

LinkedColumn       CompareColumn
10                 10
9                  20
8                  30
7                  40
6                  50
5                  60
4                  70
3                  80
2                  90
1                  5

Я хочу присоединиться к LinkedColumn from source to target, а затем выполнить сравнение на CompareColumn, которое должно дать мне следующий вывод:

SrcLinkedColumn      SrcCompareColumn     TgtLinkedColumn    TgtCompareColumn
    1                       100           1                  5
    2                       200           2                  90               

Логика:

var data = (from s in List1.AsEnumerable()
             join t in List2.AsEnumerable() on s.Field<string>("LinkedColumn") equals t.Field<string>("LinkedColumn")
             where s.Field<decimal>("CompareColumn") != t.Field<decimal>("CompareColumn")
                        select new
                        {
                            srcLinkedcol = s.Field<string>("LinkedColumn"),
                            srcCompareCol = s.Field<decimal>("CompareColumn"),
                            tgtLinkedCol = t.Field<string>("LinkedColumn"),
                            tgtCompareCol = t.Field<decimal>("CompareColumn")
                        }).ToList();

Будет миллионы записей из источника в цель, которые я хочу сравнить с такой большой проблемой, из out of memory exception, с которой мы сталкиваемся прямо сейчас, загружая все данные в память, а затем выполняем сравнение с предыдущим запросом linq.

Есть два решения, о которых я подумал:

1) Откройте 2 заказанных считывателя данных.

Плюсы:

    - No memory exception
    - Fast as there will be 1 to 1 comparision of LinkedColumn for List1 and 
      List2 records.

Минусы:

    - Order by is require on LinkedColumns and as i have no control over 
      query as because it is dumped by webservice in QueryStorage table so 
      user is explicitly require to submit query with order by on 
      LinkedColumn.
    - Wont work if order by on Linkedcolumn is not present.
    - Order by query have performance overhead so because of this user may not include order by on LinkedColumn in query.                               

2) Сравните фрагмент с помощью записей chunk, изменив запрос и добавив OffSet and FetchRowNext. Вот как я думаю об алгоритме:

введите описание изображения здесь

Но я все еще чувствую, что со вторым подходом я могу получить проблему с исключением памяти, потому что на некоторых шагах, где данные из источника и цели не совпадают, я буду хранить их внутри буфера (datatable или list и т.д.) для следующего сравнения chunk.

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

Примечание. Я не хочу использовать LinkedServer и SSIS.

Ответ 1

Ваше второе решение похоже на попытку заново изобрести External merge sort. Это действительный подход, если ваши данные не вписываются в память, но это слишком сложно, когда ваши данные вписываются в память.

24 миллиона строк с двумя номерами каждый (8 байт) составляют всего ~ 200 МБ памяти. Два набора данных - 400 МБ. Это ничего не стоит на современном настольном оборудовании.

Загрузите оба набора данных в память. В простых массивах. Сортировка массивов в памяти. Найдите разницу, сканируя одновременно отсортированные массивы. Для этого вам не нужен фантастический LINQ.

Вот псевдокод. Мы имеем Array1 и Array2. Поддерживайте две переменные, которые содержат текущий индекс каждого массива: Idx1 и Idx2. Переместите индексы вдоль массивов, ища места, где LinkedColumn соответствует.

Idx1 = 0; Idx2 = 0;
while (true)
{
    // scan arrays until both indexes point to the same LinkedColumn
    // here we assume that both arrays are sorted by LinkedColumn
    // and that values in LinkedColumn are unique

    while (Idx1 < Array1.Length && Idx2 < Array2.Length &&
        Array1[Idx1].LinkedColumn < Array2[Idx2].LinkedColumn)
    {
        // move along Array1
        Idx1++;
    }

    while (Idx1 < Array1.Length && Idx2 < Array2.Length &&
        Array1[Idx1].LinkedColumn > Array2[Idx2].LinkedColumn)
    {
        // move along Array2
        Idx2++;
    }

    // at this point both indexes point to the same LinkedColumn
    // or one or both of the arrays are over

    if (Idx1 >= Array1.Length || Idx2 >= Array2.Length)
    {
        break;
    }

    // compare the values
    if (Array1[Idx1].CompareColumn != Array2[Idx2].CompareColumn)
    {
        // TODO: output/save/print the difference
    }

    Idx1++; Idx2++;
}

ИЛИ

вы можете сбросить оба набора данных в базе данных по вашему выбору в двух таблицах T1 и T2, создать уникальный индекс в LinkedColumn в обеих таблицах и запустить этот запрос:

SELECT
     T1.LinkedColumn  AS SrcLinkedColumn
    ,T1.CompareColumn AS SrcCompareColumn
    ,T2.LinkedColumn  AS DstLinkedColumn
    ,T2.CompareColumn AS DstCompareColumn
FROM
    T1 INNER JOIN T2 ON T1.LinkedColumn = T2.LinkedColumn
WHERE
    T1.CompareColumn <> T2.CompareColumn
ORDER BY
    T1.LinkedColumn
;

Псевдокод выше выполняет то же самое объединение, что и сервер СУБД для этого запроса.

Ответ 2

Если я правильно понял, вы получаете данные таблиц в C# по некоторым типам, например DataReader. Если вы храните записи, которые извлекаются из веб-службы, а затем выполняете некоторые запросы, как упоминал @VladimirBaranov, для хранения требуется слишком долгое время, и это не разумно.

Я думаю, что лучшим решением для сравнения в этом случае является Двоичный поиск. Его порядок времени O(log n), а порядок памяти - O (1). Таким образом, это не требует дополнительной памяти.

Вы можете использовать двоичный поиск, как показано ниже:

1- Сортировка таблицы меньшего размера.

Порядок времени: если эта таблица имеет n элементов, в среднем время выполнения O(nlogn) (дополнительная информация).

Порядок памяти: O (1), потому что метод sort в С# На месте.

2- Сравнение других табличных записей с отсортированной таблицей с помощью двоичного поиска.

Порядок времени: если в другой таблице есть m записей, время выполнения O(m)*O(logn) = O(mlogn). (дополнительная информация)

Порядок памяти: ему не нужна дополнительная память.

В заключение я благодарю, что это лучшее решение для вашей проблемы, но я думаю, что это решение является хорошим решением, которое имеет O(mlogn) время выполнения, O(1) порядок памяти и реализуется только на С#, нет необходимости сохранять записи в база данных.

Ответ 3

Вероятно, вы потеряете память из-за фрагментации памяти.

  • Убедитесь, что вы используете эффективный способ чтения данных, таких как DataReader.
  • Чтобы избежать фрагментации, получите счетчик ваших записей и инициализируйте массив List 1 с этим счетчиком перед началом вставки в массив. Если вы не можете получить счет, вы можете увеличить коэффициент роста вашего списка или построить запрос, который будет содержать исходный запрос в качестве суб-запроса, например: SELECT COUNT (*) FROM ([ВАШ ОРИГИНАЛЬНЫЙ ВОПРОС ЗДЕСЬ])
  • Создайте массив struct, который содержит ваши поля или массив 2 измерений, для каждой строки не потребуется ссылка.
  • Кроме того, вы можете сохранить исходные данные (список 1) в формате назначения. Таким образом, у вас будет только один массив в памяти.

Вот псевдокод:

  • Получить количество строк списка List1:
  • Создайте массив с двумя размерами для хранения данных из списка1
  • Получить данные из списка 1 и добавить эти записи в ваш массив
  • Утилизируйте первый datareader
  • Используйте сортировку по месту для сортировки данных list1 (таким образом вы сможете использовать двоичный поиск, чтобы найти строку list1).
  • Получить данные из списка2
  • В строке списка List2 используется двоичный поиск для поиска соответствующей записи в списке1: добавьте значение сравнения List2 в соответствующую строку (помните, что в вашем массиве уже есть ваши 3 столбца). Вам нужно только установить значение сравнения list2).
  • Очистите результат, удалив строки, которые не совпадают. Я рекомендую вам очистить, используя тот же массив. С помощью двух индексов вы можете сканировать массив и перемещать запись в массиве.

Ответ 4

Извините, но я могу только дать вам идею:

Ограничения:

  • У цели есть миллионы записей.
  • Источник имеет миллионы записей
  • Ограниченная память

Идея:

  • Используйте как минимум 2 машины для этого
  • Один как [Source], а другой - как [Target], который имеет соответствующую службу
  • Отправляйте все записи n (chunk) с [Source] на [Target], вызывая службу, позволяя ей соответствовать записям и затем отправлять результат в [Source]

Бонус:

  1. Используйте более 1 машины [Target], и пусть машина [Source] использует их все в асинхронном сценарии и балансировке нагрузки.

Есть несколько похожих (или заранее) решений, которые вы можете найти, например:

  • [Target] и [Source] могут использовать общую память (кэш-сервер, например), чтобы сохранить результат
  • с использованием реального уровня балансировки нагрузки между ними для обработки нескольких запросов на несколько машин [Target]
  • с использованием уровня очереди/обмена сообщениями
  • даже используя несколько [Source] с несколькими компьютерами [Target]

Извините, но (по крайней мере, для меня) этот случай слишком велик, если использовать только 1 машину

Надеюсь, по крайней мере, это может дать вам идею

Ответ 5

Похоже, что LinkedColumn содержит уникальные значения для каждого источника, я бы загружал обе данные в две таблицы в локальной базе данных, создавал индекс на LinkedColumn и делал соединение.

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

LinkedColumn   Identifier   CompareColumn
1              S            100
2              S            200
3              S            300
                   ...
4              O            70
3              O            80
2              O            90

S означает SQL Server и O для Oracle. Сортировка файла (возможно, вы можете запустить сортировку ОС или другую внешнюю сортировку). Прочитайте отсортированный файл по строкам. Первая строка должна быть сохранена, поэтому вы сравните ее LinkedColumn со второй строкой для сопоставления, тем самым собирая их или сохраняя вторую и отбрасывая первую, чтобы найти соответствие на третьем и т.д.

Надеюсь, что это поможет.

Ответ 6

Как насчет удаления элементов, которые соответствуют? Что-то вроде:

List1.RemoveAll(x => List2.Any(y => y.LinkedColumn == x.LinkedColumn && y.CompareColumn == x.CompareColumn))

Ответ 7

Просто оставьте это задание SQL Server.

Пусть он обрабатывает WHERE CLAUSE

T1.CompareColumn <> T2.CompareColumn