Maxi: Cursores en SQL Server, por que no son tan malos :P

25/11/2004 - 17:40 por K | Informe spam
Saludos Maxi

Yo he tenido esa discusión, pero en realidad no solo de cursores, sino de la
mayor parte de la plataforma de desarrollo de Microsoft, ja ja ja, muchas
personas piensan que lo mas basico es lo mejor, y no lo es en el 100% de los
casos, como VS6 vs VS.Net, pero, acerca de SQL principalmente las
discusiones han sido acerca de tablas temporales VS cursores para hacer
scroll en un resultSet, de cualquier forma, aquí va por que considero una
ayuda benigna los cursores, precisamente ayer tuve un detalle acerca de eso,
te muestro el query que estuve trabajando

SELECT
F1.ABAN8 AS ClientID,
CASE
WHEN MIN(PS.FechaActivacion) IS Null THEN 0
ELSE 1
END AS ESelling,
MIN(PS.FechaActivacion) AS ESellingDate,
CASE
WHEN Wap.wapServiceDate IS Null THEN 0
ELSE 1
END AS Wap,
Wap.wapServiceDate AS WapDate,
CASE
WHEN x24Hrs.x24HrsServiceDate IS Null THEN 0
ELSE 1
END AS x24Hrs,
x24Hrs.x24HrsServiceDate AS x24HrsDate,
CASE
WHEN MIN(UGF.StartDate) IS Null THEN 0
ELSE 1
END AS CemPlus,
MIN(UGF.StartDate) AS CemPlusDate,
CASE
WHEN MIN(TCC.RegistrationDate) Is Null THEN 0
ELSE 1
END AS CCases,
MIN(TCC.RegistrationDate) AS CCasesDate,
CASE
WHEN Eficem.EficemDate IS NULL THEN 0 -- Aqui no utilizamos MIN por que
siempre me trae un solo registro
ELSE 1
END AS Eficem,
Eficem.EficemDate AS EficemDate

FROM
F0101 F1
LEFT JOIN EsEsp300.dbo.FProspectos AS PS ON F1.ABAN8 = PS.Cliente --AND
PS.FechaActivacion Is Not Null
LEFT JOIN vw_UsersGF AS UGF ON F1.ABAN8 = UGF.JDECode
LEFT JOIN tCASCases TCC ON F1.ABAN8 = TCC.BranchID
LEFT JOIN (
SELECT
UGF.JDECode AS JDECode,
A.StartDate AS EficemDate
FROM
cpEsp300.dbo.Agreement A
INNER JOIN vw_UsersGF UGF ON A.IDUser = UGF.IDUser
) AS Eficem ON F1.ABAN8 = Eficem.JDECode
LEFT JOIN (
SELECT
T1.SDAN8 AS SDAN8,
CASE WHEN T2.Counter2 IS NULL THEN T1.Counter
ELSE T2.Counter2
END AS x24HrsServiceDate
FROM
(SELECT SDAN8, MIN(SDTRDJ) AS Counter FROM F4211 F1 WHERE SDUPC1 = 3
AND SDTRDJ Is Not Null GROUP BY SDAN8) AS T1 FULL JOIN
(SELECT SDAN8 AS SDAN82, MIN(SDTRDJ) AS Counter2 FROM F42119 F2 WHERE
SDUPC1 = 3 AND SDTRDJ Is Not Null GROUP BY SDAN8) AS T2 ON T1.SDAN8 T2.SDAN82
) AS x24Hrs ON x24Hrs.SDAN8 = F1.ABAN8
LEFT JOIN (
SELECT
T3.SDAN8 AS SDAN8,
CASE WHEN T4.Counter2 IS NULL THEN T3.Counter
ELSE T4.Counter2
END AS WapServiceDate
FROM
(SELECT SDAN8, MIN(SDTRDJ) AS Counter FROM F4211 F1 WHERE SDUPC1 = 2
AND SDTRDJ Is Not Null GROUP BY SDAN8) AS T3 FULL JOIN
(SELECT SDAN8 AS SDAN82, MIN(SDTRDJ) AS Counter2 FROM F42119 F2 WHERE
SDUPC1 = 2 AND SDTRDJ Is Not Null GROUP BY SDAN8) AS T4 ON T3.SDAN8 T4.SDAN82
) AS Wap ON F1.ABAN8 = Wap.SDAN8
GROUP BY
F1.ABAN8, Wap.wapServiceDate, x24Hrs.x24HrsServiceDate, Eficem.JDECode,
Eficem.EficemDate
HAVING
MIN(PS.FechaActivacion) Is Not Null OR
MIN(Wap.wapServiceDate) Is Not Null OR
MIN(x24Hrs.x24HrsServiceDate) Is Not Null OR
MIN(UGF.StartDate) Is Not Null OR
MIN(TCC.RegistrationDate) Is Not Null

Bueno, como background, la tabla que saca los valores, con DISTINCT (que es
algo asi como la "llave", es con lo que sabemos que registros afectar),
produce 220000+ regs como Result Set, ahora, eso no es problema y SQL los
recupera en menos de 2 secs, el problema viene por que, en la tabla F42119,
consiste de mas de 3 000 000+ regs, filtrandola con la llave, la consula es
instantánea (foreignKey = 'value'), pero en el SELECT no puedo mandarle a un
SELECT anidado el valor explícito de la llave, eso lo tuve que emular con el
JOIN externo (AS Wap ON F1.ABAN8 = Wap.SDAN8), además, las tablas F4211 y
F42119 son identicas, solo que una contiene registros históricos (La de los
3M+) y no puedo utilizar UNION, por que el UNION me producía muchisimos mas
registros y hacía mas pesado el JOIN del nivel superior, y al no poder
enviarle sino atravez del JOIN la llave contra que filtrarlo para cada
registro en particula, me tardaba aún mas.

Como sea, con varias optimizaciones al nivel donde los SELECT metían los
filtrajes, lo mas que pude reducir el tiempo fue a no menos de 5 mins, via
un solo select, mientras que con el cursor corría en mas o menos 4 mins, no
es mucha diferencia, pero teoricamente, el tiempo crecerá exponencialmente
mas para el SELECT que para el CURSOR, por que el cursor manda un valor
especifico a un SP que tiene las colsultas necesarias para generar los
resultados que necesito

En esta ocasión, al menos en las pruebas que hice, el Cursor desempeño de
mejor forma, y el SELECT me afectaba cuando quería correr otro query a
alguna de las tablas, me marcaba timeout, con el Cursor corriendo no, igual
se alentaba el Query, pero respondía, quizá esto ultimo fue coincidencia,
pero fue en mas de una ocasión. E incluso, una de las veces que lo corrí
solo, el mismo parecía como bloquearse en los ultimos registros, y tardó mas
de 20 Min.

Y pues nada mas como respuesta rapida también al respecto, cuando entablo
una discusión sobre cursores, hay cosas que desafortunadamente de otra forma
no puedes realizar, suponiendo por ejemplo, que tuvieras un sistema que
trabajaras con probabilidades, y necesitaras generar números aleatorios para
proyecciones, utilizar RAND en un SELECT pe. SELECT RAND() AS Tendencia,
Field AS Factor FROM TablaHistorico, te va a utilizar el mismo número
aleatorio atravez de todo el Result Set, lo cual, pues sería incorrecto.

Quizá tu nunca has tenido necesidad de utilizarlos en base a los proyectos
que has estado, pero yo sigo pensando que, como la mayoría de las
tecnologías un poco mas complejonas de MS, bien usados, no son un enemigo. Y
decir que su uso es patético, me parece... cierto, pero no siempre.

De cualquier forma, sigo trabajando en ese query, por que me pareció
interesante el hecho de tratar de reducir el tiempo de consulta moviendo de
niveles el filtraje (incluso sacandolo del contexto inicial ^_^) te aviso si
pude hacer que el SELECT jalara aún mas rápido. Suerte

K' Ragnarok





"MAXI" <maxi_da@infovia.com.ar.sacame> wrote in message
news:OLKkXaP0EHA.2804@TK2MSFTNGP15.phx.gbl...

Hola, podrias ampliar un poco en que te basas que los cursores ayudan? yo
considero que son muy malos para tareas de produccion y que se usan porque
es mas facil :(

Te cuento mas, hace unos dias tuve una linda discusion con arquitectos


.net

sobre este tema y demostre que el uso de cursores es patetico para


cualquier

cosa




Maxi

Buenos Aires - Argentina
Desarrollador .NET 3 Estrellas
Microsoft User Group (MUG)
Mail: Maxi_accotto[arroba]speedy.com.ar

Msn Messenger: Maxi_adrogue@msn.com

"K'" <soki.gakiya@adepsa.com.mx> escribió en el mensaje
news:O0xmLPP0EHA.2540@TK2MSFTNGP09.phx.gbl...
> De hecho, si revisas los resultados de tu cursor, hace un fetch linea


por

> linea, como si hicieras un SELECT por renglón
>
> Si creas el SP y lo ejecutas desde QA, debe traerte exactamente el mismo
> resultado (una especie de query multiple), al parecer al no descargar


los

> valores en variables, el CURSOR lo toma como consultas completas
>
> Y supongo que si eso lo estás capturando con ADO, por eso solo te


regresa

> un
> resultset
>
> En realidad no entendí bien cual es tu tirada con ese CURSOR, me parece
> que
> puedes lograr la funcionalidad necesaria sin uso del mismo, los CURSORES
> no
> son malos usados correctamente, te ayudar a agilizar procesos, pero mal
> usados, si representan un riesgo enorme, especialmente en performance,


así

> que mejor dejalos para cuando todo lo demás atenta de forma mas
> comprometedora en tu sistema.
>
> Si nos especificas que necesitas hacer, será mas facil apuntarte en la


dir

> para solucionar tu problema
>
> K'
>
> "Paulino Padial" <paulifoc@telefonica.net> wrote in message
> news:%23X$b4aO0EHA.2688@TK2MSFTNGP09.phx.gbl...
>> Una pregunta, ¿ Te devuelve solo la ultima fila ? , puede ser que


vayan

> por
>> ahi los tiros ;-)
>>
>>
>> "fabicabrera@yahoo.com" <anonymous@discussions.microsoft.com> escribió


en

> el
>> mensaje news:873301c4d0e3$d25fbde0$a601280a@phx.gbl...
>> > Saludos Grupo!
>> >
>> > Necesito crar una consulta como la que sigue
>> > ***********
>> > Use Pubs
>> > Go
>> > DECLARE authors_cursor CURSOR
>> > FOR SELECT * FROM authors
>> > OPEN authors_cursor
>> >
>> > FETCH NEXT FROM authors_cursor
>> > WHILE @@FETCH_STATUS = 0
>> > BEGIN
>> > FETCH NEXT FROM authors_cursor
>> > END
>> > CLOSE authors_cursor
>> > DEALLOCATE authors_cursor
>> > ****
>> >
>> > Cuando lo ejecuto en el analizador de consultas de SQL
>> > funciona perfectamente, pero cuando lo guardo en un
>> > procedimieto almacenado y lo llamo desde mi aplicacion me
>> > retorna solo la primera fila...
>> >
>> > Si alguien puede ayudarme, gracias de Antemano
>> >
>> > Fabi
>>
>>
>
>


 

Leer las respuestas

#1 Maxi
25/11/2004 - 18:28 | Informe spam
Hola, mira deberia tener las tablas para poder analizar mejor el caso!! pero
me parece que hay algunas cosas que no me cierran ;)

Si la tabla tiene 3M de registros los necesitas todos?

Ademas habria que ver tema de indices y otras yerbas, pero te aseguro que un
select tarda menos que un cursor en el 99% de los casos. Eso si, si haces un
tableScan (que es seguro lo que te esta pasando con la de 3M) va a demorar
un lindo ratito ;)

Tambien puedes probar de cambiar el nivel de aislamiento en la query y
probar


Salu2
Maxi


"K'" escribió en el mensaje
news:
Saludos Maxi

Yo he tenido esa discusión, pero en realidad no solo de cursores, sino de
la
mayor parte de la plataforma de desarrollo de Microsoft, ja ja ja, muchas
personas piensan que lo mas basico es lo mejor, y no lo es en el 100% de
los
casos, como VS6 vs VS.Net, pero, acerca de SQL principalmente las
discusiones han sido acerca de tablas temporales VS cursores para hacer
scroll en un resultSet, de cualquier forma, aquí va por que considero una
ayuda benigna los cursores, precisamente ayer tuve un detalle acerca de
eso,
te muestro el query que estuve trabajando

SELECT
F1.ABAN8 AS ClientID,
CASE
WHEN MIN(PS.FechaActivacion) IS Null THEN 0
ELSE 1
END AS ESelling,
MIN(PS.FechaActivacion) AS ESellingDate,
CASE
WHEN Wap.wapServiceDate IS Null THEN 0
ELSE 1
END AS Wap,
Wap.wapServiceDate AS WapDate,
CASE
WHEN x24Hrs.x24HrsServiceDate IS Null THEN 0
ELSE 1
END AS x24Hrs,
x24Hrs.x24HrsServiceDate AS x24HrsDate,
CASE
WHEN MIN(UGF.StartDate) IS Null THEN 0
ELSE 1
END AS CemPlus,
MIN(UGF.StartDate) AS CemPlusDate,
CASE
WHEN MIN(TCC.RegistrationDate) Is Null THEN 0
ELSE 1
END AS CCases,
MIN(TCC.RegistrationDate) AS CCasesDate,
CASE
WHEN Eficem.EficemDate IS NULL THEN 0 -- Aqui no utilizamos MIN por
que
siempre me trae un solo registro
ELSE 1
END AS Eficem,
Eficem.EficemDate AS EficemDate

FROM
F0101 F1
LEFT JOIN EsEsp300.dbo.FProspectos AS PS ON F1.ABAN8 = PS.Cliente --AND
PS.FechaActivacion Is Not Null
LEFT JOIN vw_UsersGF AS UGF ON F1.ABAN8 = UGF.JDECode
LEFT JOIN tCASCases TCC ON F1.ABAN8 = TCC.BranchID
LEFT JOIN (
SELECT
UGF.JDECode AS JDECode,
A.StartDate AS EficemDate
FROM
cpEsp300.dbo.Agreement A
INNER JOIN vw_UsersGF UGF ON A.IDUser = UGF.IDUser
) AS Eficem ON F1.ABAN8 = Eficem.JDECode
LEFT JOIN (
SELECT
T1.SDAN8 AS SDAN8,
CASE WHEN T2.Counter2 IS NULL THEN T1.Counter
ELSE T2.Counter2
END AS x24HrsServiceDate
FROM
(SELECT SDAN8, MIN(SDTRDJ) AS Counter FROM F4211 F1 WHERE SDUPC1 = 3
AND SDTRDJ Is Not Null GROUP BY SDAN8) AS T1 FULL JOIN
(SELECT SDAN8 AS SDAN82, MIN(SDTRDJ) AS Counter2 FROM F42119 F2 WHERE
SDUPC1 = 3 AND SDTRDJ Is Not Null GROUP BY SDAN8) AS T2 ON T1.SDAN8 > T2.SDAN82
) AS x24Hrs ON x24Hrs.SDAN8 = F1.ABAN8
LEFT JOIN (
SELECT
T3.SDAN8 AS SDAN8,
CASE WHEN T4.Counter2 IS NULL THEN T3.Counter
ELSE T4.Counter2
END AS WapServiceDate
FROM
(SELECT SDAN8, MIN(SDTRDJ) AS Counter FROM F4211 F1 WHERE SDUPC1 = 2
AND SDTRDJ Is Not Null GROUP BY SDAN8) AS T3 FULL JOIN
(SELECT SDAN8 AS SDAN82, MIN(SDTRDJ) AS Counter2 FROM F42119 F2 WHERE
SDUPC1 = 2 AND SDTRDJ Is Not Null GROUP BY SDAN8) AS T4 ON T3.SDAN8 > T4.SDAN82
) AS Wap ON F1.ABAN8 = Wap.SDAN8
GROUP BY
F1.ABAN8, Wap.wapServiceDate, x24Hrs.x24HrsServiceDate, Eficem.JDECode,
Eficem.EficemDate
HAVING
MIN(PS.FechaActivacion) Is Not Null OR
MIN(Wap.wapServiceDate) Is Not Null OR
MIN(x24Hrs.x24HrsServiceDate) Is Not Null OR
MIN(UGF.StartDate) Is Not Null OR
MIN(TCC.RegistrationDate) Is Not Null

Bueno, como background, la tabla que saca los valores, con DISTINCT (que
es
algo asi como la "llave", es con lo que sabemos que registros afectar),
produce 220000+ regs como Result Set, ahora, eso no es problema y SQL los
recupera en menos de 2 secs, el problema viene por que, en la tabla
F42119,
consiste de mas de 3 000 000+ regs, filtrandola con la llave, la consula
es
instantánea (foreignKey = 'value'), pero en el SELECT no puedo mandarle a
un
SELECT anidado el valor explícito de la llave, eso lo tuve que emular con
el
JOIN externo (AS Wap ON F1.ABAN8 = Wap.SDAN8), además, las tablas F4211 y
F42119 son identicas, solo que una contiene registros históricos (La de
los
3M+) y no puedo utilizar UNION, por que el UNION me producía muchisimos
mas
registros y hacía mas pesado el JOIN del nivel superior, y al no poder
enviarle sino atravez del JOIN la llave contra que filtrarlo para cada
registro en particula, me tardaba aún mas.

Como sea, con varias optimizaciones al nivel donde los SELECT metían los
filtrajes, lo mas que pude reducir el tiempo fue a no menos de 5 mins, via
un solo select, mientras que con el cursor corría en mas o menos 4 mins,
no
es mucha diferencia, pero teoricamente, el tiempo crecerá exponencialmente
mas para el SELECT que para el CURSOR, por que el cursor manda un valor
especifico a un SP que tiene las colsultas necesarias para generar los
resultados que necesito

En esta ocasión, al menos en las pruebas que hice, el Cursor desempeño de
mejor forma, y el SELECT me afectaba cuando quería correr otro query a
alguna de las tablas, me marcaba timeout, con el Cursor corriendo no,
igual
se alentaba el Query, pero respondía, quizá esto ultimo fue coincidencia,
pero fue en mas de una ocasión. E incluso, una de las veces que lo corrí
solo, el mismo parecía como bloquearse en los ultimos registros, y tardó
mas
de 20 Min.

Y pues nada mas como respuesta rapida también al respecto, cuando entablo
una discusión sobre cursores, hay cosas que desafortunadamente de otra
forma
no puedes realizar, suponiendo por ejemplo, que tuvieras un sistema que
trabajaras con probabilidades, y necesitaras generar números aleatorios
para
proyecciones, utilizar RAND en un SELECT pe. SELECT RAND() AS Tendencia,
Field AS Factor FROM TablaHistorico, te va a utilizar el mismo número
aleatorio atravez de todo el Result Set, lo cual, pues sería incorrecto.

Quizá tu nunca has tenido necesidad de utilizarlos en base a los proyectos
que has estado, pero yo sigo pensando que, como la mayoría de las
tecnologías un poco mas complejonas de MS, bien usados, no son un enemigo.
Y
decir que su uso es patético, me parece... cierto, pero no siempre.

De cualquier forma, sigo trabajando en ese query, por que me pareció
interesante el hecho de tratar de reducir el tiempo de consulta moviendo
de
niveles el filtraje (incluso sacandolo del contexto inicial ^_^) te aviso
si
pude hacer que el SELECT jalara aún mas rápido. Suerte

K' Ragnarok





"MAXI" wrote in message
news:
Hola, podrias ampliar un poco en que te basas que los cursores ayudan? yo
considero que son muy malos para tareas de produccion y que se usan
porque
es mas facil :(

Te cuento mas, hace unos dias tuve una linda discusion con arquitectos


.net
sobre este tema y demostre que el uso de cursores es patetico para


cualquier
cosa




Maxi

Buenos Aires - Argentina
Desarrollador .NET 3 Estrellas
Microsoft User Group (MUG)
Mail: Maxi_accotto[arroba]speedy.com.ar

Msn Messenger:

"K'" escribió en el mensaje
news:
> De hecho, si revisas los resultados de tu cursor, hace un fetch linea


por
> linea, como si hicieras un SELECT por renglón
>
> Si creas el SP y lo ejecutas desde QA, debe traerte exactamente el
> mismo
> resultado (una especie de query multiple), al parecer al no descargar


los
> valores en variables, el CURSOR lo toma como consultas completas
>
> Y supongo que si eso lo estás capturando con ADO, por eso solo te


regresa
> un
> resultset
>
> En realidad no entendí bien cual es tu tirada con ese CURSOR, me parece
> que
> puedes lograr la funcionalidad necesaria sin uso del mismo, los
> CURSORES
> no
> son malos usados correctamente, te ayudar a agilizar procesos, pero mal
> usados, si representan un riesgo enorme, especialmente en performance,


así
> que mejor dejalos para cuando todo lo demás atenta de forma mas
> comprometedora en tu sistema.
>
> Si nos especificas que necesitas hacer, será mas facil apuntarte en la


dir
> para solucionar tu problema
>
> K'
>
> "Paulino Padial" wrote in message
> news:%23X$
>> Una pregunta, ¿ Te devuelve solo la ultima fila ? , puede ser que


vayan
> por
>> ahi los tiros ;-)
>>
>>
>> "" escribió


en
> el
>> mensaje news:873301c4d0e3$d25fbde0$
>> > Saludos Grupo!
>> >
>> > Necesito crar una consulta como la que sigue
>> > ***********
>> > Use Pubs
>> > Go
>> > DECLARE authors_cursor CURSOR
>> > FOR SELECT * FROM authors
>> > OPEN authors_cursor
>> >
>> > FETCH NEXT FROM authors_cursor
>> > WHILE @@FETCH_STATUS = 0
>> > BEGIN
>> > FETCH NEXT FROM authors_cursor
>> > END
>> > CLOSE authors_cursor
>> > DEALLOCATE authors_cursor
>> > ****
>> >
>> > Cuando lo ejecuto en el analizador de consultas de SQL
>> > funciona perfectamente, pero cuando lo guardo en un
>> > procedimieto almacenado y lo llamo desde mi aplicacion me
>> > retorna solo la primera fila...
>> >
>> > Si alguien puede ayudarme, gracias de Antemano
>> >
>> > Fabi
>>
>>
>
>






Preguntas similares