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
>>
>>
>
>


Preguntas similare

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:
Mostrar la cita
#2 K
25/11/2004 - 19:04 | Informe spam
En realidad la tabla no está normalizada, y no fui yo quien la diseñó, de
forma que no conozco bien como va a quedar al final, mi idea es que ya
normalizada va a ser aún mas rápido y si, son muchisimo mas rapidos, estoy
de acuerdo, mi otra idea de hecho fue hacer, mediante funciones de agregado
(que es realmente lo que necesito en todas las tablas (valores mínimos)) un
espejo menor (MUCHO menor) de las tablas grandes, pero como que era mas
tardado aún no sé por que, no puedo utilizar temporales, de forma que hice
una var tipo tabla, y te digo, fue tardado

Acerca de lo del nivel de aislamiento, a que te refieres? comprendo mas o
menos, pero no me queda muy claro, de forma que no sé que responderte sin
arriegarme a meter la pata :P

K'

"Maxi" wrote in message
news:%
Mostrar la cita
pero
Mostrar la cita
un
Mostrar la cita
un
Mostrar la cita
de
Mostrar la cita
muchas
Mostrar la cita
una
Mostrar la cita
WHERE
Mostrar la cita
WHERE
Mostrar la cita
Eficem.JDECode,
Mostrar la cita
los
Mostrar la cita
a
Mostrar la cita
con
Mostrar la cita
y
Mostrar la cita
via
Mostrar la cita
exponencialmente
Mostrar la cita
de
Mostrar la cita
coincidencia,
Mostrar la cita
entablo
Mostrar la cita
proyectos
Mostrar la cita
enemigo.
Mostrar la cita
aviso
Mostrar la cita
yo
Mostrar la cita
parece
Mostrar la cita
mal
Mostrar la cita
performance,
Mostrar la cita
la
Mostrar la cita
escribió
Mostrar la cita
#3 Maxi
25/11/2004 - 19:13 | Informe spam
Hola, bueno como te dije antes!! habria que ver varias cosas en esa
megaconsulta!! quizas los indices no sean los correctos por ej y se esten
usando cluster scan (si nos pasas el plan de ejecucion seria muy bueno)

Cuando hablo del aislamiento me refiero a esto:

SET TRANSACTION ISOLATION LEVEL

Quizas poniendo que no bloquee tarde mucho menos!! claro tiene sus riegos
pero... habria que analizar la cosa mas en profundidad para poder dar una
solucion acertada. De algo estoy seguro, con un SELECT siempre sera mejor,
una de las razones es la velocidad, otra los bloqueos que generan los
cursores y una tercera es la escalabilidad!!


Salu2
Maxi


"K'" escribió en el mensaje
news:
Mostrar la cita
#4 Salvador Ramos
25/11/2004 - 19:26 | Informe spam
Otra alternativa es utilizar vistas indexadas

Un saludo
Salvador Ramos
Murcia - España
[Microsoft MVP SQL Server]
www.helpdna.net
¿ Te interesa participar en las reuniones
del grupo de Usuarios de SQL Server y .NET ?
Se harán en levante de España, (Alicante o Murcia)?

"K'" escribió en el mensaje
news:
Mostrar la cita
#5 Javier Loria
26/11/2004 - 17:42 | Informe spam
Hola:
No soy Maxi, pero prodrias probar si el siguiente codigo, obtiene los
mismos resultados y mejor desempeno?
==SELECT F1.ABAN8 AS ClientID
, CASE WHEN MIN(PS.FechaActivacion) IS Null
THEN 0
ELSE 1
END AS ESelling
, MIN(PS.FechaActivacion) AS ESellingDate
, -- WAP
CASE WHEN x24HrsWap.wapServiceDate IS Null
THEN 0
ELSE 1
END AS Wap
, x24HrsWap.wapServiceDate AS WapDate
, -- 24Hrs
CASE
WHEN x24HrsWap.x24HrsServiceDate IS Null
THEN 0
ELSE 1 END AS x24Hrs
, x24HrsWap.x24HrsServiceDate AS x24HrsDate
, -- CEMPlus
CASE WHEN MIN(UGF.StartDate) IS Null
THEN 0
ELSE 1 END AS CemPlus
, MIN(UGF.StartDate) AS CemPlusDate
, --Cases
CASE WHEN MIN(TCC.RegistrationDate) Is Null
THEN 0
ELSE 1 END AS CCases
, MIN(TCC.RegistrationDate) AS CCasesDate
, CASE WHEN Agr.StartDate IS NULL
THEN 0 -- Aqui no utilizamos MIN por que siempre me trae un solo registro
ELSE 1 END AS Eficem
, Agr.StartDate 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 vw_UsersGF AS UGF
ON F1.ABAN8 = UGF.JDECode
LEFT JOIN cpEsp300.dbo.Agreement AS Agr
ON Agr.IDUser = UGF.IDUser
LEFT JOIN (SELECT T1.SDAN8 AS SDAN8
, COALESCE(T2.X24Hrs, T1.X24Hrs) AS x24HrsServiceDate
, COALESCE(T2.Wap , T1.Wap) AS x24HrsServiceDate
FROM
(SELECT SDAN8
, MIN(CASE WHEN SDUPC1=3 THEN SDTRDJ END) AS X24Hrs
, MIN(CASE WHEN SDUPC1=2 THEN SDTRDJ END) AS Wap
FROM F4211
WHERE (SDUPC1 = 3 OR SDUPC1 =2) AND
AND SDTRDJ Is Not Null
GROUP BY SDAN8) AS T1
FULL JOIN
(SELECT SDAN8 AS SDAN82
, MIN(CASE WHEN SDUPC1=3 THEN SDTRDJ END) AS X24Hrs
, MIN(CASE WHEN SDUPC1=2 THEN SDTRDJ END) AS Wap
FROM F42119
WHERE (SDUPC1 = 3 OR SDUPC1 =2)
AND SDTRDJ Is Not Null
GROUP BY SDAN8) AS T2
ON T1.SDAN8 =T2.SDAN82) AS x24HrsWap
ON x24HrsWap.SDAN8 = F1.ABAN8
GROUP BY F1.ABAN8, x24HrsWap.wapServiceDate, x24HrsWap.x24HrsServiceDate,
UGF.JDECode, Agr.StartDate
HAVING MIN(PS.FechaActivacion) Is Not Null OR
MIN(Wap.wapServiceDate) Is Not Null OR
MIN(x24HrsWap.x24HrsServiceDate) Is Not Null OR
MIN(UGF.StartDate) Is Not Null OR
MIN(TCC.RegistrationDate) Is Not Null

== Basicamente, trate de "aplanar" algunas de las consultas y evitar una
consulta. Espero que no tenga errores de sintaxis ya que esta escrito sin el
codigo de las tablas, lo que hace muy dificil la
Suerte,


Javier Loria
Costa Rica
Se aprecia la inclusion de DDL (CREATE, INSERTS, etc.)
que pueda ser copiado y pegado al Query Analizer.
La version de SQL y Service Pack tambien ayuda

"K'" wrote in message
news:
Mostrar la cita
la
Mostrar la cita
los
Mostrar la cita
eso,
Mostrar la cita
que
Mostrar la cita
WHERE
Mostrar la cita
WHERE
Mostrar la cita
es
Mostrar la cita
F42119,
Mostrar la cita
es
Mostrar la cita
un
Mostrar la cita
el
Mostrar la cita
los
Mostrar la cita
mas
Mostrar la cita
no
Mostrar la cita
igual
Mostrar la cita
mas
Mostrar la cita
forma
Mostrar la cita
para
Mostrar la cita
Y
Mostrar la cita
de
Mostrar la cita
si
Mostrar la cita
yo
Mostrar la cita
porque
Mostrar la cita
mismo
Mostrar la cita
parece
Mostrar la cita
CURSORES
Mostrar la cita
mal
Mostrar la cita
escribió
Mostrar la cita
Ads by Google
Search Busqueda sugerida