2011-08-22 16 views
7

Tengo que implementar un algoritmo en los datos que (por buenas razones) se almacenan dentro del servidor SQL. El algoritmo no se ajusta muy bien a SQL, por lo que me gustaría implementarlo como una función o procedimiento de CLR. Esto es lo que quiero hacer:"cursor like" lectura dentro de un procedimiento/función CLR

  • ejecutar varias consultas (por lo general 20-50, pero hasta 100-200) todos los cuales tienen la forma select a,b,... from some_table order by xyz. Hay un índice que se ajusta a esa consulta, por lo que el resultado debería estar disponible más o menos sin ningún cálculo.

  • Consumir los resultados paso a paso. El paso exacto depende de los resultados, por lo que no es exactamente predecible.

  • Agregue algunos resultados al repasar los resultados. Solo consumiré las primeras partes de los resultados, pero no puedo predecir cuánto necesitaré. El criterio de detención depende de algún umbral dentro del algoritmo.

Mi idea era abrir varias SqlDataReader, pero tengo dos problemas con esta solución:

  • Puede tener sólo un SqlDataReader por conexión y dentro de un método CLR sólo tengo una conexión - hasta donde yo entiendo.

  • No sé cómo decirle a SqlDataReader cómo leer datos en trozos. No pude encontrar documentación sobre cómo debe comportarse SqlDataReader. Por lo que yo entiendo, está preparando todo el conjunto de resultados y cargaría todo el resultado en la memoria. Incluso si consumiera solo una pequeña parte de eso.

¿Alguna pista sobre cómo solucionar eso como método CLR? ¿O hay una interfaz de nivel más bajo para el servidor SQL que es más adecuada para mi problema?

Actualizar: que debería haber hecho dos puntos más explícitos:

  1. Estoy hablando de grandes conjuntos de datos, por lo que una consulta podría resultar en 1 millón de registros, pero mi algoritmo sólo el consumiría primeros 100-200 unos. Pero como dije antes: no sé el número exacto de antemano.

  2. Soy consciente de que SQL puede no ser la mejor opción para ese tipo de algoritmo. Pero debido a otras limitaciones tiene que ser un servidor SQL. Así que estoy buscando la mejor solución posible.

+0

Solo escribo una pequeña aplicación C# si un procedimiento almacenado no funciona. –

+2

Sin saber lo que estás haciendo exactamente, me estremezco cuando hablas de iterar a través de los datos paso a paso. ¿Has intentado encontrar una forma de manejar esto? Los métodos basados ​​en conjuntos pueden ser bastante sofisticados y años luz más rápidos que fila por agonía-fila. Dicho esto, sí, puede haber algunas calcuaciones muy complejas que deben manejarse una fila a la vez, simplemente no me he encontrado con muchas a través de los años. – HLGEM

+0

Lo he implementado usando sets, pero tuve que "usarlos de alguna manera". Probablemente me habría escrito su respuesta, pero el código basado en el conjunto es realmente incómodo y se está convirtiendo en una pesadilla para mantener y depurar. Normalmente no me gustan las operaciones del cursor, pero como sé que voy a recorrer un índice en orden, creo que es la mejor opción. La única alternativa podría ser DSL, que genera código SQL. – Achim

Respuesta

4

SqlDataReader no lee todo el conjunto de datos, que está confundiendo con la clase Dataset. Se lee fila por fila, como se llama al método .Read(). Si un cliente no consume el conjunto de resultados, el servidor suspenderá la ejecución de la consulta porque no tiene espacio para escribir el resultado (las filas seleccionadas). La ejecución se reanudará a medida que el cliente consuma más filas (se está llamando a SqlDataReader.Read). Incluso hay un indicador de comportamiento de comando especial SequentialAccess que indica a ADO.Net que no precargue en la memoria toda la fila, lo que es útil para acceder a columnas BLOB grandes de forma continua (consulte Download and Upload images from SQL Server via ASP.Net MVC para obtener un ejemplo práctico).

Puede tener varios conjuntos de resultados activos (SqlDataReader) activos en una sola conexión cuando MARS está activo. Sin embargo, MARS es incompatible con las conexiones de contexto SQLCLR.

Para que pueda crear un CLR streaming TVF para hacer algo de lo que necesita en CLR, pero solo si tiene un único origen de consulta SQL. Múltiples consultas requeriría que abandone la conexión de contexto y use isntead una conexión completa, es decir. conectarse de nuevo a la misma instancia en un bucle invertido, y esto permitiría a MARS y por lo tanto consumir múltiples resultados. Pero el bucle invertido tiene sus propios problemas, ya que rompe los límites de transacción que tiene de la conexión de contexto.Específicamente con una conexión de bucle invertido, su TVF no podrá leer los cambios realizados por la misma transacción que llamó al TVF, porque es una transacción diferente en una conexión diferente.

+0

Remus: ¿No puede tener la conexión de bucle de retroceso unir la transacción ya en curso desde el contexto? ¿O eso todavía se autobloquea? – RBarryYoung

+0

@RBarryYoung Técnicamente puede, usando 'sp_getbindtoken' /' sp_bindsession'. No recomendado en absoluto. –

0

Los cursores son una función solo SQL. Si quisiera leer fragmentos de datos a la vez, se requeriría algún tipo de paginación para que solo se devolviera una cierta cantidad de registros. Si se utiliza Linq,

.Skip(Skip) 


.Take(PageSize) 

Se pueden utilizar saltos y tomas para limitar los resultados obtenidos.

Simplemente puede iterar sobre el DataReader haciendo algo como esto:

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

Esto sería iterar sobre el resultado de uno en uno, de forma similar a un cursor en SQL Server.

Para varios conjuntos de registros a la vez, tratar MARS (si SQL Server)

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

+0

Cargar/generar un conjunto de resultados en la memoria no es una opción. Y MARS no está disponible dentro de un procedimiento CLR, por lo que pude deducir. ¿Tienes un ejemplo de trabajo? – Achim

+1

Un IDataReader en un comando de SQL como "SELECT ... FROM ..." en realidad abre un cursor de avance de solo lectura. ** No ** carga todo el conjunto de resultados en la memoria. Cada registro se lee cuando se ejecuta el método .Read(). Esta es la principal diferencia con algo así como un DataSet que carga todo en la memoria. Por supuesto, podría depender del proveedor subyacente de ADO.NET, pero con SQL Server, así es como funciona. Entonces es una buena solución. Y el método Skip() de BTW Linq hace exactamente eso. –

+0

@Simon: he actualizado mi respuesta en función de su comentario. Gracias. –

1

SQL está diseñado para trabajar en contra de grandes conjuntos de datos, y es extremadamente poderoso. Con la lógica basada en conjuntos, a menudo es innecesario iterar sobre los datos para realizar operaciones, y hay una serie de formas integradas para hacerlo dentro del propio SQL.

1) Aparato de escritura lógica basada actualizar los datos sin cursores

2) utilizar deterministas funciones definidas por el usuario con el sistema de lógica basada (se puede hacer esto con la SqlFunction attribute in CLR code). No determinista tendrá el efecto de convertir la consulta en un cursor internamente, significa que el resultado de valor no siempre es el mismo con la misma entrada.

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

3) utilice los cursores como último recurso. Esta es una forma poderosa de ejecutar la lógica por fila en la base de datos, pero tiene un impacto en el rendimiento. Parece que desde this article CLR puede ejecutar cursores SQL (gracias Martin).

Vi su comentario de que la complejidad del uso de la lógica basada en conjuntos era demasiado. ¿Puede dar un ejemplo? Hay muchas formas de SQL para resolver problemas complejos: CTE, Vistas, particiones, etc.

Por supuesto, es posible que tenga razón en su enfoque, y no sé lo que está tratando de hacer, pero mi instinto me dice: apalancamiento las herramientas de SQL. Generar múltiples lectores no es la forma correcta de abordar la implementación de la base de datos. Puede ser que necesite varios hilos que llamen a un SP para ejecutar un procesamiento simultáneo, pero no haga esto dentro del CLR.

Para responder a su pregunta, con las implementaciones de CLR (y IDataReader) realmente no necesita colocar los resultados en páginas en fragmentos porque no está cargando datos en la memoria ni transportando datos a través de la red. IDataReader le da acceso a la secuencia de datos fila por fila. Por los sonidos, su algoritmo determina la cantidad de registros que necesitan actualización, por lo que cuando esto sucede simplemente deje de llamar al Read() y termine en ese punto.

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

Un posible ejemplo podría ser una consulta de tipo de total acumulado. Ver [mi respuesta aquí] (http://stackoverflow.com/questions/6877507/when-are-tsql-cursors-the-best-or-only-option/6879770#6879770) para ver un gráfico que muestra cómo los cursores salen rápidamente soluciones "basadas en conjuntos". Es posible implementar un "cursor más rápido" usando CLR como Adam Machanic analiza [aquí.] (Http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/running-sums-yet-again-sqlclr -saves-the-day.aspx) –

+0

Enlaces interesantes gracias, por supuesto, depende de los datos/algoritmo que es más rápido a la derecha (cursor vs. conjunto basado)? Los resultados de Adam Machanic se ven muy interesantes, me encantaría ver un poco más de benchmark en diferentes conjuntos de datos. – TheCodeKing