none
Como hacer para evitar usar un cursor?

    Question

  • Hola,

    a ver si se explicarme con claridad. Estoy haciendo un procedimiento donde creo una tabla temporal, después selecciono unos registros de una tabla y los inserto en la temporal, hasta aquí todo normal.

    La cosa es que ahora he de seleccionar otros registros de otra tabla, y, si el código coincide con uno de los que ya están en la tabla temporal, tengo que hacer un update de dicho registro, y si el código no coincide con ninguno de los de la tabla temporal, tengo que hacer un insert con este registro que no coincide.

    A mi la única forma que se me ocurre de hacer esto es con un cursor, pero en diversos post de este foro y artículos que he leído, dicen que hay que huir de los cursores y hacerlo de otra forma.

    Bien, pues si alguien me puede iluminar sobre otra forma de hacer esto, se lo agradecería.

    Tuesday, August 23, 2011 8:30 AM

Answers

  • Weatherby, puedo entender que el proceso sea más complejo que lo que aquí expones, pero eso no es impedimento para intentar trabajar un poco la pregunta de modo que se ajuste lo máximo posible a la realidad y, sobre todo, no falte información.

    Por otro lado, no entiendo porqué usas MAX sobre el código de producto y no agrupas también por ese concepto, más después de lo que acabas de comentar; tampoco aplicaría el CONVERT sobre el campo FECHA si este es de tipo DATETIME o SMALLDATETIME. En definitiva, que la consulta quedaría así:

     

    SELECT PRODUCTO, SUM(CANTIDAD) AS CANTIDAD
    FROM PRODUCTOS
    WHERE PROVEEDOR = @Proveedor
     AND FECHA > @Fecha --(AAAAMMDD)
    GROUP BY PRODUCTO
    

     

    Y luego, para aplicar la actualización o inserción según exista o no el código de producto, echa un vistazo al script que pasó Miguel en una respuesta anterior. Lo único que tendrías que cambiar es el criterio de combinación, puesto que ahí Miguel supuso que querías hacerlo por código de proveedor, cuando lo que parece es que hay que hacerlo por código de producto

     


    Tuesday, August 23, 2011 3:24 PM

All replies

  • El problema de los cursores no son tanto ellos mismos como el uso que se hace de ellos. Cuando no se tiene mucha experiencia, es más fácil pensar en las soluciones de forma secuencial (al igual que como se programa) en vez de como conjuntos de registros (que es como está pensado para trabajar SQL Server). Los cursores trabajand de forma secuencial, registro a registro, y aunque para nosotros es más fácil hacerlo así, a SQL Server... le matas.

    Es cierto que en algunas situaciones, el uso de cursores es inevitable; incluso en ciertos casos (contadísimos), una solución con cursores será más eficiente que una orientada a conjuntos. Pero para darse cuenta de eso hay que tener mucha experiencia y certificar con pruebas que efectivamente el cursor es la mejor opción. Así que, como norma general, siempre se recomienda no usarlos.

    En tu caso no parece que sea necesario el uso del cursor, pero tampoco nos das mucha información para ayudarte. Necesitaríamos el script de creación de las tablas, unos datos de ejemplo, el código actual y los resultados esperados para poder trabajar mejor.

    Tuesday, August 23, 2011 8:47 AM
  • Hola Carlos,

    a ver si me se explicar mejor.

    Suponemos que tenemos una tabla temporal (que se crea en la stored procedure) con los campos CODIGO, tA, tB, tC,...tM

    Después realizo una selección de registros sobre la tabla TABLA1 y relleno los campos de la tabla temporal CODIGO, tA, tB, tC, tD

    A continuación (aqui es donde viene lo del cursor) realizo una selección de registros de la tabla TABLA2, en esta tabla, puedo encontrar registros coincidentes o no. En el caso de encontrarlos, se pueden dar dos situaciones:

    1) que haya un registro con un valor de CODIGO que ya exista en la tabla temporal, en ese caso tengo que hacer un update de los campos tE, tF con la condición WHERE tablatemporal.CODIGO = TABLA2.CODIGO

    2) que en la TABLA2 haya un registro cuyo valor de CODIGO no exista en la tabla temporal, en ese caso hay que hacer un insert para los campos: CODIGO, tE, tF

     

    El campo CODIGO es clave de tabla y no se puede repetir, cualquier otro campo puede estar informado o a nulo.

    Como he dicho antes, se puede dar la circunstancia de que en TABLA2 no aparezca ningún registro, en ese caso no hay que hacer nada.

     

    Tuesday, August 23, 2011 9:12 AM
  • Si explicarte te explicas, a lo que me refería es a que sin los datos que te comentaba en mi respuesta anterior, no vamos a poder trabajar en una solución más concreta que te ayude más en tu situación.

    Además, tampoco indicas ni la versión ni edición de SQL Server con la que trabajas, lo cual hace más difícil dar con una solución. Por esta razón se creó el hilo fijo "Ayúdanos a ayudarte" (http://social.msdn.microsoft.com/Forums/es-ES/sqlserveres/thread/0783f75a-6a2f-49e6-916d-76e1f0ffdfb4)

    En cualquier caso, con lo que comentas el uso de un cursor es innecesario. Si tienes que actualizar o insertar unos registros de TABLA2 en base a si coinciden un campo o no, puedes usar directamente MERGE (http://msdn.microsoft.com/es-es/library/bb510625.aspx) si trabajas con la versión 2008 o superior; en caso contrario, puedes hacer un UPDATE o un INSERT combinando ambas tablas (la temporal y la destino) como en este script

     

    -- actualizamos los existentes
    UPDATE tt
     SET tt.CODIGO=t.CODIGO, tt.tE=t.tE, tt.tF=t.tF
    FROM tabla2 t INNER JOIN #temporal tt
     ON tt.codigo = t.codigo
    
    -- insertamos los no existentes
    INSERT #temporal
     SELECT t.CODIGO, t.tE, t.tF
     FROM #temporal tt RIGHT JOIN tabla2 t
      ON tt.codigo = t.codigo
     WHERE tt.codigo IS NULL
    


     


    Tuesday, August 23, 2011 9:25 AM
  • Hola Carlos,

    la versión de SQL Server es la 2005, por lo tanto creo que no puedo utilizar MERGE.

    La select que utilizo para recuperar los datos del equivalente de la tabla2 es esta:

     

     

    SELECT MAX(PRODUCTO) AS PRODUCTO, SUM(CANTIDAD) AS CANTIDAD

    FROM PRODUCTOS

    WHERE PROVEEDOR = @Proveedor

    AND convert(varchar(8), FECHA, 112) > @Fecha --(AAAAMMDD)

     

     

    Los registros devueltos de esta consulta son los que he de actualizar o insertar según corresponda en la tabla temporal

    No veo como adaptar tus scripts de ejemplo para usar el sumatorio y la agrupación.

    Tuesday, August 23, 2011 2:05 PM
  • Por cosas como esta es necesario que se proporcionen la edición y versión de SQL Server, el script de creación de las tablas, datos de ejemplo, el código actual y el resultado esperado. Hasta ahora el hilo lleva 4 respuestas y aún no se ha podido dar con una solución a tu problema porque poco a poco va saliendo nueva información.

    Si ahora resulta que la consulta que nutre de datos a la temporal tiene funciones de agregado, y que lo que hay que actualizar no es la tabla temporal sino la tabla original, pues la cosa vuelve a cambiar.

    Es posible usar esa consulta para actualizar o insertar registros de otra tabla adaptando un poco el código, pero el problema es que no disponemos de toda la información necesaria para hacerlo. Por ejemplo, en una de tus respuestas hablas de un campo CODIGO que es la clave; también hablas de otros dos campos que no sé a qué se corresponden de esa consulta...

    En resumen: que lo suyo es que nos pongas toda la información que solicitamos para que esto tenga un sentido y no se pierda tiempo en preguntas y respuestas que al final no llevan a ningún sitio.

    Tuesday, August 23, 2011 2:40 PM
  • supongamos que la tabla destino tiene un campo producto, un campo proveedor y un campo cantidad.

    Supongamos también que max(producto) no desvirtua nada, porque desde el punto de vista de negocio... me resulta dificil creer que puedas sumar "peras con manzanas".

    creo que esto se acerca a lo que buscas

    create table productos (producto int ,cantidad int, proveedor int, fecha datetime)
    go
    create table destino(producto int, cantida int, proveedor int)
    declare @fecha datetime
    set @fecha='20100901';
    update d
    SEt cantida=agrup.cantidad,producto=agrup.PRODUCTO
    from destino d
    inner join (
    SELECT proveedor,MAX(PRODUCTO) AS PRODUCTO, SUM(CANTIDAD) AS CANTIDAD
    FROM PRODUCTOS
    where FECHA > @Fecha --(AAAAMMDD)
    group by proveedor
    ) agrup on d.proveedor=agrup.proveedor
    
    insert into destino (producto,cantida,proveedor)
    select agrup.producto,agrup.cantidad,agrup.proveedor
    from destino d
    right join (
    SELECT proveedor,MAX(PRODUCTO) AS PRODUCTO, SUM(CANTIDAD) AS CANTIDAD
    FROM PRODUCTOS
    where FECHA > @Fecha --(AAAAMMDD)
    group by proveedor
    ) agrup on d.proveedor=agrup.proveedor
    where d.producto is null 


    Comparte lo que sepas, aprende lo que no sepas (FGG) http://www.portalsql.com
    Tuesday, August 23, 2011 2:47 PM
  • Hola,

    Carlos, desde el punto de vista de negocio, la cosa es bastante compleja, solo para explicarlo de manera funcional ya me llevaría un buen rato, es por eso que intento hacer una abstracción del punto que me tiene bloqueado.

    Miguel, el poner max(producto) es porque necesito el valor de ese campo para hacer el insert, y si no lo incluyo en una función de agregado (ya que tengo que realizar el sumatorio) no me lo deja poner. ¿Hay otra forma más correcta de hacerlo?

    A ver si ahora soy capaz de incluir la información necesaria para buscar una solución.

    --Estructura de la tabla temporal

    create table #temp(Id int IDENTITY(1,1) NOT NULL, Producto varchar(10), Stock decimal(18,2), StockNoDisp decimal(18,2), Entradas decimal(18,2));

       

    --Llenamos la tabla temporal con los productos del almacén

    INSERT INTO #temp (Producto, Stock, StockNoDisp)

    SELECT PRODUCTO, CANT_DISP CANT_NO_DISP

    FROM ALMACEN

    WHERE FECHASTOCK = @Fecha AND PROVEEDOR = @Proveedor

     

    Ahora aqui es donde selecciono las entradas nuevas de productos que se han de apuntar en la columna entradas de la tabla temporal de manera que si el producto que entra ya existe en la tabla, se actualice el registro y que si no existe se añada un nuevo registro

    He de sumar la CANTIDAD porque en el mismo día pueden haber más de una entrada del mismo producto para el mismo proveedor.

     

    SELECT MAX(PRODUCTO) AS PRODUCTO, SUM(CANTIDAD) AS CANTIDAD

    FROM PRODUCTOS

    WHERE PROVEEDOR = @Proveedor

    AND convert(varchar(8), FECHA, 112) > @Fecha --(AAAAMMDD)

    Tuesday, August 23, 2011 3:12 PM
  • Weatherby, puedo entender que el proceso sea más complejo que lo que aquí expones, pero eso no es impedimento para intentar trabajar un poco la pregunta de modo que se ajuste lo máximo posible a la realidad y, sobre todo, no falte información.

    Por otro lado, no entiendo porqué usas MAX sobre el código de producto y no agrupas también por ese concepto, más después de lo que acabas de comentar; tampoco aplicaría el CONVERT sobre el campo FECHA si este es de tipo DATETIME o SMALLDATETIME. En definitiva, que la consulta quedaría así:

     

    SELECT PRODUCTO, SUM(CANTIDAD) AS CANTIDAD
    FROM PRODUCTOS
    WHERE PROVEEDOR = @Proveedor
     AND FECHA > @Fecha --(AAAAMMDD)
    GROUP BY PRODUCTO
    

     

    Y luego, para aplicar la actualización o inserción según exista o no el código de producto, echa un vistazo al script que pasó Miguel en una respuesta anterior. Lo único que tendrías que cambiar es el criterio de combinación, puesto que ahí Miguel supuso que querías hacerlo por código de proveedor, cuando lo que parece es que hay que hacerlo por código de producto

     


    Tuesday, August 23, 2011 3:24 PM
  • Hola,

    teneis razón, no hace falta el MAX :)

    El convert lo utilizo porque la fecha me llega en formato AAAAMMDD

    En base a vuestros ejemplos he escrito esta solución, no sé si será lo más óptimo, por favor, darme vuestra opinión

     

    UPDATE #temp SET Entradas = CANTIDAD

    FROM (

       SELECT PRODUCTO AS PRODUCTO, SUM(CANTIDAD) AS CANTIDAD

       FROM PRODUCTOS

       WHERE PROVEEDOR = @Proveedor

         AND convert(varchar(8), FECHA, 112) > @Fecha --(AAAAMMDD)

    ) X INNER JOIN #temp T ON X.PRODUCTO = T.Producto

     

    INSERT #temp (Producto, Entradas)

    SELECT X.PRODUCTO, X.CANTIDAD

    FROM (

       SELECT PRODUCTO AS PRODUCTO, SUM(CANTIDAD) AS CANTIDAD

       FROM PRODUCTOS

       WHERE PROVEEDOR = @Proveedor

         AND convert(varchar(8), FECHA, 112) > @Fecha --(AAAAMMDD)

    ) X LEFT JOIN #temp T ON X.PRODUCTO = T.Producto

    WHERE T.PRODUCTO IS NULL

     

    PD.- como haceis para poder pegar el código con colores y sin tanta separación entre líneas?

    Tuesday, August 23, 2011 3:37 PM

  • por partes.

     

    Quita el convert, si @fecha es una fecha, convertir a varchar es una perdida que anula el uso de index seek en caso de poder ser usado, es decir, o no hará nada o perjudicará el rendimiento. asegurate que @fecha es del tipo datetime, y que cuando la pasas.. la pasas en el formato que describes.

    El código que escribes parece correcto.

    P.D. En el menú de insertar código, si le dices que es TSQL lo colorea bien :)

    por ejemplo

    UPDATE #temp SET Entradas = CANTIDAD
    FROM (
      SELECT PRODUCTO AS PRODUCTO, SUM(CANTIDAD) AS CANTIDAD
      FROM PRODUCTOS
      WHERE PROVEEDOR = @Proveedor
       AND convert(varchar(8), FECHA, 112) > @Fecha --(AAAAMMDD)
    ) X INNER JOIN #temp T ON X.PRODUCTO = T.Producto
    
    INSERT #temp (Producto, Entradas)
    SELECT X.PRODUCTO, X.CANTIDAD
     FROM (
      SELECT PRODUCTO AS PRODUCTO, SUM(CANTIDAD) AS CANTIDAD
      FROM PRODUCTOS
      WHERE PROVEEDOR = @Proveedor
       AND convert(varchar(8), FECHA, 112) > @Fecha --(AAAAMMDD)
     ) X LEFT JOIN #temp T ON X.PRODUCTO = T.Producto
     WHERE T.PRODUCTO IS NULL
    
    

    Comparte lo que sepas, aprende lo que no sepas (FGG) http://www.portalsql.com
    Tuesday, August 23, 2011 3:42 PM
  • Si el parámetro @fecha es alfanumérico (CHAR o VARCHAR), no tienes porqué hacer ese CONVERT. El formato AAAAMMDD es universal (como cadena de caracteres, claro) y SQL Server lo convierte a fecha sin los problemas típicos de la configuración regional. Por eso te lo hemos recomendado quitar tanto Miguel como yo.

    El script que pasas daría errores porque no estás agrupando por PRODUCTO. Salvando eso, es lo que te hemos estado sugiriendo...

    Y en cuanto a lo de pegar el código en formato "bonito", tienes un botón en la barra de herramientas de la caja de texto donde se escribe la respuesta (el tercero empezando por la derecha, el que está al lado de uno que pone "HTML") que permite escribir código y seleccionar un tipo de lenguaje para formatearlo como corresponda.

    Tuesday, August 23, 2011 3:44 PM
  • UPSS, bueno, aunque se me olvidó escribir el group by si que lo utilizo :)

     

    GROUP BY PRODUCTO
    


    entiendo pues que a parte de lo del convert del campo fecha (que ya lo quitaré) el usar la select en el from es lo más correcto en este caso, así pues lo dejaré de esta forma.

     

    Un saludo y muchas gracias por vuestra ayuda

     

     

    Tuesday, August 23, 2011 3:55 PM