чтение «как курсор» внутри процедуры / функции CLR - PullRequest
7 голосов
/ 23 августа 2011

Я должен реализовать алгоритм для данных, которые (по уважительным причинам) хранятся на сервере SQL. Алгоритм не очень хорошо подходит для SQL, поэтому я хотел бы реализовать его как функцию или процедуру CLR. Вот что я хочу сделать:

  • Выполнить несколько запросов (обычно 20-50, но не более 100-200), которые имеют форму select a,b,... from some_table order by xyz. Существует индекс, который соответствует этому запросу, поэтому результат должен быть доступен более или менее без каких-либо вычислений.

  • Используйте результаты шаг за шагом. Точный шаг зависит от результатов, поэтому он не совсем предсказуем.

  • Соберите некоторый результат, перешагнув через результаты. Я буду использовать только первые части результатов, но не могу предсказать, сколько мне понадобится. Критерий остановки зависит от некоторого порога внутри алгоритма.

Моя идея состояла в том, чтобы открыть несколько SqlDataReader, но у меня есть две проблемы с этим решением:

  • У вас может быть только один SqlDataReader для каждого соединения, и внутри метода CLR у меня только одно соединение - насколько я понимаю.

  • Я не знаю, как сказать SqlDataReader, как читать данные кусками. Я не смог найти документацию о том, как SqlDataReader должен вести себя. Насколько я понимаю, он готовит весь набор результатов и загружает весь результат в память. Даже если бы я потреблял только небольшую его часть.

Любой намек, как решить это как метод CLR? Или есть более низкоуровневый интерфейс для SQL-сервера, который больше подходит для моей проблемы?

Обновление: Я должен был сделать два замечания более явными:

  1. Я говорю о больших наборах данных, поэтому запрос может привести к записи в 1 миллион, но мой алгоритм будет использовать только первые 100-200 записей. Но, как я уже говорил, я не знаю точного числа заранее.

  2. Я знаю, что SQL может быть не лучшим выбором для такого рода алгоритма. Но из-за других ограничений это должен быть сервер SQL. Поэтому я ищу лучшее из возможных решений.

Ответы [ 3 ]

4 голосов
/ 07 сентября 2011

SqlDataReader не читает весь набор данных, вы путаете его с классом Dataset. Он читает строку за строкой, так как вызывается метод .Read(). Если клиент не использует набор результатов, сервер приостанавливает выполнение запроса, потому что у него нет места для записи вывода (выбранные строки). Выполнение возобновится, когда клиент будет использовать больше строк (вызывается SqlDataReader.Read). Существует даже специальный флаг поведения команды SequentialAccess, который указывает ADO.Net не загружать в память всю строку целиком, что полезно для доступа к большим столбцам BLOB в потоковом режиме (см. Загрузить и загрузка изображений с SQL Server через ASP.Net MVC для практического примера).

Вы можете иметь несколько активных наборов результатов (SqlDataReader) активными для одного соединения, когда MARS активен. Однако MARS несовместим с контекстными соединениями SQLCLR.

Таким образом, вы можете создать CLR для потоковой передачи TVF , чтобы делать то, что вам нужно в CLR, но только если у вас есть один источник SQL-запросов. Многократные запросы потребовали бы от вас отказаться от контекстного соединения и использовать его вместо полноценного соединения, т.е. подключитесь обратно к одному и тому же экземпляру в обратной петле, и это позволит MARS и, следовательно, потреблять несколько наборов результатов. Но у петлевого управления есть свои проблемы, так как он нарушает границы транзакций, которые у вас есть из контекстного соединения. В частности, с помощью петлевого соединения ваш TVF не сможет считывать изменения, внесенные той же транзакцией, которая назвала TVF, поскольку это транзакция другая для другого соединения.

1 голос
/ 04 сентября 2011

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

1) запись на основе логики для обновления данных без курсоров

2) использовать детерминированные пользовательские функции с установленной логикой (это можно сделать с помощью атрибута SqlFunction в коде CLR ). Недетерминированный будет иметь эффект превращения запроса в курсор внутри, это означает, что вывод значения не всегда одинаков при заданном входе.

[SqlFunction(IsDeterministic = true, IsPrecise = true)]
public static int algorithm(int value1, int value2)
{
    int value3 = ... ;
    return value3;
}

3) использовать курсоры в качестве крайней меры. Это мощный способ выполнения логики для каждой строки в базе данных, но он влияет на производительность. Как следует из этой статьи CLR может выполнять SQL-курсоры (спасибо Мартину).

Я видел ваш комментарий о том, что сложность использования логики на основе множеств слишком велика. Можете ли вы привести пример? Существует множество способов SQL для решения сложных задач - CTE, Views, разбиение и т. Д.

Конечно, вы вполне можете быть правы в своем подходе, и я не знаю, что вы пытаетесь сделать, но моя интуиция говорит об использовании инструментов SQL. Создание нескольких читателей не является правильным подходом к реализации базы данных. Вполне может быть, что вам нужно несколько потоков, вызывающих SP, чтобы запустить параллельную обработку, но не делайте этого внутри CLR.

Чтобы ответить на ваш вопрос, с реализациями CLR (и IDataReader) вам на самом деле не нужно распечатывать результаты по частям, потому что вы не загружаете данные в память или не переносите данные по сети. IDataReader дает вам доступ к потоку данных построчно. Судя по звукам, ваш алгоритм определяет количество записей, которые необходимо обновить, поэтому, когда это произойдет, просто прекратите звонить Read() и закончите в этой точке.

SqlMetaData[] columns = new SqlMetaData[3];
columns[0] = new SqlMetaData("Value1", SqlDbType.Int);
columns[1] = new SqlMetaData("Value2", SqlDbType.Int);
columns[2] = new SqlMetaData("Value3", SqlDbType.Int);

SqlDataRecord record = new SqlDataRecord(columns);
SqlContext.Pipe.SendResultsStart(record);

SqlDataReader reader = comm.ExecuteReader();

bool flag = true;

while (reader.Read() && flag)
{
    int value1 = Convert.ToInt32(reader[0]);
    int value2 = Convert.ToInt32(reader[1]);

    // some algorithm 
    int newValue = ...;

    reader.SetInt32(3, newValue);        

    SqlContext.Pipe.SendResultsRow(record);

    // keep going?
    flag = newValue < 100;
 }
0 голосов
/ 23 августа 2011

Курсоры являются функцией только SQL. Если вы хотите читать порции данных за раз, потребуется какой-то вид подкачки, чтобы было возвращено только определенное количество записей. При использовании Linq,

.Skip(Skip)


.Take(PageSize)

Пропуски и взятия могут использоваться для ограничения возвращаемых результатов.

Вы можете просто перебрать DataReader, выполнив что-то вроде этого:

using (IDataReader reader = Command.ExecuteReader())
{
    while (reader.Read())
    {
        //Do something with this record
    }
}

Это будет повторять результаты по одному, аналогично курсору в SQL Server.

Для нескольких наборов записей одновременно попробуйте MARS (если SQL Server)

http://msdn.microsoft.com/en-us/library/ms131686.aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...