jueves, 27 de septiembre de 2007

Recopilacion trucos Quake 3

Consejos

· Aprovecha tus disparos. Ten en cuenta que los impactos de tus armas pueden distraer al adversario. Puedes tirar un rocket lejos (tardan en explotar un poco) e irte corriendo por otra zona para intentar emboscarle, abrir puertas disparándolas para ver su interior a cierta distancia, dejar algunas granadas en la salida de un teletransportador...

· Velocidad de impacto. Recuerda que para un determinado tiempo entre el disparo y el impact, que variará dependiendo del arma empleada. Los impactos con la railgun o la machinegun son inmediatos, sin embargo con el rocket launcher o con la plasma gun pasa un tiempo determinado acorde a la distancia hasta el objetivo. Tenlo en cuenta cuando uses estas armas y "calcula" en qué posición estará a la hora de disparar.

· Control. Si quieres jugar a un nivel aceptable tendrás que aprender a controlar determinadas zonas, defenderlas y usarlas de cebo porque sabes que tu adversario acudirá a ella. Por otro lado deberás también aprender a controlar los items, el tiempo que tardan en salir para cogerlos tú y no el contrario. Es un punto importante si quieres sobrevivir a un encuentro y abatir al adversario saliendo bien parado.

· Combos. Al igual que en muchos juegos de lucha, en Quake también existen combos de armas. Consiste en disparar con un arma y justo después disparar con otra. La razón es simple: un arma para disparar tiene que esperar un tiempo determinado después del último disparo, es el tiempo de recarga. Hay armas que tardan más que otras en recargarse, y en ocasiones contra grandes rivales, ese tiempo puede significar la muerte. Por ejemplo rocket+railgun, primero se levanta del suelo al adversario con un rocket en los pies, y cuando éste está en el aire y su movilidad queda mermada se le encaja un disparo de rail. Otro ejemplo distinto es rail+lightning, tras un acierto de la primera si el adversario está tocado la lightning gun es tan rápida, dañina y efectiva en un seguimiento que el contrario puede darse por muerto si lo haces bien. Si con un rocket (por ejemplo) cazas a alguien en el aire saltando por encima de ti hacia una plataforma y haces que baje a tu nivel, en el aire puedes meterle una sucesión de bolas de plasma que puede resultar mortal para tu adversario. Ten en cuenta que cada situación puede requerir un combo distinto para sacar provecho a dicho tiempo de recarga.

· Strafe jumping. Es un tipo de salto muy usado que te dará una velocidad muy superior a la de correr. Consiste en ir saltando mientras pulsas una tecla de strafe lateral, pudiendo corregir la dirección levemente en el momento de caer. Es decir, sin parar de correr saltas y pulsas un strafe, en el momento de caer pegas otro salto y mantienes pulsada la misma tecla o lo más usual es pulsar la contraria para intentar ir recto, si bien puedes usar una u otra para corregir la trayectoria, e incluso el ratón con un leve movimiento lateral.

· Circle jumping. Es algo más complejo que el anterior y se usa para obtener un salto más largo. Para realizarlo tienes que tomar velocidad (corriendo o preferiblemente viniendo con una serie de strafe jumping) y en el último salto hacer un giro lento pero contínuo del ratón hacia la misma dirección del strafe que vengas haciendo trazando un semiarco. Es decir, la dirección del arco que hagas con el ratón debe dirigirse hacia donde tengas pulsado el strafe lateral. Este salto es muy práctico para alcanzar zonas que con un salto normal o strafe no se puede, o bien para ir más rápido con un pequeño circle mezclado con el strafe jumping en cada contacto con el suelo.

· Rocket jumping. Muy sencillo de hacer, aunque muy perfeccionable desde el primer intento que hagas. Tan "sencillo" como saltar y cuando estés en el aire lanzar un rocket a tus piés. La onda expansiva de la explosión del rocket hará que tu salto sea sumamente mayor. Se usa para alcanzar niveles altos de forma rápida. El perfeccionar este salto consiste en saltar lo máximo posible provocando en ti el menor daño que puedas.

· Rocket jump-pad jumping. Es igual que el anterior pero el salto (y su respectivo rocket) hay que hacerlo sobre una plataforma de salto del mapa (jump-pad). Se consigue un salto asombrosamente alto con el mismo fin y forma de mejora que el anterior.

· Plasma climbing. Mediante la plasma gun se consigue escalar por las paredes. Pegado a una pared y tras un pequeño salto para despegar del suelo, estando la plasma gun apuntando a unos 50/60º hacia el suelo y disparando de forma contínua puedes conseguir escalar una pared lisa hacia arriba, o bien recorrerla de un lado a otro. Ten en cuenta que te quitará vida.

· Rocket climbing. Consiste en lo mismo al anterior pero con los rockets, aprovechando la onda expansiva para subir o avanzar por la pared.


Hay más saltos de este tipo (grenade jumping, grenade-rocket jumping...) que suelen usarse en exhibiciones del juego en lo que se llama "tricking".

NOTA: El autor de la columna original es CeeC y ha sido extraída de AunaArena.





Opinión completa



Para mejorar es necesario hacer rocket jumpings, que se consiguen mirando al suelo y saltando a la vez que disparando con el misil. no es recomendable hasta que se llegue a NIGHTMARE.

Un buen config, adecuado a tu computadora es imprescindible, así como unas teclas adecuadas.

El salto en el botón derecho del ratón, y el disparo en el izquierdo, así como el cambio de armas rocket-rail-escopeta en un botón y ametralladora-plasma-bfg en otro te ayudarán, luego te lo puedes configurar más al gusto. No es recomendable usar mucho fov en el config fov 90 o 100 va bien, ni usar el zoom más que en contadas ocasiones.

ESTRATEGIA: es más importante tener armadura que salud y salud que armas a la hora de jugar un versus.

PARA MEJORAR DE VERDAD JUGAR VERSUS EN SQUIRMISH EN LA DM4 y OTRAS CON XAERO.
adjunto un config "de competición", aunque el juego parece mucho más malo es muy útil para jugar online con módem o para apuntar mejor.
---------------------------------------------------------------------
-q3config.cfg-

unbindall

//MOVE\\

//set sjump "+moveup;wait;wait;+moveleft;wait;-moveleft;wait;-moveup"
bind SPACE "+moveup" //saltar
//bind SPACE vstr sjump //saltar


bind d "+moveright" //strafe derecha
bind s "+moveleft" //strafe izkierda
bind z "+movedown" //agacharse
bind ALT "+back" //atras
bind MOUSE2 "+forward" //alante
bind MOUSE1 "+attack" //atakar
bind MOUSE3 "+moveup" //correr/andar
bind TAB "+scores" //ranking
bind 1 "+button2" //usar item



//weapons\\
bind a "weapnext"
bind CTRL "weapon 5" //rocket
bind c "weapon 6" //rayo
bind e "weapon 7" //railgun
bind f "weapon 5" //rocket
bind q "weapon 3" //bombastic
bind r "weapon 8" //plasma
bind w "weapon 2" //machina
bind g "weapon 4" //pi¤as
bind t "weapon 9" //bfg
bind 2 "weapon 1" //gauntlet
bind 3 "weapon 1" //gauntlet
bind 4 "weapon 1" //gauntlet

seta cg_autoswitch "0" //cambio automatico al cojer el arma
seta cg_gun "0" //muestra / oculta el arma
seta cg_drawGun "0" //lo mismo




//rail q2
seta color "1" //color del rastro de la rail
seta r_railWidth "32" //128
seta cg_railTrailTime "1234"
seta r_railSegmentLength "32" //64
seta r_railCoreWidth "6" // 16

//rata
seta cl_yawspeed "140"
seta cl_pitchspeed "140"
seta sensitivity "5.8" //sensibilidad del mouse
seta m_pitch "0.024000" //velocidad al mirar arriba/abajo
seta cl_anglespeedkey "1.7" //velocidad del giro al correr
seta in_mouse "1" //asin usa dinput
seta cl_mouseAccel "0" //aceleracion del mouse off
seta m_filter "1" //filtro del raton
seta m_yaw "0.022" //velocidad del giro al correr



//cosas molestas

seta com_blood "0" //sangre en pantalla cuando te dan
seta cg_drawRewards "0" // medallas off
seta cg_lagometer "0" //visor del lag
seta con_notifytime "2" //tiempo de salida de los mensajes en la consola
seta com_hunkmegs "150" // default: 56 RAM pal q3
scr_conspeed "5000" //velocidad de bajada d la consola
// seta msg "1" //msg de single player (no va)
cg_centertime "0.3" //tiempo ke sale tu ranking en la pantalla
seta cg_drawAmmoWarning "1" //ver alerta de poka municion
seta cg_drawAttacker "0" //ver kien te ataka
seta g_knockback "1000" //movimiento del mu¤eco al ser alcanzado por un arma (depende del server pero biene bien keste a 0 )
seta cg_drawfps "1"

//croshair

seta cg_drawCrosshair "1" //tipo de crosshair , cruz
seta crosshairhealth "1" //ver el color del crosshair segun la vida ke tengas
seta cg_drawCrosshairNames "1" //ver el nombre del tio al keapuntas
seta cg_crosshairsize "27" //tamaño del crosshair



//dimitri

//r_smp "0" //pal dual (solo NT)

seta cg_footsteps "1" //sonido de los pasos
bind F4 exec name1.cfg
bind F2 exec name2.cfg
bind F12 "screenshot" //footooo
bind 5 "say =)" // :)
seta sv_hostname "117"
//seta cl_timeNudge "-30"
//seta snaps "25"
seta rate "8000" //rdsi
////seta cl_packetdup "1"
//seta cl_maxpackets "30"
seta com_maxfps "125" //limita las fps ke se muestran en pantalla
seta cl_run "1" //corriendo/andando
seta cg_drawtimer "1" //cronometro
seta cg_forceModel "1" //ver a todos iguales
seta cg_brassTime "0" //tiempo ke salen los cartuchos
seta cg_simpleItems "0" //ITEMS simples pal mal ping
seta cg_gibs "0" //gore al minimo
seta handicap "100" //vida a 100
seta sv_pure "0" //cuando haces de server admite paks ke no esten en el mismo


seta cg_predictItems "1" //predice si vas a cojer los items (util si hay lag)


//Zoom - Fov
seta cg_zoomfov "40" //angulo de vision de zoom
seta zoomsensitivity "2.3" //sensitivity del zoom
seta cg_fov "100" //angulo de vision

//me permite cambiar la funcion de "mouse3" apretando "b"
set zorun "bind mouse3 +zoom;wait;bind b vstr zorun2"
set zorun2 "bind mouse3 +speed;wait;bind b vstr zorun"

bind b vstr zorun




//balanceos del arma y el cuerpo desactivados
seta cg_runpitch "0"
seta cg_runroll "0"
seta cg_bobup "0"
seta cg_bobpitch "0"
seta cg_bobroll "0"

//video general

//seta r_mode "2" //512x768
seta r_mode "3"//640x480
seta r_displayrefresh "85" //refresco de pantalla
seta r_vertexLight "1" //luz vertex Tipo viseado Q1
seta cg_marks "0" //marcas en las paredes off
seta r_dynamiclight "0" //luces Dinamicas off
seta r_fastsky "1" //cielo rapido
seta r_allowExtensions "1" //permite extensiones del GL
seta r_texturebits "0" //bits de color d las texturas
seta r_colorbits "8" //bits de calidad del color
seta r_depthbits "8" //bits de algo :))
seta r_lodbias "2" //calidad de las armas baja
seta r_flares "0" //luces dinamicas en las antorchas off
seta cg_shadows "0" //sombras off
seta r_drawSun "0" //dibuja el sol en el cielo
seta r_detailtextures "0" //texturas detalladas off
seta r_picmip "5" //texturas de tamaño medio 1/9
seta r_simpleMipMaps "1" //mips simples
seta r_gamma "1.800000" //brillo
seta r_dlightBacks "0" //reflejo de las luces dinamicas
seta r_nolightcalc "1" //evita el calculo de las luces
seta r_flareSize "0" //tamaño de las antorchas
seta cg_draw3dIcons "1" // pone/kita los iconos de armor y municion 3D del HUD
seta r_noportals "1" //efecto de ver el destino al ke llegaras si miras en el teleporter
seta r_znear "4" //?? pero es bueno
seta r_showsky "0" //muestra / oculta el cielo
seta r_subdivisions "20" //Detalle geometriko al minimo
seta cg_viewsize "100" //vision pantalla completa
seta r_nocull "0" //evita ke se renderizen los objetos ocultos
seta r_intensity "1.40" //tipo el glmodulate del q2
seta r_lodscale "5" //establece la escala del nivel de detalle asignado en "r_lodbias"
seta r_logFile "2" //log del juego
seta r_ext_multitexture "1" //activa la multitextura
seta r_gamma "1.3"

//Super Contraste
seta r_ignorehwgamma "1"
seta cg_deferPlayers "1"
seta r_mapoverbrightbits "5"

//audio
//seta r_stereo "1" //sonido cutre
//seta s_khz "22" //mas cutre mas



//says
//
seta cg_teamChatTime "1500" //tiempo ke salen los mensajes de teamplay
bind 9 "messagemode" //modo de mensajes
seta cg_drawTeamOverlay "1" //cajilla de informacion del estado del ekipo


bind y "say_team ^3Weapon/Armor Available"
bind u "say_team ^2NEED ARMOR/WEAPON!!!"

bind n "say_team GO TO.."
bind i "say_team RaiLGuN"
bind k "say_team ^4QUAD!!!!!"
bind h "say_team YELLOW ARMOR"
bind j "say_team RED ARMOR!!"
bind m "say_team MegaHealt"
bind l "say_team Rocket Launcher"


//Scripts

//rocketjump

seta cl_pitchspeed "9999"
set rjump "+lookdown;wait;wait;+attack;+moveup;wait;wait;-attack;-moveup;-lookdown;wait;centerview;wait;centerview"
bind "f" vstr rjump

//antilag

set antilag_on "cg_lagometer 1; rate 1; snaps 1; cl_maxpackets 10; cl_nodelta 1; echo [AntiLag ON]; set antilag vstr antilag_off"
set antilag_off "cg_lagometer 0; rate 3500; snaps 20; cl_maxpackets 30; cl_nodelta 0; echo [AntiLag OFF]; set antilag vstr antiLag_on"
set antilag vstr antilag_on
bind * vstr antilag


vid_restart

---------------------------------------------------------------------



Opinión completa
SALTO NORMAL
No es muy útil para esquivar disparos, te recomindo que hagas los que te explico a continuación.

STRAFE JUMP
Pica alante + strafe + salto. Es un salto dífícil de manejaer, hay que practicarlo mucho.

BUNNYHOPPING
Hacer dos strafe jumps seguidos, ganas el doble de velocidad pero es de gran dificultad su manejo.
Comienza a correr, haz el strafe jump, justo cuando vas a volver al suelo vuelve a hacer un strafejump, irás deslizándote por el suelo a gran velocidad y saltaras horizontalmente el doble que en un salto normal.

SALTO CON ARMAS

Es utilizar el impulso del disparo de un arma para saltar más, solo se puede hacer con la Plasma, Grenade, Rocket y BFG. El problema que tiene es que quita vida, pero sus resultados son espectaculares.

Con la Plasma Gun o BFG consiste en mirar hacia abajo, y justo en el momento que disparas saltas, consiguiendo así un mayor salto vertical.

Grenade jumps.
Consiste en saltar justo cuando la granada va a explotar (3 segundos después de lanzarla)

-------------------------------------------------------------

miércoles, 26 de septiembre de 2007

Detectar sniffers en nuestra red

Muy buen articulo sobre como detectar sniffers en nuestra red, con herramientas para linux o para windows

lunes, 20 de agosto de 2007

Nociones de Debian

reenviado de http://bulma.net/body.phtml?nIdNoticia=1609


Una vez mas Bulma nos da la receta infadible para varios temas

¿Porqué existen tres versiones de Debian: stable, testing, unstable?

La estable (stable) es la versión que se ha liberado oficialmente como versión Debian y de las cuales existen CDs oficiales. Las otras dos versiones son los pasos previos para lograr una versión estable. Es decir, los paquetes nuevos de software empiezan por la inestable (unstable), si no tienen errores graves en 10 días, pasan a testing automáticamente. El proceso de hacer una estable es "manual", se decide una fecha de "congelación", donde no se añaden nuevos paquetes a testing y hay que solucionar los bugs de los existentes. Una vez que se solucionan todos, en Debian se decide o no pasarlos a estable y así liberar una versión nueva.

¿Qué son esos nombres como Potato, Woody, Sarge, Sid?

Actualmente (enero de 2003) la versión estable es Woody, que fue oficialmente lanzada hace unos pocos meses. La testing, que tenía el nombre Woody antes que ésta se convierta en stable, ahora se llama Sarge. La inestable dicen que siempre se llamará Sid, y que en realidad significa "System in Development". Pero puede ser sólo una leyenda urbana. :-)

¿Y de donde sacan los nombres?

Se empezaron a usar en la época que Bruce Perens era líder de Debian. Bruce trabajó en Pixar, la empresa de animación de Disney y que hizo la película "Toy Story".

Tengo Internet, ¿que paquetes debo instalar del CD?

Los mínimos para que te funcione la red, luego ya podrás instalar y actualizar todo desde los servidores de Debian.

¿Cómo se actualiza Debian?

En muy pocas palabras, con un "apt-get update" para actualizar la lista de paquetes y versiones, y con "apt-get dist-upgrade" para resolver dependencias y actualizar todos los paquetes instalados.

¿Cómo se instala el paquete xyz desde la red?

apt-get install xyz

¿Donde se especifican las listas de servidores para consultar y bajar paquetes con el apt-get?

En el fichero /etc/apt/sources.list.

¿Cuales es la configuración básica del sources.list para actualizar con el apt-get?

deb http://http.us.debian.org/debian stable main contrib non-free
deb http://non-us.debian.org/debian-non-US stable/non-US main contrib non-free
deb http://security.debian.org stable/updates main contrib non-free
deb-src http://http.us.debian.org/debian stable main contrib non-free
deb-src http://non-us.debian.org/debian-non-US stable non-US


¿Esos son los únicos servidores?

No, hay muchos, elige los que mejor te vayan. Por ejemplo los de Holanda suelen ir muy bien desde España (es sólo un ejemplo...). Basta cambiar los ".us." del nombre de los servidores por ".nl.":

deb http://http.nl.debian.org/debian stable main contrib non-free
deb-src http://http.nl.debian.org/debian stable main contrib non-free


¿Donde está la lista completa de mirrors de Debian?

En http://www.debian.org/mirror/list

¿Cómo puedo comprobar que servidores me van más rápidos?

Con el netselect. Mira este artículo en Bulma.

¿Donde se especifica la versión de Debian (stable, testing o unstable) que uso?

En el tercer argumento de cada línea en el sources.list.

¿Que diferencia hay entre especificar, por ejemplo, stable o Woody?

Mientras no se cambie la versión estable, no hay ninguna. Sin embargo cuando, por ejemplo, Sarge pase a estable, los que tenían especificado "stable" se actualizarán a la nueva, Sarge. En cambio, si tenías especificado Woody, seguirás con ella hasta el final de los días, o hasta que cambies el sources.list.

¿Que versión debo usar?

En principio la versión "pública y oficial" es la estable, pero todos sabemos que como Debian libera una estable con muy poca frecuencia, a los pocos meses se queda "anticuada". Ésto hace que mucha gente esté usando testing o unstable. Si vas a usarlo en un servidor de producción, lo mejor es la estable. Pero si tienes un poco más de "márgen", te consideras un "iniciado" en Linux y te interesa la última versión de todos los paquetes, puedes usar la testing, suele ir muy bien (en bulma.net usamos testing desde hace tiempo), salvo esporádicos problemas que se pueden solucionar fácilmente si tienes los conocimientos técnicos necesarios. Si por otro lado, te gusta vivir al borde del peligro y probar paquetes y versiones nuevas (y así ayudar a Debian reportando bugs), usa la unstable.

¿Cómo paso de la versión estable a la testing?

Modificando el sources.list. Hay que cambiar el tercer argumento (stable) por testing. Luego hay que hacer un apt-get update y apt-get dist-upgrade.

¿Cómo paso de la versión testing a la unstable?

Haciendo lo mismo que la anterior, pero en este caso habrá que especificar unstable.

He cambiado el tercer parametro de stable a unstable y he hecho apt-get update y apt-get upgrade. Sin embargo no ha actualizado todos los paquetes.

Que en este caso siempre hay que usar
apt-get dist-upgrade
ya que resuelve mejor las dependencias.

¿Cómo paso de testing a stable?

Uep!!! Alerta, estás intentando hacer un downgrade, aunque posible, puede ser muy complicado y hasta imposible si se han cambiado los ficheros de configuración. Quizás es mejor hacer una reinstalación desde CD, pero si realmente insistes, haz un man 5 apt_preferences y lee con mucha atención antes de intentarlo. No daré la solución, porque si te falla algo me echarás la culpa :-)-

¿Cómo paso de unstable a stable?

Ver pregunta anterior.

¿Y puedo pasar de unstable a testing?

Si tu sistema está funcionando correctamente (pero no quieres tantas actualizaciones diarias o más seguridad en el futuro), mi recomendación es: cambia el sources.list y especifica testing donde había unstable. A partir de ahora paciencia, a los pocos días tu sistema ya empezará a estar sincronizado con testing, a medida que los paquetes de testing se hayan actualizado con los que había en unstable. A las pocas semanas, a menos que haya un cambio muy gordo en Debian como el que está pasando ahora mismo con el gcc y las glibc, tu sistema ya "será" un testing.

¿Es posible mezclar paquetes de distintas ramas? ¿Es decir usar paquetes de unstable en testing, o de testing en stable?

Si, es posible. Mira este artículo y/o este otro.

¿Cuál es la mejor forma de reportar un bug?

Instala el paquete reportbug y ejecuta reportbug nombre_de_paquete.

¿Cómo se hace para reconfigurar el paquete xyz?

dpkg-reconfigure xyz

¿Cómo se hace para reinstalar el paquete xyz?

Normalmente no hace falta, a menos que ta hayas cargado algún programa esencial. Si es así basta con un

apt-get install --reinstall xyz

No quiero hacer una actualización completa, sólo instalar las últimas versiones de los paquetes abc y zyz. ¿Cómo lo hago?

apt-get install abc xyz

¿Cómo hago para ver todos los ficheros que se instalaron con el paquete xyz?

dpkg -L xyz

¿Cómo hago para saber a que paquete pertenece un fichero?

dpkg -S fichero

¿Cómo hago para buscar paquetes Debian usando palabras claves?

apt-cache search palabra1 palabra2...

¿Y para ver la descripción más detallada del paquete xyz?

apt-cache show xyz
o
apt-cache showpkg xyz

¿Cómo hago para desinstalar el paquete xyz?

apt-get remove xyz
o
dpkg -r xyz

¿Me quedan los ficheros de configuración del paquete xyz aunque lo desinstale?

Si, esos ficheros no son borrados.

Si vuelvo a reinstalar un paquete, ¿se mantienen sus ficheros de configuración anteriores?

Si.

Pero yo también quiero borrar esos ficheros de configuración. ¿Cómo hago?

apt-get remove --purge xyz
(es aquivalente un dpkg -P xyz).

¿Cómo reconfiguro la zona horaria?

tzconfig

¿Cómo hago para cambiar los locales del sistema?

dpkg-reconfigure locales

En modo consola no me reconoce el teclado español. ¿Cómo hago para cambiar la configuración del teclado?

dpkg-reconfigure console-data

Aún así no me reconoce el teclado en el KDE ¿Cómo lo hago?

La forma más sencilla es ir al panel de control de KDE, en el módulo "Regional & Accesibility::Keyboard Layout". Allí hay que habilitar el "Enable Keyboard Layouts" (arriba de todo) y luego seleccionar el teclado que corresponda.

¿Cómo hago para bajar el código fuente de los paquetes?

apt-get source xyz
Te bajará los fuentes, compuestos normalmente de un .tgz, .diff y un dsc, en el directorio actual de trabajo y te creará un directorio (xyz-versión con todo preparado para compilar.

¿Cómo compilo el código fuente?

Dentro del directorio que te ha generado el apt-get-source haces:
fakeroot dpkg-buildpackage
El fakeroot te permite compilar y generar los .deb sin necesidad de ser root. Al finalizar verás que en el directorio inmediato superior uno o varios .deb. Ya podrás hacer un dpkg -i

Cuando intento compilar el paquete xyz me indica que no puede compilar porque faltan instalar otros paquetes, ¿cómo lo soluciono?

Hay que instalar los paquetes necesarios para poder compilar un paquete:
apt-get build-depxyz

Me pasé a Sid, pero al intentar el dist-upgrade me dice que no puede instalar BBBB porque tiene conflictos con ficheros del paquete AAAA. ¿Cómo lo soluciono?

Lo más probable es que el paquete AAAA ya no exista y no haya sido seleccionado para removerlo (hey, ¡que ésto es Sid! :-). Por lo tanto, si estás seguro que el paquete AAAA no es esencial, puedes removerlo con el dpkg:

dpkg -r AAAA

He actualizado la Sid y las fuentes me salen de cualquier manera, a pesar que tengo bien configurado el fichero /etc/X11/XftConfig. ¿Qué pasa?

Que Debian ha pasado a las Xft2 y se configura en otro fichero. Mira éste artículo para más detalles e instrucciones.

¿Cómo arreglo las fuentes del Mozilla que me salen tan grandes y feas cuando lo uso desde el KDE?

Mira éste artículo.

¿Cómo es que hay gente que tiene paquetes instalados que no están en Debian? ¿Cómo los consigo?

Seguramente no son paquetes oficiales, porque están en pruebas todavía o porque violan las políticas de Debian. Mira éste artículo.

¿Cómo hago para borrar los ficheros de configuración (purge) que han quedado de paquetes que ya he removido?

Aquí va la solución en una línea:
dpkg -P `COLUMNS=200 dpkg -l |grep ^rc |awk '{print $2}' `

Por hoy suficiente :-), ya iré actualizando la lista cuando vea preguntas interesantes que sirvan a los novatos.

jueves, 26 de julio de 2007

Como Bajar Videos de youtube muy facil

Casi todos aveces vemos un videoen youtube y lo queremos, y para eso buscamos y buscamos programas para bajar videos de youtbe tardando un monton en buscar en google, mientras hay otros metodos de bajar un video de youtube en 3 segundos o menos , en fin lo que tenemos que hacer es esto:



1. entramos en youtube.com

2. buscamos el video

3. espremos que carge la lenia roja, para que cuando lo bajemos sea rapido

4. ahora añademos (kiss) despues de la www.(kiss aki la escribimos)...la direccion del video

5. nos carga una pagina de youtube que nos permite descargar el video, donde nos sale (download Now) y bajamos el video

6. ahora neceistamos un programa para reproducer el formato (.flv) osea el video. aki os dejo el link para descargar el programa (es freeware)

http://www.programas-gratis.net/php/programa2.php?id_programa=3216

7. y final feliz xd, ahora ya pdemos descargar cuantos videos keramos y sentarnos muy trankilos.

martes, 17 de julio de 2007

Mapa mental usando wikipedia

Muy recomendable pagina, que arma un mapa mental o mapa conceptual utilizando la wikipedia http://www.wikimindmap.org

Ejemplos de uso de lsof

 

lsof

lsof is the Linux/Unix über-tool. I use it most for getting network connection related information from a system, but that's just the beginning for this amazing and little-known application. The tool is aptly called lsof because it "lists open files". And remember, in Unix just about everything (including a network socket) is a file.

** lsof is also the Linux/Unix command with the most switches. It has so many it has to use both pluses and minuses.

usage: [-?abhlnNoOPRstUvV] [+|-c c] [+|-d s] [+D D] [+|-f[cgG]]  [-F [f]] [-g [s]] [-i [i]] [+|-L [l]] [+|-M] [-o [o]]  [-p s] [+|-r [t]] [-S [t]] [-T [t]] [-u s] [+|-w] [-x [fl]] [--] [names] 

As you can see, lsof has a truly staggering number of options. You can use it to get information about devices on your system, what a given user is touching at any given point, or even what files or network connectivity a process is using. lsof replaces my need for both netstat and ps entirely. It has everthing I get from those tools and much, much more.

Show Your Network Connections

Show all connections with -i

lsof -i

COMMAND  PID USER   FD   TYPE DEVICE SIZE NODE NAME dhcpcd 6061 root 4u IPv4 4510 UDP *:bootpc sshd 7703 root 3u IPv6  6499 TCP *:ssh (LISTEN) sshd 7892 root 3u IPv6  6757 TCP 10.10.1.5:ssh->192.168.1.5:49901 (ESTABLISHED) 

Show only TCP (works the same for UDP)

lsof -iTCP

COMMAND  PID USER   FD   TYPE DEVICE SIZE NODE NAME sshd 7703 root 3u IPv6 6499 TCP *:ssh (LISTEN) sshd 7892 root 3u IPv6 6757 TCP 10.10.1.5:ssh->192.168.1.5:49901 (ESTABLISHED) 

-i :port shows all networking related to a given port

lsof -i :22

COMMAND  PID USER   FD   TYPE DEVICE SIZE NODE NAME sshd 7703 root 3u  IPv6 6499 TCP *:ssh (LISTEN) sshd 7892 root 3u  IPv6 6757 TCP 10.10.1.5:ssh->192.168.1.5:49901 (ESTABLISHED) 

To show connections to a specific host, use @host

lsof -i@192.168.1.5

sshd 7892 root 3u IPv6 6757 TCP 10.10.1.5:ssh->192.168.1.5:49901 (ESTABLISHED) 

Show connections based on the host and the port using @host:port

lsof -i@192.168.1.5:22

sshd 7892 root 3u IPv6 6757 TCP 10.10.1.5:ssh->192.168.1.5:49901 (ESTABLISHED) 

Grepping for "LISTEN" shows what ports your system is waiting for connections on

lsof -i| grep LISTEN

iTunes     400 daniel   16u  IPv4 0x4575228  0t0 TCP *:daap (LISTEN) 

Grepping for "ESTABLISHED" shows current active connections

lsof -i| grep ESTABLISHED

firefox-b 169 daniel  49u IPv4 0t0 TCP 1.2.3.3:1863->1.2.3.4:http (ESTABLISHED) 


Working with Users, Processes, and Files

You can also get information on various users, processes, and files on your system using lsof:

Show what a given user has open using -u

lsof -u daniel

-- snipped -- Dock 155 daniel  txt REG   14,2   2798436   823208 /usr/lib/libicucore.A.dylib Dock 155 daniel  txt REG   14,2   1580212   823126 /usr/lib/libobjc.A.dylib Dock 155 daniel  txt REG   14,2   2934184   823498 /usr/lib/libstdc++.6.0.4.dylib Dock 155 daniel  txt REG   14,2    132008   823505 /usr/lib/libgcc_s.1.dylib Dock 155 daniel  txt REG   14,2    212160   823214 /usr/lib/libauto.dylib -- snipped -- 

See what files and network connections a command is using with -c

lsof -c syslog-ng
COMMAND    PID USER   FD   TYPE     DEVICE    SIZE       NODE NAME syslog-ng 7547 root  cwd    DIR    3,3    4096   2 / syslog-ng 7547 root  rtd    DIR    3,3    4096   2 / syslog-ng 7547 root  txt    REG    3,3  113524  1064970 /usr/sbin/syslog-ng syslog-ng 7547 root  mem    REG    0,0   0 [heap]  syslog-ng 7547 root  mem    REG    3,3  105435   850412 /lib/libpthread-2.4.so syslog-ng 7547 root  mem    REG    3,3 1197180   850396 /lib/libc-2.4.so syslog-ng 7547 root  mem    REG    3,3   59868   850413 /lib/libresolv-2.4.so syslog-ng 7547 root  mem    REG    3,3   72784   850404 /lib/libnsl-2.4.so syslog-ng 7547 root  mem    REG    3,3   32040   850414 /lib/librt-2.4.so syslog-ng 7547 root  mem    REG    3,3  126163   850385 /lib/ld-2.4.so -- snipped -- 

Pointing to a file shows what's interacting with that file

lsof /var/log/messages
COMMAND    PID USER   FD   TYPE DEVICE   SIZE   NODE NAME syslog-ng 7547 root    4w   REG    3,3 217309 834024 /var/log/messages 

The -p switch lets you see what a given process ID has open, which is good for learning more about unknown processes

lsof -p 10075
-- snipped -- sshd    10068 root  mem    REG    3,3   34808 850407 /lib/libnss_files-2.4.so sshd    10068 root  mem    REG    3,3   34924 850409 /lib/libnss_nis-2.4.so sshd    10068 root  mem    REG    3,3   26596 850405 /lib/libnss_compat-2.4.so sshd    10068 root  mem    REG    3,3  200152 509940 /usr/lib/libssl.so.0.9.7 sshd    10068 root  mem    REG    3,3   46216 510014 /usr/lib/liblber-2.3 sshd    10068 root  mem    REG    3,3   59868 850413 /lib/libresolv-2.4.so sshd    10068 root  mem    REG    3,3 1197180 850396 /lib/libc-2.4.so sshd    10068 root  mem    REG    3,3   22168 850398 /lib/libcrypt-2.4.so sshd    10068 root  mem    REG    3,3   72784 850404 /lib/libnsl-2.4.so sshd    10068 root  mem    REG    3,3   70632 850417 /lib/libz.so.1.2.3 sshd    10068 root  mem    REG    3,3    9992 850416 /lib/libutil-2.4.so -- snipped -- 

The -t option returns just a PID

lsof -t -c Mail
350 
ps aux | grep Mail
daniel 350 0.0 1.5 405980 31452 ?? S  Mon07PM 2:50.28 /Applications/Mail.app 


Advanced Usage

Using-a allows you to combine search terms, so the query below says, "show me everything running as daniel connected to 1.1.1.1"

lsof -a -u daniel -i @1.1.1.1
bkdr   1893 daniel 3u  IPv6 3456 TCP 10.10.1.10:1234->1.1.1.1:31337 (ESTABLISHED) 

Using the -t and -c options together you can HUP processes

kill -HUP `lsof -t -c sshd`

You can also use the -t with -u to kill everything a user has open

kill -9 `lsof -t -u daniel`

lsof +L1 shows you all open files that have a link count less than 1, often indicative of a cracker trying to hide something

lsof +L1
(hopefully nothing) 


Conclusion

This primer just scratches the surface of lsof's functionality. For a full reference, run man lsof or check out the online version. I hope this has been useful to you, and as always, comments and corrections are welcomed.


sábado, 26 de mayo de 2007

Fw: Cambiar el Tamaño de todas las fotos de una carpeta


Pequeño truco para ImageMagick
 
ir a la carpeta en donde estan las fotos y ejecutar
 
 c:\imagenestmp> for %a in (*.jpg) do convert -geometry 800x800 %a %a
 
y listo!!!!
Ojo, haganlo siempre sobre una carpeta con archivos copiados, sino sobreescribiran los originales
 

lunes, 21 de mayo de 2007

Manejo del arranque de windows vista

Navegando por ahi, encontre este excelente articulo que posteo completamente de este sitio , y cuya autoria firma José Manuel Tella Llop


Manejo del Boot en Windows Vista
-------------------------------------

El sistema de Boot (arranque) de Windows Vista (beta2 y posteriores) tiene ya poco que ver con los sistemas tradicionales y el boot.ini que estábamos acostumbrados a manejar.

Recordemos un poco el sistema de XP (y sistemas basados en núcleo NT):

1) El sector de arranque de la partición (que se establece al formatear) tiene "hardcoded" -es decir, incorporado "a pelo" en su interior y no parametrizable- el primer archivo que se carga: NTLDR

2) En C:\ existen tres ficheros que son necesarios para la carga, esté donde esté situado el sistema operativo: NLTDR, NTDETECT.COM y BOOT.INI

3) El BOOT.INI es un archivo de parámetros. Archivo de texto que podemos tocar y modificar y que contiene los posibles sistemas operativos y sus lo calizaciones.

* La secuencia de arranque: la Bios carga el MBR del disco el cual contiene las particiones del disco y la marca de partición activa. El miniprograma del MBR, decide entonces cargar el primer sector (sector de Boot) de la partición marcada como activa. Esto carga el punto 1) anterior. Ese sector es un miniprograma que carga el archivo que tiene "harcoded" en su interior. En este caso el NTLDR. Es el responsable entre otra cosas de leer el BOOT.INI y si existe más de una entrada (más de un sistema operativo) nos mostrará un menú y nos permitirá seleccionar.

Hasta aquí todo sencillo. Si formateábamos C:\ siempre podíamos luego reconstruir a mano el BOOT.INI sin más que saber su sintaxis (ver un articulo mío titulado"Instalación de un Sistema Operativo.doc" publicado en http://www.multingles.net/jmt.htm para profundizar sobre el tema).


MODIFICACIONES INTRODUCIDAS POR WINDOWS VISTA
---------------------------------------------

Windows Vista cambia drásticamente la filosofía de arranque.

1) Se modifica el sector de boot de la partición el cual contendrá ahora "harcoded" el nombre del primer fichero a cargar y ejecutar. En este caso BOOTMGR

2) En C:\ se graba oculto el archivo BOOTMGR

3) En C: se crea una carpeta llamada Boot cuyo contenido es:

Directory of C:\Boot

20/10/2005 19:29 .
20/10/2005 19:29 ..
22/10/2005 23:06 262.144 BCD
22/10/2005 23:06 29.696 BCD.LOG <----- está oculto. 20/10/2005 19:29 EFI
13/09/2005 21:29 1.024 bootfix.bin
05/10/2005 12:44 248.320 fixfat.exe
05/10/2005 12:44 260.096 fixntfs.exe
05/10/2005 09:16 370.176 hibrsm32.efi
05/10/2005 09:16 371.712 hibrsm32.exe
05/10/2005 09:23 534.016 hibrsm64.exe
05/10/2005 09:16 359.936 memtest.exe
05/10/2005 15:44 1.556 windowscodeintegrity.luacdf

He marcado el archivo oculto BCD.LOG porque realmente es el que va a hacer con Windows Vista y los posibles sistemas operativos que tengamos, la función que hacia el BOOT.INI en sistemas anteriores (XP y previos).


¿QUE SUCEDE SI TENEMOS XP E INSTALAMOS VISTA?
--------------------------------------------

Imaginemos que tenemos XP y otros sistemas operativos de núcleo NT instalados en nuestra máquina, y por tanto ya poseemos un BOOT.INI. Para nuestro ejemplo, pongamos que tenemos esto:

[boot loader]
timeout=10
default=multi(0)disk(0)rdisk(5)partition(1)\WINDOWS

[operating systems]
multi(0)disk(0)rdisk(5)partition(2)\WINDOWS="Windows XP Professional x64 Edition [E]" /NOEXECUTE=OPTIN /FASTDETECT
multi(0)disk(0)rdisk(0)partition(2)\WINDOWS="Windows XP Professional x64 Edition" /NOEXECUTE=OPTIN /FASTDETECT
multi(0)disk(0)rdisk(4)partition(2)\WINDOWS="Windows Server 2003, Standard [R]"/NOEXECUTE=OPTOUT /FASTDETECT
multi(0)disk(0)rdisk(4)partition(1)\WINDOWS="Windows Server 2003, Enterprise" /FASTDETECT /PAE /NOEXECUTE=OPTOUT
multi(0)disk(0)rdisk(3)partition(1)\WINDOWS="Microsoft Windows XP Professional [K]" /FASTDETECT /NOEXECUTE=OPTIN
multi(0)disk(0)rdisk(5)partition(1)\WINDOWS="Microsoft Windows XP Professional [I]" /FASTDETECT /NOEXECUTE=OPTOUT
multi(0)disk(0)rdisk(6)partition(3)\WINDOWS="Microsoft Windows XP Professional [Q]" /FASTDETECT
C:\BOOTSECT.DOS="Previous Operating System"

Al instalar Windows Vista, nos machará el sector de Boot de la partición, nos grabará en C:\ el archivo BOOTMGR (ejecutable) y nos creará la carpeta Boot rellenándola con los archivos citados anteriormente.

El proceso de arranque, en este caso, nos mostrará un menú con 3 líneas:

Legacy (pre-Longhorn) Microsoft Windows Operating System
Microsoft Windows
Legacy (pre-Longhorn) Microsoft Windows Operating System

La primera línea, si la pulsamos, iniciará el proceso de carga de los antiguos sistemas operativos: es decir no sacará el menú clásico que está embebido en el BOOT.INI y podremos arrancar cualquiera de nuestros anteriores sistemas.

La segunda línea (existirá una de este estilo por cada Windows Vista instalado en nuestra máquina) nos permitirá arrancar directamente Windows Vista.

La última línea (si existiese), nos permitirá arrancar un viejo MSDOS en el sistema. Es decir, lo que realmente permite es arrancar el viejo BOOTSECT.DOS si existiese en nuestra máquina, correspondiente en el ejemplo que estoy poniendo, justo a la ultima línea del BOOT.INI.


¿DÓNDE ESTÁN LAS LINEAS DE ESTE MENU Y COMO PODEMOS MODIFICAR SU CONTENIDO?
---------------------------------------------------------------------------

Aquí empieza el primer problema. No existe un fichero de texto equivalente al BOOT.INI. Modificar el orden de carga (es decir, qué línea estará preseleccionada), puede hacerse de una manera similar a Windows XP: botón derecho en mi PC, propiedades, pestaña de avanzado, botón de inicio y recuperación. Allí podremos establecer qué sistema operativo será seleccionado por defecto y el tiempo en segundos que esperará el menú. Pero no existe ya el botón de "editar" porque no se puede editar nada: no hay archivo de texto como antes era el BOOT.INI.

Dónde conserva Windows Vista estos datos es en el fichero oculto BCD.LOG que está en la carpeta Boot. Pero este archivo es un archivo binario, que no sólo contiene eso, sino también código ejecutable. No podemos editarlo ya que lo destruiremos.

Existe una utilidad de comando de línea que veremos a continuación: BCDEDIT.EXE la cual nos permite la modificación. Pero no es sencilla ni tan siquiera intuitiva.

Imaginemos que instalamos en nuestra máquina dos Windows Vista: un vista de 32 y uno de 64. El menú nos quedará:

Legacy (pre-Longhorn) Microsoft Windows Operating System
Microsoft Windows
Microsoft Windows
Legacy (pre-Longhorn) Microsoft Windows Operating System

El cual contiene dos líneas"Microsoft Windows". La primera corresponde al 64 bits y la segunda al 32. Lo lógico es que queramos que cada línea muestre el texto correspondiente para no inducirnos a error. Es decir, que contenga, por ejemplo:

Legacy Microsoft Windows Operating System
Microsoft Windows Vista 64
Microsoft Windows Vista 32
Legacy Microsoft Windows Operating System

La manera de hacerlo es mediante el comando de línea BCDEDIT.EXE el cual modificará el archivo BCD.LOG (oculto) que he citado anteriormente.

Al ejecutar BCDECIT.EXE en una consola de comandos, nos mostrará (en nuestro ejemplo anterior):

Windows Boot Manager
--------------------
Identifier: {bootmgr}
Type: 10100002
Device: partition=C:
Description: Windows Boot Manager
Locale: ENG-US
Inherit options: {emssettings}
{dbgsettings}
Default: {legacy}
Display order: {legacy}
{5a45d108-42ed-11da-ab93-b0ac505a9c5d}
{c59756d6-419f-11da-bb58-e429938b8537}
Timeout: 10

Windows Legacy OS Loader
------------------------
Identifier: {legacy}
Type: 10300006
Device: partition=C:
Path: \ntldr
Description: Legacy (pre-Longhorn) Microsoft Windows Operating System

Windows Boot Loader
-------------------
Identifier: {5a45d108-42ed-11da-ab93-b0ac505a9c5d}
Type: 10200003
Device: partition=D:
Path: \Windows\system32\winload.exe
Description: Microsoft Windows
Locale: ENG-US
Inherit options: {emssettings}
{dbgsettings}
Windows device: partition=D:
Windows root: \Windows
No Execute policy: OptIn

Windows Boot Loader
-------------------
Identifier: {c59756d6-419f-11da-bb58-e429938b8537}
Type: 10200003
Device: partition=L:
Path: \Windows\system32\winload.exe
Description: Microsoft Windows
Locale: ENG-US
Inherit options: {emssettings}
{dbgsettings}
Windows device: partition=L:
Windows root: \Windows
No Execute policy: OptIn

Se pueden modificar los parámetros, textos, etc con el mismo comando. Incluso añadir entradas. Si se ejecuta con /? nos dará la sintaxis. En nuestro caso, para modificar únicamente los literales a mostrar en el menú de arranque debemos hacer:

BCDEDIT -set {5a45d108-42ed-11da-ab93-b0ac505a9c5d} Description "Microsoft Windows Vista 64"

Es decir {ID a modificar}y a continuación lo que queremos modificar. Puede ser la Descripcion, el Path, las políticas de No Execute, etc.....


POSIBLES PROBLEMAS SI INSTALAMOS XP/W2003 CON POSTERIORIDAD
-----------------------------------------------------------

El problema surge porque la instalación de XP / W2003 o cualquier sistema operativo anterior nos machacará el sector de Boot y por tanto ya no apuntará a los cargadores de Windows Vista.

Para recuperarlo, dentro de la carpeta Boot tenemos dos programas:

FIXFAT
FIXNTFS

Deberemos ejecutar el correspondiente a nuestro sistema de archivos (FAT O NTFS) que exista en C:\ (independientemente de cómo sea el sistema de archivos de las instalaciones de Windows). Ejecutándolo con /? os dará la sintaxis de cómo lanzarlo para recuperar el sector de Boot de Windows Vista.

martes, 8 de mayo de 2007

Configuracion de Mikrotik

Pagina en portugues de como configurar paso a paso placas Mikrotik

lunes, 23 de abril de 2007

Cambio ID en todas las tablas de una bbdd

Algunas veces es necesario cambiar un ID en todas las tablas de una bbdd, con este script lo podemos lograr

 

IF OBJECT_ID ( 'usp_cambioid', 'P' ) IS NOT NULL

DROP PROCEDURE usp_cambioid;

GO

 

create proc usp_cambioid @idviejo decimal, @idnuevo decimal

as

begin

IF @idviejo IS NULL or @idnuevo is null

BEGIN

PRINT 'ERROR: falta un parametro. use usp_cambioid idviejo, idnuevo'

RETURN

END

begin tran

DECLARE @Tabla varchar(100), @Campo varchar(100), @sql varchar(500), @sqlacu varchar(4000)

declare @count int

set @count = 0

set @sql = ''

set @sqlacu = 'set nocount on' +char(13)+'begin tran '

declare ctablas cursor for

SELECT sysObjects.name as 'Tabla', sysColumns.name as 'Campo'

FROM sysObjects, sysColumns

WHERE sysObjects.id = sysColumns.id and sysObjects.xtype = 'u'

and sysColumns.name = ('id') order by sysObjects.name

open ctablas

FETCH NEXT FROM ctablas into @Tabla, @Campo;

WHILE @@FETCH_STATUS = 0

BEGIN

set @sql = 'update ' + @tabla + ' set ' + @campo + ' = ' +

cast(@idnuevo as varchar(100)) + ' where ' + @campo + ' = ' +

cast(@idviejo as varchar(100))

set @sqlacu = @sqlacu +char(13)+ @sql

FETCH NEXT FROM ctablas into @Tabla, @Campo;

END;

set @sqlacu = @sqlacu + char(13) + 'IF (@@ERROR <> 0) BEGIN ' + char(13) +

' print ''error'''+ char(13) + ' rollback tran ' + char(13) + 'end' + char(13)+

'commit tran'

execute (@sqlacu)

--print @sqlacu

CLOSE ctablas;

DEALLOCATE ctablas;

end

commit tran

Excelente articulo sobre shells scripts en linux

Linux Shell Scripting Tutorial v1.05r3
A Beginner's handbook

Copyright © 1999-2002 by Vivek G. Gite <vivek@nixcraft.com>

origen http://freeos.com/guides/lsst/index.html

nixCraft Logo :: Next generation *nix services
(Formally know as vivek-tech.com)

Linux Shell Scripting Tutorial - A Beginner's handbook

Table of Contents

Chapter 1: Quick Introduction to Linux
What Linux is?
Who developed the Linux?
How to get Linux?
How to Install Linux
Where I can use Linux?
What Kernel Is?
What is Linux Shell?
How to use Shell
What is Shell Script ?
Why to Write Shell Script ?
More on Shell...
Chapter 2: Getting started with Shell Programming
How to write shell script
Variables in shell
How to define User defined variables (UDV)
Rules for Naming variable name (Both UDV and System Variable)
How to print or access value of UDV (User defined variables)
echo Command
Shell Arithmetic
More about Quotes
Exit Status
The read Statement
Wild cards (Filename Shorthand or meta Characters)
More commands on one command line
Command Line Processing
Why Command Line arguments required
Redirection of Standard output/input i.e. Input - Output redirection
Pipes
Filter
What is Processes
Why Process required
Linux Command(s) Related with Process
Chapter 3: Shells (bash) structured Language Constructs
Decision making in shell script ( i.e. if command)
test command or [ expr ]
if...else...fi
Nested ifs
Multilevel if-then-else
Loops in Shell Scripts
for loop
Nested for loop
while loop
The case Statement
How to de-bug the shell script?
Chapter 4: Advanced Shell Scripting Commands
/dev/null - to send unwanted output of program
Local and Global Shell variable (export command)
Conditional execution i.e. && and ||
I/O Redirection and file descriptors
Functions
User Interface and dialog utility-Part I
User Interface and dialog utility-Part II
Message Box (msgbox) using dialog utility
Confirmation Box (yesno box) using dialog utility
Input (inputbox) using dialog utility
User Interface using dialog Utility - Putting it all together
trap command
The shift Command
getopts command
Chapter 5: Essential Utilities for Power User
Preparing for Quick Tour of essential utilities
Selecting portion of a file using cut utility
Putting lines together using paste utility
The join utility
Translating range of characters using tr utility
Data manipulation using awk utility
sed utility - Editing file without using editor
Removing duplicate lines from text database file using uniq utility
Finding matching pattern using grep utility
Chapter 6: Learning expressions with ex
Getting started with ex
Printing text on-screen
Deleting lines
Coping lines
Searching the words
Find and Replace (Substituting regular expression)
Replacing word with confirmation from user
Finding words
Using range of characters in regular expressions
Using & as Special replacement character
Converting lowercase character to uppercase
Chapter 7: awk Revisited
Getting Starting with awk
Predefined variables of awk
Doing arithmetic with awk
User Defined variables in awk
Use of printf statement
Use of Format Specification Code
if condition in awk
Loops in awk
Real life examples in awk
awk miscellaneous
sed - Quick Introduction
Redirecting the output of sed command
How to write sed scripts?
More examples of sed
Chapter 8: Examples of Shell Scripts
Logic Development:
Shell script to print given numbers sum of all digit
Shell script to print contains of file from given line number to next given number of lines
Shell script to say Good morning/Afternoon/Evening as you log in to system
Shell script to find whether entered year is Leap or not
Sort the given five number in ascending order (use of array)
Command line (args) handling:
Adding 2 nos. suppiled as command line args
Calculating average of given numbers on command line args
Finding out biggest number from given three nos suppiled as command line args
Shell script to implement getopts statement.
Basic math Calculator (case statement)
Loops using while & for loop:
Print nos. as 5,4,3,2,1 using while loop
Printing the patterns using for loop.
Arithmetic in shell scripting:
Performing real number calculation in shell script
Converting decimal number to hexadecimal number
Calculating factorial of given number
File handling:
Shell script to determine whether given file exist or not.
Screen handling/echo command with escape sequence code:
Shell script to print "Hello World" message, in Bold, Blink effect, and in different colors like red, brown etc.
Background process implementation:
Digital clock using shell script
User interface and Functions in shell script:
Shell script to implements menu based system.
System Administration:
Getting more information about your working environment through shell script
Shell script to gathered useful system information such as CPU, disks, Ram and your environment etc.
Shell script to add DNS Entery to BIND Database with default Nameservers, Mail Servers (MX) and host
Integrating awk script with shell script:
Script to convert file names from UPPERCASE to lowercase file names or vice versa.
Chapter 9: Other Resources
Appendix - A : Linux File Server Tutorial (LFST) version b0.1 Rev. 2
Appendix - B : Linux Command Reference (LCR)
About the author
About this Document

jueves, 22 de marzo de 2007

Las virtudes y maldades del SQL dinámico

Un artículo SQL de Erland Sommarskog, SQL Server MVP.

Traducido por Simon Hayes.

Este artículo está disponible también en coreano y alemán,gracias al ASP MVP Jongshin Kim, y a Frank Kalis, respectivamente.

En los varios newsgroups en los que se discute el SQL Server de Microsoft, mucha gente pregunta cómo ejecutar consultas tales como las siguientes:

SELECT * FROM @tabla SELECT @columna FROM tabla SELECT * FROM tabla WHERE x IN (@listado) 

Frecuentemente, alguien responde diciendo utiliza el SQL dinámico y adjunta un ejemplo sencillo, pero no se suele mencionar los inconvenientes de esta solución.

En este artículo analizo el uso del SQL dinámico en procedimientos almacenados en el SQL Server - es una característica potente, pero tienes que utilizarla con cuidado. Empiezo describiendo por qué utilizamos procedimientos almacenados, antes de tratar el uso del SQL dinámico. Examino los conflictos entre los beneficios de los procedimientos almacenados y los efectos del SQL dinámico, incluido el muy conocido problema de seguridad que es la inyección de SQL . Propongo unas buenas prácticas para el código SQL, y termino examinando los casos en que la gente suele utilizar el SQL dinámico, por motivos correctos o incorrectos; en los casos dónde el SQL dinámico no es la solución más adecuada, ofrezco otras posibilidades.

Contenido:

¿Por qué utilizar procedimientos almacenados?
EXEC() y sp_executesql
EXEC()
sp_executesql
Qué método utilizar
Los cursores y el SQL dinámico
El SQL dinámico y los procedimientos almacenados
La inyección de SQL: un problema grave de seguridad
Las buenas prácticas de código y el SQL dinámico
Cuándo utilizar o no el SQL dinámico
select * from @tabla
select * from ventas + @yymm
update tabla set @columna = @valor where keycol = @key
select * from @bbdd + '..tabla'
select * from tabla where columna in (@listado)
select * from tabla where @criterios
Criterios de búsqueda dinámicos
select * from tabla order by @columna
select top @n from tabla order by @columna
create table @tabla
Servidores vinculados
OPENQUERY()
Longitud de columna dinámica
Menciones y contacto
Historia de cambios

¿Por qué utilizar procedimientos almacenados?

Antes de examinar el SQL dinámico, quiero aclarar por qué utilizamos procedimientos almacenados, ya que podríamos desarrollar una aplicación que envía consultas de SQL ad hoc directamente del cliente o de la capa intermedia en lugar de ejecutar procedimientos. No utilizamos los procedimientos almacenados para divertirnos, sino porque llevan varias ventajas.

1. Los permisos

Un procedimiento almacenado es la solución clásica para manejar el acceso de los usuarios a los datos. Un usuario no debería tener los permisos para ejecutar SELECT, INSERT, UPDATE y DELETE directamente - mediante una herramienta como el Query Analyzer podría hacer cualquier cosa. Por ejemplo, una persona podría aumentar su salario en la base de datos del departamento de personal... Ya que un procedimiento almacenado se ejecuta con los permisos de su propietario, el usuario no necesita permisos explícitos en las tablas.

Pero hoy tenemos más posibilidades. Desde el SQL Server 7, puedes dar acceso a una función de aplicación que se activa con una contraseña escondida en la aplicación. Aún más seguro sería un servidor intermedio como COM+, puesto que los usuarios ni tendrían acceso al servidor MSSQL.

Incluso cuando no adoptas una de esas soluciones, los procedimientos siguen siendo importantes para la seguridad.

2. Almacenar los planes de consulta en la caché

Otra ventaja importante de los procedimientos almacenados es el rendimiento. Cuando un procedimiento almacenado se ejecuta por la primera vez, el SQL Server crea un plan de consulta y lo almacena en la caché para poder reutilizarlo en el futuro. El SQL Server quita el plan de la caché cuándo sea demasiado viejo, o cuándo sea necesario crear un nuevo plan - esto puede ocurrir durante la ejecución del mismo procedimiento, pero no describo el proceso en este artículo -.

SQL Server almacena planes de consulta para instrucciones SQL que no están en un procedimiento almacenado, y es capaz de parametrizar la consulta:

    SELECT * FROM pubs..authors WHERE state = 'CA'     go     SELECT * FROM pubs..authors WHERE state = 'WI' 

La segunda consulta utiliza el plan de consulta generado para la consulta anterior, ya que internamente SQL Server lo almacena así:

    SELECT * FROM pubs..authors WHERE state = @1 

El SQL Server no puede parametrizar consultas complejas, y he observado que puede ser que no se encuentre un plan en la caché cuando la única diferencia entre las dos consultas reside en el espacio blanco. Claro está que aunque el SQL Server utiliza parametrización de una manera eficaz, es más probable que vuelva a utilizar el plan de consulta para un procedimiento almacenado.

El uso de la caché es más importante para pequeños procedimientos - o instrucciones - que se ejecutan frecuentemente y rápidamente, ya que consagrar 500ms a la creación de un plan de consultas tendría un impacto notable. Pero si el procedimiento se ejecuta durante veinte minutos, es aceptable dedicar tres segundos a la creación de un plan.

Para quiénes todavía tienen el SQL Server 6.5, cabe destacar que esa versión almacena planes de consulta únicamente para procedimientos almacenados, no para las consultas de SQL ad hoc.

3. Reducir el tráfico en la red

El tráfico en la red también afecta el rendimiento. Digamos que tienes una consulta compleja que alcanza 50 líneas de código, pero cambias sólo un par de valores en la cláusula WHERE para cada ejecución. Poner esta consulta en un procedimiento reduce la cantidad de bytes que se transmite por la red, lo que puede mejorar el rendimiento si hay mucho tráfico.

La diferencia es aún más evidente cuando tienes una serie de instrucciones SELECT/INSERT/UPDATE interrelacionadas. Un procedimiento almacenado te permite utilizar tablas temporales o variables de tabla para procesar todos los datos en el servidor. Si utilizas instrucciones de SQL ad hoc, tienes que enviar todos los datos entre el servidor y el cliente o la capa intermedia. - En realidad, esto no es cierto, ya que puedes crear tablas temporales sin un procedimiento almacenado, pero en este caso puedes tener problemas con el connection pooling y los recordsets -.

4. Utilizar parámetros de salida

Si quieres ejecutar una consulta de SQL ad hoc que devuelve una sola fila, debes utilizar un conjunto de resultados. Un procedimiento almacenado ofrece la posibilidad de utilizar parámetros de salida, lo que resulta ser mucho más rápido. Si ejecutas sólo una consulta, no notarás la diferencia, pero si ejecutas la consulta siguiente 2000 veces, es probable que ganes mucho devolviendo @key como parámetro de salida en lugar de un conjunto de resultados:

    INSERT tbl (...) VALUES (...)     SET @key = @@identity 

5. Establecer bloques de lógico

Ahora, no hablo de seguridad ni de rendimiento, sino de manejar bien tu código. Los procedimientos almacenados permiten evitar la tarea de generar instrucciones SQL en tu código cliente. Pero sería igualmente posible crear funciones en tu aplicación que generan el código SQL según tus parámetros - aunque puede que el código SQL quede escondido en las profundidades de tu código cliente -.

Existe un caso especial: escribes procedimientos almacenados para administradores y DBAs, y por lo cual siempre serán ejecutados desde el Analizador de Consultas. En este caso, no hay otra manera de agrupar tu código en bloques de lógico.

6. Manejar las dependencias

En un sistema complejo que incluye unos centenares de tablas, muchas veces quieres saber dónde se usa una tabla o columna determinada, por ejemplo si quieres cambiar una columna de alguna manera. Si todo el código está en procedimientos almacenados, sólo tienes que buscar en el texto de los procedimientos para encontrar las referencias. O puedes recrear la base de datos sin la columna o tabla, para ver qué efecto tiene el cambio. Existen también la tabla de sistema sysdepends y el procedimiento almacenado de sistema sp_depends que lo utiliza. Pero es difícil conseguir que los datos en sysdepends sean siempre correctos.

Si envías instrucciones SQL directamente desde tu aplicación, el problema es mucho más grande. Debes buscar en una cantidad mayor de código, y si el nombre de la columna tiene un nombre común como status, pues la tarea puede ser casi imposible. Y en este caso la tabla sysdepends no sirve para nada.

EXEC() y sp_executesql

En el SQL Server, el SQL dinámico de ejecuta con EXEC() o sp_executesql.

EXEC()

Utilizar EXEC() es la manera más sencilla de ejecutar SQL dinámico:

    SELECT @tabla = 'ventas' + @año + @mes     EXEC('SELECT * FROM ' + @tabla) 

Puede que este ejemplo te parezca extremadamente básico, pero quiero destacar unos puntos importantes. Primero, son los permisos del usuario actual que están en vigor al ejecutar la instrucción, incluso cuando la instrucción se halla dentro de un procedimiento almacenado. Segundo, EXEC() es similar al EXEC que se utiliza para ejecutar un procedimiento almacenado, pero ejecuta un lote de instrucciones SQL en lugar de un procedimiento almacenado. Sin embargo, en ambos casos el lote SQL - o procedimiento almacenado - se ejecuta en otro alcance del procedimiento que lo ejecutó, pues hay que notar los puntos siguientes:

  • Dentro del lote SQL no tienes acceso a las variables locales o a los parámetros del procedimiento almacenado externo
  • Si usas USE en el lote SQL, esto no afecta al procedimiento almacenado externo
  • Si creas tablas temporales en el lote SQL se quitan al acabar el lote - como si fuera un procedimiento almacenado - pues no las puedes utilizar en el procedimiento almacenado externo. Sin embargo, el lote SQL tiene acceso a las tablas temporales creadas en el procedimiento almacenado externo.
  • Si cambias las opciones SET en el lote SQL, esto no afecta al procedimiento almacenado externo
  • El plan de consultas para el lote SQL no es parte del plan de consultas para el procedimiento almacenado externo. En cuanto a la caché, el lote SQL es igual a una consulta de SQL ad hoc enviada desde el cliente.
  • Si sale un error que termina el lote - por ejemplo un rollback en un desencadenador - se termina el lote, pero también el procedimiento almacenado externo, y el procedimiento almacenado que lo ejecutó, y así...

A diferencia de la ejecución de un procedimiento almacenado, no puedes utilizar parámetros, y tampoco existe un valor del estado de retorno. El valor de @@ERROR depende de la última instrucción en el lote SQL, pues si hay un error en el lote pero luego una instrucción que se ejecuta sin error, @@ERROR tendrá el valor 0.

EXEC() existe desde el SQL Server 6.0.

Nota que EXEC(@sql) no tiene nada que ver con EXEC @sp, lo que ejecuta un procedimiento almacenado cuyo nombre es el valor de @sp.

sp_executesql

sp_executesql existe desde el SQL Server 7, y lleva la ventaja que admite el uso de parámetros de entrada y salida con la cadena de SQL dinámico. El ejemplo siguiente demuestra el uso de un parámetro de salida:

    DECLARE @sql nvarchar(4000),    -- nvarchar(MAX) en SQL 2005.             @col sysname,             @min varchar(20)     SELECT @col = N'au_fname'     SELECT @sql = N'SELECT @min = convert(varchar(20), MIN(' + @col +                   N')) FROM authors'     EXEC sp_executesql @sql, N'@min varchar(20) OUTPUT', @min OUTPUT     SELECT @min 

Ya ves que es mucho más fácil asignar un valor de tu instrucción de SQL dinámico a una variable local con sp_executesql que con EXEC() - lo puedes hacer mediante una instrucción INSERT EXEC(), pero no es una solución muy cómoda -.

El primer parámetro para sp_executesql es una instrucción SQL. El tipo de datos del parámetro es ntext, pues tienes que utilizar una variable de tipo nvarchar - ya que no se admite la declaración de parámetros ntext -. Si tu instrucción es una cadena literal, pon N antes de la comilla simple para indicar una cadena Unicode. La sentencia puede incluir parámetros que empiezan con una arroba - @ - y que no están relacionados con las variables en al alcance actual. La instrucción SQL es como todas las demás, es decir que los parámetros se admiten únicamente donde la sintaxis SQL los permite. O sea, no puedes utilizar parámetros para los nombres de tablas o columnas , pues si los determinas dinámicamente, tienes que incluirlos en la cadena que será ejecutada.

El segundo parámetro para sp_executesql es un listado de declaraciones de variables en la forma habitual, incluyendo parámetros de salida y valores predeterminados - parece que el tema de los parámetros de salida no está en los Books Online del SQL Server -. El listado es de tipo ntexttambién, y debe incluir todas las variables utilizadas en la sentencia SQL.

Los demás parámetros para sp_executesql son los que has declarado en el listado de parámetros, en el mismo orden - o puedes escribir los nombres explícitamente -.

sp_executesql suele ser más útil que EXEC() por varios motivos. Utilizando el sp_executesql no tienes que contar con la autoparametrización de SQL Server, ya que tú suministras los parámetros. Así, es más probable que el SQL Server utilice un plan de consultas que ya existe en la caché - aunque las diferencias en el espacio en blanco todavía pueden impedir esto -. Hablaré de las otras ventajas de sp_executesql en mi análisis de inyección del SQL y las buenas prácticas de código.

Los puntos que destaqué en la sección sobre EXEC() también se aplican en el caso de sp_executesql:

  • El lote SQL está en su propio alcance, y no tienes acceso a las variables en el procedimiento almacenado exterior
  • Los permisos del usuario actual se aplican
  • El uso de USE no afecta al procedimiento almacenado exterior
  • Tablas temporales creadas por el lote SQL no están accesibles desde el procedimiento almacenado exterior
  • El uso de opciones SET afecta únicamente al lote SQL
  • Si el lote ejecutado por sp_executesql se termina debido a un error, el procedimiento almacenado exterior también se termina
  • El valor de @@ERROR indica el estatus de la última sentencia en el lote SQL

Según dice Books Online, el valor del estado de retorno de sp_executesql puede ser 0 - éxito - o 1 - fallo -. Pero parece que en realidad, el valor del estado de retorno es el valor de @@ERROR, por lo menos en el SQL Server 2000.

Consulta Books Online para tener todos los detalles sobre sp_executesql. Mira también el artículo 262499 en el Microsoft Knowledge Base, donde se explica el uso de parámetros OUTPUT.

Qué método utilizar

Si utilizas el SQL dinámico frecuentemente, pues sp_executesql sería mejor porque el SQL Server puede utilizar el plan de consultas en la caché, y también gozas del uso de parámetros. Claro que no lo puedes utilizar si sigues trabajando con la versión el SQL Server 6.5, pero tampoco es posible si tu sentencia SQL es demasiado larga para una variable nvarchar(4000). EXEC() permite concatenar cadenas:

    EXEC(@sql1 + @sql2) 
Al ejecutar sp_executesql puedes utilizar una sola variable, puesto que el TSQL no permite el uso de expresiones como parámetros cuando ejecutas un procedimiento almacenado. Pero si absolutamente necesitas una consulta parametrizado, pues puedes anidar sp_executesql en una cadena para EXEC():
    DECLARE @sql1 nvarchar(4000),             @sql2 nvarchar(4000),             @state char(2)     SELECT @state = 'CA'     SELECT @sql1 = N'SELECT COUNT(*)'     SELECT @sql2 = N'FROM authors WHERE state = @state'     EXEC('EXEC sp_executesql N''' + @sql1 + @sql2 + ''',                              N''@state char(2)'',                              @state = ''' + @state + '''') 

Aun es posible anidarlo cuando tienes parámetros de salida, mediante un INSERT-EXEC:

    CREATE TABLE #result (cnt int NOT NULL)     DECLARE @sql1  nvarchar(4000),             @sql2  nvarchar(4000),             @state char(2),             @mycnt int     SELECT @state = 'CA'     SELECT @sql1 = N'SELECT @cnt = COUNT(*)'     SELECT @sql2 = N'FROM authors WHERE state = @state'     INSERT #result (cnt)         EXEC('DECLARE @cnt int               EXEC sp_executesql N''' + @sql1 + @sql2 + ''',                                  N''@state char(2),                                     @cnt   int OUTPUT'',                                  @state = ''' + @state + ''',                                  @cnt = @cnt OUTPUT               SELECT @cnt')     SELECT @mycnt = cnt FROM #result 

Puede que prefieras evitar este laberinto de comillas utilizando solamente EXEC() - el código puede ser un poco más claro como verás cuando presento la función definida por el usuario quotestring() -.

En el SQL Server 2005, puedes emplear el nuevo tipo de datos nvarchar(max) para la variable @sql, y no tienes que poner sp_executesql dentro de un EXEC().

Los cursores y el SQL dinámico

Normalmente, deberías evitar los cursores, pero la gente suele preguntar cómo utilizar el SQL dinámico con los cursores, por lo cual presento un ejemplo. No es posible decir DECLARE CURSOR EXEC(), pues tienes que incluir la sentencia entera en una cadena para usar el SQL dinámico:

    SELECT @sql = 'DECLARE my_cur CURSOR FOR SELECT col1, col2, col3 FROM ' + @tabla     EXEC sp_executesql @sql 

Nota que aquí no puedes utilizar un cursor local - los cursores locales se quitan al terminar el alcance - pero Anthony Faull me ha señalado que lo puedes hacer mediante una variable de tipo cursor:

    DECLARE @my_cur CURSOR     EXEC sp_executesql           N'SET @my_cur = CURSOR FOR SELECT name FROM dbo.sysobjects; OPEN @my_cur',           N'@my_cur cursor OUTPUT', @my_cur OUTPUT     FETCH NEXT FROM @my_cur 

Se declara una variable de tipo cursor, y ya ves que la puedes manipular como cualquier otro parámetro. Debo confesar que nunca había visto un uso para las variables de tipo cursor, hasta recibir el ejemplo que Anthony Faull me envió muy amablemente.

El SQL dinámico y los procedimientos almacenados

Ahora podemos examinar los motivos por los cuales utilizamos procedimientos almacenados, y el impacto del SQL dinámico. Vamos a servirnos del procedimiento siguiente como ejemplo:

    CREATE PROCEDURE general_select @tblname nvarchar(127),                                     @key     key_type AS -- key_type is char(3)     EXEC('SELECT col1, col2, col3           FROM ' + @tblname + '           WHERE keycol = ''' + @key + '''') 

Pronto verás que este procedimiento no sirve para nada, porque no permite beneficiar de las ventajas de los procedimientos almacenados. Sería igual construir la sentencia SELECT en tu aplicación y enviarla directamente al SQL Server.

1. Los permisos

Si no puedes permitir que los usuarios accedan directamente a las tablas, no puedes utilizar el SQL dinámico y no hay más qué decir. En algunos casos, puede ser que los usuarios tengan los permisos para SELECT, pero no utilices el SQL dinámico para sentencias de INSERT/UPDATE/DELETE, salvo cuándo estás absolutamente seguro de que los permisos no son un problema. Quiero subrayar que me refiero únicamente a las tablas permanentes; no hay nunca cuestiones de permisos al acceder a tablas temporales.

Si utilizas funciones de aplicaciones, o si tienes una capa intermedia como COM+ que no permite que los usuarios accedan directamente a la base de datos, pues probablemente no tienes que preocuparte mucho por los permisos. No obstante, todavía existen problemas de seguridad que debes considerar, como veremos en las sección sobre la inyección de SQL.

Finalmente, si escribes código para los usuarios sysadmin, pues claro está que no habrá problemas de permisos.

2. Almacenar los planes de consulta en la caché

Ya hemos visto que el SQL Server almacena y reutiliza planes de consulta para las consultas de SQL ad hoc y también para los procedimientos almacenados, aunque suele ser más eficaz utilizar procedimientos. En el SQL Server 6.5 estaba claro que el SQL dinámico era más despacio, ya que el SQL Server tenía que compilar cada consulta dinámica, pero en las versiones más recientes la cuestión no es tan clara.

Por ejemplo, al revisar el procedimiento general_select mencionado arriba, se nota que el plan de consulta será almacenado en la caché, y puede ser parametrizado para los valores de @tblname. Pero sería exactamente igual si generaras la consulta dentro de tu aplicación.

A pesar de todo eso, cabe destacar el hecho de que el uso apto del SQL dinámico puede mejorar el rendimiento. Supongamos que tienes una consulta compleja en un procedimiento almacenado muy largo, y el mejor plan de consulta depende mucho de la cantidad y valores de los datos en tus tablas. Puedes convertir esta consulta en SQL dinámico, esperando que el SQL Server no utilice el plan de la caché - si hay en tu consulta una tabla temporal, es muy poco probable que utilice el plan -. También puedes poner la consulta en otro procedimiento separado, pero el código puede ser más claro siendo todo en un solo procedimiento. Como siempre, esto supone que tu estrategia de permisos admite el uso del SQL dinámico.

3. Reducir el tráfico en la red

En las dos secciones anteriores, hemos visto que el SQL dinámico dentro de un procedimiento almacenado no tiene ninguna ventaja sobre una consulta de SQL puro elaborado dentro de una aplicación. Pero desde el punto de vista de la red, es cierto que hay una ventaja: el SQL dinámico en un procedimiento almacenado no tiene ningún impacto en al red. En el caso de general_select, no ganas mucho, ya que el número de bytes en la consulta dinámica es casi igual al número que se necesita para ejecutar el procedimiento.

Pero digamos que tienes una consulta compleja en que seis tablas se están combinando con condiciones complejas, y que una de las tablas se llama algo así como ventas0101, ventas0102 etc., según la demanda del usuario. El diseño de las tablas no es bueno, como pronto veremos, pero supongamos que no lo puedes cambiar por el momento. Si utilizas un procedimiento almacenado, sólo envías un parámetro con el año y mes al servidor, en lugar de enviar la consulta entera. Si se ejecuta la consulta una vez cada hora, no ganas mucho, pero si se ejecuta todos los cinco segundos y si la red no tiene la capacidad adecuada, es muy probable que ganes algo.

4. Utilizar parámetros de salida

Si escribes un procedimiento únicamente para poder utilizar un parámetro de salida, no afectas nada sirviéndote del SQL dinámico. Pero también puedes aprovecharte de parámetros de salida sin escribir tu propio procedimiento ejecutando sp_executesql directamente desde tu aplicación.

5. Establecer bloques de lógico

No puedo añadir mucho sobre este tema, pero quiero señalar que cuando utilizas los procedimientos almacenados, es mejor esconder los detalles de tu base de datos dentro de tus procedimientos, así que se convierten en una capa de abstracción. Haciéndolo así, tener nombres de tablas como parámetros no es una buena idea, salvo en el caso de herramientas para los sysadmins.

6. Manejar las dependencias

El SQL dinámico impide el mantenimiento eficaz de las dependencias, ya que siempre esconde una referencia que luego no existe en sysdepends. La dependencia tampoco se revela al crear una nueva base de datos sin el objeto referenciado. Pero si puedes evitar el uso de tablas o columnas como parámetros, todavía puedes buscar en tu código SQL para saber si hay referencias a una tabla o columna determinada. Por lo tanto, si utilizas el SQL dinámico, intenta siempre referirte a las tablas y columnas en el código de los procedimientos.

La inyección de SQL: un problema grave de seguridad

La inyección de SQL es una técnica que permite que un atacante manipule tu código SQL para ejecutar algo que tú no quieres ejecutar. Te expones a ataques mediante la inyección de SQL cuando pasas algo directamente del cliente a tu código SQL, o sea SQL dinámico en un procedimiento, o sea SQL generado en tu aplicación. La inyección de SQL afecta todas las bases de datos relacionales, no sólo SQL Server.

Consideramos este ejemplo:

    CREATE PROCEDURE search_orders @custname varchar(60) = NULL,                                    @prodname varchar(60) = NULL AS     DECLARE @sql nvarchar(4000)     SELECT @sql = 'SELECT * FROM orders WHERE 1 = 1 '     IF @custname IS NOT NULL         SELECT @sql = @sql + ' AND custname LIKE ''' + @custname + ''''     IF @prodname IS NOT NULL         SELECT @sql = @sql + ' AND prodname LIKE ''' + @prodname + ''''     EXEC(@sql) 

Digamos que los valores para @custname y @prodname vienen directamente de un campo de input, y que un atacante suministra el valor siguiente para @custname:

    ' DROP TABLE orders -- 

La cadena SQL que resulta del input es la siguiente:

    SELECT * FROM orders WHERE 1 = 1  AND custname LIKE '' DROP TABLE orders--' 

¿Has visto el texto rojo? Este método de atacar la base no tendrá éxito en todos los casos. Es poco probable que un usuario normal conectado directamente al SQL Server tenga los permisos necesarios para quitar una tabla. Pero si el usuario está conectado a través de una aplicación web, y si el servidor web tiene permisos más amplios, pues el ataque tendrá éxito. Incluso si el atacante no puede llevar a cabo su primer ataque, todavía puede ejecutar instrucciones SQL que no debería.

La estrategia del atacante es la siguiente. Primero, intenta una comilla simple en el campo de entrada para ver qué pasa. Si el servidor devuelve un error de sintaxis, el atacante sabe que la vulnerabilidad existe, pues intentará descubrir si se necesita algún carácter o palabra clave para terminar la consulta, antes de añadir su propio comando. Finalmente, añade un carácter que indica un comentario, para que el SQL Server no evalúe la cadena entera, así evitando un error de sintaxis. El atacante puede intentar también un punto y coma, que desde el MSSQL 7 es un terminador de instrucción opcional. Si esto devuelve un error, puede haber encontrado algo como el general_select, y si el valor para el parámetro @tblname viene directamente del valor suministrado por el usuario, el atacante puede crear la cadena siguiente:

    some_table WHERE keycol = 'ABC' DELETE orders 

No olvides que existen más puntos de ataque que los obvios campos de input - si tienes parámetros en un URL que sirven como argumentos para procedimientos almacenados, un atacante los utilizará también.

Si imaginas que un atacante necesita no sólo conocimientos técnicos sino también suerte para encontrar una vulnerabilidad de este tipo, recuerda que hay demasiados hackers con demasiado tiempo. La inyección de SQL es un problema grave de seguridad, y debes defenderte contra ella: existen dos defensas.

  • Da sólo los permisos más mínimos a los usuarios en el SQL Server. Si tu aplicación conecta desde una capa intermedia, utiliza un login normal, que tiene los permisos de SELECT como máximo. Así evitas que un desarrollador descuidado o inexperto cree una vulnerabilidad a la inyección de SQL.
  • Hay prácticas de código muy básicas que puedes utilizar, y las veremos en la próxima sección del artículo.

Insisto en el hecho de que la inyección de SQL afecta no sólo los procedimientos almacenados, sino también el código en aplicaciones donde generas consultas, lo que puede ser aún más vulnerable - ya que muchas veces tus variables de cadena no tienen una longitud fija -. Incluso si utilizas procedimientos almacenados, puede que generes las sentencias EXEC para ejecutarlos, y estas sentencias también son vulnerables.

Las buenas prácticas de código y el SQL dinámico

Escribir código para ejecutar el SQL dinámico puede parecer fácil, pero en realidad debes tener cierta disciplina para no perder el control del código. Si no tienes cuidado, tu código acaba siendo complicado, así que es muy difícil leerlo, detectar problemas, y mantenerlo. Miramos de nuevo el procedimiento almacenado horroroso que es general_select:

    CREATE PROCEDURE general_select @tblname nvarchar(127),                                     @key     key_type AS -- key_type is char(3)     EXEC('SELECT col1, col2, col3           FROM ' + @tblname + '           WHERE keycol = ''' + @key + '''') 

Quizás has visto las múltiples comillas simples, y te has preguntado ¿para qué sirve todo eso? El TSQL es uno de los lenguajes en los que tienes que duplicar un terminador de cadena para que sea evaluado como una cadena normal. Las cuatro comillas consecutivas ('''') son una cadena cuyo valor es una sola comilla ('). Y esto es un ejemplo sencillo - el asunto puede ser aún peor.

Un error muy frecuente es el siguiente:

    EXEC('SELECT col1, col2, col3           FROM' + @tblname + '           WHERE keycol = ''' + @key + '''') 

¿Has visto que falta un espacio tras la palabra FROM? Al crear el procedimiento no te saldrá ningún error, pero al ejecutarlo el SQL Server te dice que no existen las columnas keycol, col1, col2 y col3. Puesto que sabes que sí existen en la tabla que indicaste, te quedarás muy desconcertado. Pero mira el código que se genera, utilizando los parámetros foo y abc:

    SELECT col1, col2, col3 FROMfoo WHERE keycol = 'abc' 

La sintaxis es correcta, porque FROMfoo puede ser un alias para col3.

Ya he dicho que no deberías nunca utilizar nombres de tablas o columnas como parámetros, pero ya que estoy hablando de las buenas prácticas, te lo repito. Cuando empiezas escribir los procedimientos almacenados, deben ser el único código en que te refieres a objetos en la base de datos - salvo cuando te refieres a los procedimientos si mismos, claro -. No obstante, aquí te presento una versión revisada de general_select que permite demostrar unas buenas prácticas para utilizar el SQL dinámico:

    CREATE PROCEDURE general_select @tblname nvarchar(127),                                     @key key_type,                                     @debug bit = 0 AS     DECLARE @sql nvarchar(4000)     SET @sql = 'SELECT col1, col2, col3                 FROM ' + quotename(@tblname) + '                 WHERE keycol = @key'     IF @debug = 1 PRINT @sql     EXEC sp_executesql @sql, N'@key key_type', @key = @key 

Como ya ves, he hecho varios cambios:

  • He puesto @tblname dentro de la función quotename() para protegerme contra un ataque de inyección de SQL mediante esta variable. Hay más detalles sobre quotename() debajo.
  • He añadido un parámetro @debug, pues si me sale un error extraño es fácil comprobar la cadena que se está ejecutando.
  • En lugar de meter el valor de @key directamente en una cadena con EXEC(), me sirvo de sp_executesql así que @key es un parámetro. Es otra manera de protegerme contra la inyección de SQL.

quotename() es una función del sistema que existe desde el SQL Server 7. Devuelve el valor de entrada delimitado por el terminador de cadena que seleccionas y duplica terminadores dentro de la cadena. Por omisión, el terminador es un corchete, ya que la función se usa principalmente con los nombres de objetos, pero puedes seleccionar una comilla simple. Pues si prefieres usar EXEC() por motivo alguno, puedes utilizar quotename() para protegerte contra la inyección de SQL. Vamos a mirar un ejemplo revisado tirado del procedimiento search_orders que hemos visto en la discusión sobre la inyección de SQL:

    IF @custname IS NOT NULL         SELECT @sql = @sql + ' AND custname LIKE ' + quotename(@custname, '''') 

Nota que el parámetro de entrada para quotename() es nvarchar(128), pues no sirve para cadenas largas. Si tienes el SQL Server 2000, puedes utilizar esta función definida por el usuario en su lugar:

     CREATE FUNCTION quotestring(@str nvarchar(1998)) RETURNS nvarchar(4000) AS     BEGIN         DECLARE @ret nvarchar(4000),         @sq  char(1)         SELECT @sq = ''''         SELECT @ret = replace(@str, @sq, @sq + @sq)         RETURN(@sq + @ret + @sq)     END 

- En el SQL Server 2005, reemplaza 1998 y 4000 con MAX -.

La utilizas así:

    IF @custname IS NOT NULL         SELECT @sql = @sql + ' AND custname LIKE ' + dbo.quotestring(@custname) 

En el SQL Server 7, tienes que reescribir quotestring en forma de un procedimiento almacenado. El SQL Server 6.5 no tiene la función replace(), pues no puedes hacer mucho en este caso. - Quiero señalar que fue el SQL Server MVP Steve Kass que me propuso la idea de utilizar quotename() o una función definida por el usuario -.

Otra posibilidad para evitar un laberinto de comillas anidadas es aprovecharte del hecho de que el T-SQL utiliza dos caracteres para delimitar cadenas. Más precisamente, si la opción QUOTED_IDENTIFIER está OFF, también puedes usar una comilla doble ("). El valor predeterminado de esa opción depende de la situación, pero es mejor que esté ON, y debe estar ON para utilizar vistas indizadas e índices en columnas computadas. No es una alternativa perfecta, entonces, pero si eres consciente de estas advertencias, puedes hacer esto:

    CREATE PROCEDURE general_select @tblname nvarchar(127),                                     @key     key_type,                                     @debug   bit = 0 AS     DECLARE @sql nvarchar(4000)      SET @sql = 'SET QUOTED_IDENTIFIER OFF                 SELECT col1, col2, col3                 FROM ' + @tblname + '                 WHERE keycol = "' + @key + '"'     IF @debug = 1 PRINT @sql     EXEC(@sql) 

Resulta ser más fácil leer el código cuando hay dos caracteres que delimitan las cadenas - la comilla simple para el comando SQL y la comilla doble para los valores.

Sería preferible utilizar sp_executesql y quotename() para protegerte contra la inyección de SQL, pero puede una solución adecuada para tareas de sysadmin - ya que la inyección de SQL no sería un problema - y puede ser la mejor solución para el SQL Server 6.5.

Quiero concluir esta sección diciendo que seguir las prácticas que he identificado supone aumentar considerablemente la complejidad de tu código SQL, así que deberías pensar dos veces antes de utilizar el SQL dinámico.

Cuándo utilizar o no el SQL dinámico

Mientras leyendo los newsgroups dedicados al SQL Server, se ve casi todos los días alguien que responde a una pregunta diciendo utiliza el SQL dinámico sin mencionar el impacto en los permisos y el uso de la caché. Aunque muchas de estas preguntas parecen no tener otra solución - si se entiende bien la pregunta - muchas veces existe un problema de lógico o diseño que tiene una mejor solución que es absolutamente diferente.

En esta sección, examino unos casos donde podrías servirte del SQL dinámico - en algunos, puede ser la solución más adecuada, pero en otros es una mala solución, y en otros casos la pregunta viene de un concepto básico completamente equivocado.

select * from @tabla

Se pregunta muy frecuentemente por qué lo siguiente no funciona:

    CREATE PROCEDURE my_proc @tablename sysname AS         SELECT * FROM @tablename 

Ya hemos visto que se puede escribir un procedimiento almacenado para hacer esto, pero también hemos visto que ese procedimiento no tendría sentido. Si esto te parece la buena manera de programar el SQL, pues olvida completamente los procedimientos almacenados.

Parece que la gente quiere hacer esto por varios motivos. Hay personas poco experimentadas en la programación SQL que tiene mucha experiencia en otros lenguajes como C++, VB etc. En aquellos lenguajes, la parametrización es una buena idea, ya que tener código muy genérico para facilitar el mantenimiento es una virtud.

Pero resulta que cuando hablamos de objetos en una base de datos, este principio no se aplica - debes considerar cada tabla y columna como un objeto único y constante. ¿Por qué? Pues porque al crear un plan de consultas, cada tabla tiene sus propias estadísticas y relaciones, y el SQL Server debe tratarlas individualmente. Además, si tienes un modelo complejo, es importante saber qué tablas y columnas están en uso. Sin lugar a dudas, al empezar utilizar nombres de tablas y columnas como parámetros, es el momento en que pierdes el control.

Pues si quieres escribir algo como el procedimiento sobredicho para no teclear tanto, has malentendido. Es mucho mejor escribir diez o veinte procedimientos almacenados, aunque sean muy similares.

No obstante, si tienes muchas consultas complejas, puede que ganes mucho tiempo manteniendo un sólo procedimiento en lugar de varios. En este caso, puedes considerar el uso de un pre-processor como en C/C++: el código reside en un include file, que luego se utiliza para crear múltiples procedimientos almacenados para las varias tablas.

select * from ventas + @yymm

Este caso es algo similar al caso anterior, pero mientras que antes se suponía que existía una cantiadad fija de tablas, parece que hay gente cuyos sistemas crean tablas dinámicamente, p.ej. cada mes una nueva tabla para los datos de ventas. Tener un procedimiento para cada tabla no es una solución práctica, ni siquiera con la ayuda de un pre-processor.

¿No hay otra solución que el SQL dinámico? Pues tenemos que distanciarnos un poco para ver que el concepto básico es incorrecto, ya que el modelo es defectuoso. En sistemas basados en Access o en archivos de datos muy simples, puede ser necesario tener una tabla para cada mes, pero en SQL Server u otro sistema de base de datos de alto rendimiento, no suele ser necesario. El SQL Server y sus competidores fueron diseñados para manejar grandes cantidades de datos, y para acceder a los data mediante las columnas claves. Es decir, el año y el mes son parte de la restricción PRIMARY KEY en una sola tabla denominada ventas.

Si tienes un sistema de legado, diseñar de nuevo la base de datos puede costar mucho - pero mantener el código más complejo basado en el SQL dinámico también to costará -. Si estás desarrollando un nuevo sistema, ni pienses en crear tablas dinámicamente: será muy difícil mantener el código para utilizar las tablas, y si creas las tablas muy frecuentemente - por ejemplo una tabla para cada carrito en una tienda web - corres el riesgo de crear un punto de contención en las tablas del sistema, lo que perjudica el rendimiento.

Quizá no estés convencido, y estás pensando pero tengo millones de filas, la base no funcionará si están todas en la misma tabla. Bueno, si tienes muchas filas pues sí tienes que preocuparte, pero no hablo de unos millones de filas, lo que representa una tarea básica para el SQL Server, siempre que has bien escogido tus índices. Pero si tienes unos centenares de millones de filas, puede que tengas que considerar otras opciones: el SQL Server 2000 ofrece posibilidades como vistas dividas e incluso vistas dividas distribuidas que te permiten dividir tus datos en varias tablas pero seguir accediéndoles como si estuvieran en una sola tabla. (Nota que aunque he hablado del número de filas, para simplificar el tema, pero lo que realmente importa es el tamaño total de la tabla, lo que depende de la longitud de las filas.)

update tabla set @columna = @valor where keycol = @key

Aquí, quieres cambiar los datos en una columna que seleccionas en el momento de la ejecución. La sintaxis de la sentencia arriba es correcta en T-SQL, pero lo que ocurre es que el valor de @valor se asigna a @colname una vez para cada fila afectada en la tabla.

En este caso, utilizar el SQL dinámico supone que el usuario tiene los permisos de UPDATE en la tabla, lo que se suele evitar, pues sería mejor no utilizarlo. Aquí hay otra solución que no es nada complicado:

    UPDATE tbl     SET    col1 = CASE @colname WHEN 'col1' THEN @value ELSE col1 END,            col2 = CASE @colname WHEN 'col2' THEN @value ELSE col2 END,            ... 

Si no conoces la expresión CASE, por favor consulta los Books Online. Es una característica muy potente del lenguaje SQL.

Pero hay que preguntarse otra vez por qué la gente quiere hacer esto. Quizá sus tablas sean algo como éste:

    CREATE TABLE products   (prodid   prodid_type NOT NULL,                             prodname name_type   NOT NULL,                             ...                             sales_1  money       NULL,                             sales_2  money       NULL,                             ...                             sales_12 money       NULL,                             PRIMARY KEY (prodid)) 

Puede ser mejor crear una segunda tabla para las columnas sales_n:

    CREATE TABLE product_sales (prodid prodid_type NOT NULL,                                 month  tinyint     NOT NULL,                                 sales  money       NOT NULL,                                 PRIMARY KEY (prodid, month)) 

select * from @bbdd + '..tabla'

En este caso la tabla reside en otra base de datos cuyo nombre se determina dinámicamente. Por lo visto, la gente tiene varios motivos para querer hacer esto, y cada motivo tiene una solución distinta.

Acceder a datos en otra base de datos. Si por algún motivo tu aplicación necesita dos bases de datos, deberías evitar fijar los nombres en tu código, ya que tendrás un problema si quieres crear un entorno de prueba en el mismo servidor. Se suele pensar a sacar el nombre de la otra base de una tabla de configuración, y generar una consulta dinámicamente. Pero hay otra solución, si puedes poner tu código en un procedimiento en la otra base de datos:

    SET @sp = @dbname + '..some_sp'     EXEC @ret = @sp @par1, @par2... 

Es decir, la @sp contiene el nombre del procedimiento almacenado.

Hacer algo en cada base de datos. Esto suena a una tarea de sysadmin, pues el SQL dinámico puede ser una buena solución ya que no hay que considerar ni los permisos ni la caché. No obstante, existe otra posibilidad, que es usar sp_MSforeachdb:

    sp_MSforeachdb 'SELECT ''?'', COUNT(*) FROM sysobjects' 

Como puedes imaginar, sp_MSforeachdb utiliza el SQL dinámico internamente, así que la ventaja es que no tienes que escribir un bucle tú mismo, pero debo señalar que sp_MSforeachdb no está documentado en Books Online, y entonces Microsoft no lo apoya.

Una base de datos "master". De vez en cuando he encontrado gente que tiene muchas bases de datos con la misma estructura: me imagino que tienen una empresa de hosting y cada base de datos sería para un cliente distinto. Las normas de su empresa no permiten que todos los clientes compartan una sola base de datos. Estas personas quieren evitar la tarea de mantener todas las bases, pues imaginan una sola base "master" donde pueden colocar sus procedimientos almacenados. Pero en este caso, todos los procedimientos almacenados utilizarían el SQL dinámico, que también sería una pesadilla.

Hay dos alternativas. Puedes servirte de la propia base de datos master del SQL Server, e instalar los procedimientos de tu aplicación como procedimientos del sistema. Pero Microsoft no apoya esto, y hay problemas de seguridad, pues no te lo aconsejo.

La otra solución sería crear los procedimientos en cada base de datos, y desarrollar un sistema de manejar y distribuir tus objetos SQL. Tienes que hacerlo en todo caso, para poder modificar las tablas, y si los procedimientos están en todas las bases de datos, puedes proporcionar versiones nuevas a los clientes que las quieren, sin afectar a los clientes que prefieren cambiar más lentamente, por cautela o por mezquines. Cómo desarrollar un tal sistema es cuestión de la gestión de configuración, un tema importante que no puedo analizar en este artículo. Pero te ofrezco dos consejos: el SQL Server Resource Kit incluye una herramienta para crear objetos SQL directamente del Visual SourceSafe. Y yo puedo ofrecerte AbaPerls, unas herramientas que he desarrollado para satisfacer las necesidades de mi propio entorno: es gratis - freeware - y está disponible aquí http://www.abaris.se/abaperls/.

select * from tabla where columna in (@listado)

Una pregunta muy frecuente, y la respuesta utiliza el SQL dinámico es demasiado comuna. El SQL dinámico es una mala solución: no deberías necesitar permisos SELECT para hacer esto, y si la lista tiene muchos elementos, el rendimiento será fatal con el SQL dinámico.

¿Qué es la solución? Conviertes la cadena en filas en una tabla con una función definida por el usuario o un procedimiento almacenado. Aquí no doy ningún ejemplo, ya que he escrito otro artículo en el que analizo muchos métodos para solucionar este problema, e incluyo los datos de rendimiento para cada uno. ¡El SQL dinámico llega último! Es un artículo muy largo, pero hay vínculos a la solución más adecuada para cada versión de SQL Server: Arrays and Lists in SQL Server.

select * from tabla where @criterios

Si piensas escribir un procedimiento almacenado como éste, olvídalo:

    CREATE PROCEDURE search_sp @condition varchar(8000) AS         SELECT * FROM tbl WHERE @condition 

Si haces esto, todavía estás generando las consultas SQL en tu aplicación en lugar de adoptar los procedimientos almacenados. Ve también la próxima sección.

Criterios de búsqueda dinámicos

Muy frecuentemente, un usuario debe poder ejecutar una consulta seleccionando varios criterios distintos. Cualquier desarrollador se da cuenta de que escribir una consulta estática para cada combinación de parámetros es imposible, y la mayor parte de los desarrolladores creen que poner todas las posibles condiciones en una sola consulta compleja no proporcionará un buen rendimiento.

Aquí, sí, el SQL dinámico debe ser la mejor solución. Siempre que los permisos no sean un problema, utiliza el SQL dinámico, que es lo mejor para el rendimiento y el mantenimiento. He escrito otro artículo - Dynamic Search Conditions - en el que tengo ejemplos de escribir este tipo de consulta mediante el SQL dinámico y estático.

select * from tabla order by @columna

Es fácil evitar el SQL dinámico así:

    SELECT col1, col2, col3     FROM   tbl     ORDER  BY CASE @col1                 WHEN 'col1' THEN col1                 WHEN 'col2' THEN col2                 WHEN 'col3' THEN col3               END 

Como he dicho antes, si no conoces la expresión CASE, consulta los Books Online.

Nota que si las columnas tienen tipos de datos distintos, no puedes utilizarlas juntas en la misma expresión CASE, ya que una expresión CASE tiene sólo un tipo de datos. Pero puedes solucionar este problema así:

    SELECT col1, col2, col3     FROM   tbl     ORDER  BY CASE @col1 WHEN 'col1' THEN col1 ELSE NULL END,               CASE @col1 WHEN 'col2' THEN col2 ELSE NULL END,               CASE @col1 WHEN 'col3' THEN col3 ELSE NULL END 

El SQL Server MVP Itzik Ben-Gan ha escrito un buen artículo sobre este tema en el ejemplar de marzo 2001 de SQL Server Magazine, en el que propone otras soluciones.

select top @n from tabla order by @columna

Una solución básica sin utilizar el SQL dinámico:

    CREATE PROCEDURE get_first_n @var int WITH RECOMPILE AS     SET ROWCOUNT @var     SELECT *     FROM   authors     ORDER  BY au_id     SET ROWCOUNT 0 

Quizás hayas oído que el SQL Server no hace caso de SET ROWCOUNT al crear un plan de consultas, y es cierto en el SQL Server 6.5 - en todo caso, en esa versión no tienes TOP, pues no hay otra solución - pero sí lo hace en el SQL Server 7 y 2000. Pero tienes que utilizar un parámetro - no una variable local - con SET ROWCOUNT o el optimizador no sabrá el valor y puede decidir escánear la tabla.

No olvides poner SET ROWCOUNT 0 tras la instrucción SELECT para que las instrucciones siguientes no sean afectadas.

Books Online tiene unas advertencias sobre el use de SET ROWCOUNT, y aconseja no utilizar SET ROWCOUNT con instrucciones DELETE, INSERT y UPDATE No sé precisamente por qué, pero creo que un INSERT en una tabla temporal con SET ROWCOUNT en vigor está bien. Un INSERT un una tabla que tiene desencadenadores puede sorprenderte, ya que el SET ROWCOUNT afecta el desencadenador también.

Considera también por qué quieres hacerlo. Si quieres limitar los datos que se devuelven a una página de Internet, puede ser mejor leer cada vez 500 filas, para no volver siempre a la base de datos cuando el usuario hace clic en "Próximo". - Y a mí me dan asco las páginas que limitan los datos que puedo ver a sólo diez o veinte líneas -.

create table @tabla

En este caso, no hay problemas ni de seguridad ni de la caché - no hay un problema de permisos, puesto que el usuario necesita los permisos de CREATE TABLE incluso cuando el comando está estático y en un procedimiento almacenado -. Y además, tampoco hay problemas con las dependencias, pues al parecer no existen razones contra el SQL dinámico.

Pero sí hay una pregunta importante: ¿Por qué? ¿Qué motivo puedes tener? Quizá sea razonable en un script de sysadmin si necesitas crear un par de tablas similares, pero si vas creando tablas dinámicamente en tus aplicaciones pues te has saltado todas las reglas de diseño de los bases de datos. En una base de datos relacional, el conjunto de tablas y columnas debería ser fijo. Puede cambiar al instalar una nueva versión, pero no durante la ejecución de tu aplicación. Hay más detalles en esta sección: select * from ventas + @yymm

A veces parece que la gente quiere crear nombres únicos para tablas temporales, pero es absolutamente innecesario puesto que el SQL Server ya tiene esta característica. Puedes crear una tabla temporal con la sintaxis siguiente:

    CREATE TABLE #nisse (a int NOT NULL) 

El nombre real de la tabla será más largo, y sólo la conexión actual puede ver esta instancia de #nisse.

Si quieres crear una tabla permanente para cada conexión - quizás utilices conjuntos de resultados desconectados y entonces no puedes utilizar las tablas temporales -, puede ser mejor crear una sola tabla para todos las conexiones, cuya primera columna es una clave única generado para cada conexión.

Servidores vinculados

Este caso parece similar a parametrizar el nombre de una base de datos, pero las respuestas no son exactamente las mismas. Si puedes crear un procedimiento almacenado en el servidor vinculado, puedes generar el nombre del procedimiento dinámicamente:

    SET @sp = @server + 'db.dbo.some_sp'     EXEC @ret = @sp @par1, @par2... 

Si quieres hacer una combinación de una tabla local y una tabla en un servidor remoto que no puedes identificar de antemano, el SQL dinámico sería la mejor solución.

Pero hay una alternativa, aunque no es útil en todos los casos. Puedes utilizar sp_addlinkedserver para crear un alias temporal:

    EXEC sp_addlinkedserver MYSRV, @srvproduct='Any',                                    @provider='SQLOLEDB', @datasrc=@@SERVERNAME     go     CREATE PROCEDURE linksrv_demo_inner WITH RECOMPILE AS         SELECT * FROM MYSRV.master.dbo.sysdatabases     go     EXEC sp_dropserver MYSRV     go     CREATE PROCEDURE linksrv_demo @server sysname AS     IF EXISTS (SELECT * FROM master..sysservers WHERE srvname = 'MYSRV')         EXEC sp_dropserver MYSRV     EXEC sp_addlinkedserver MYSRV, @srvproduct='Any',                                    @provider='SQLOLEDB', @datasrc=@server     EXEC linksrv_demo_inner     EXEC sp_dropserver MYSRV     go     EXEC linksrv_demo 'Server1'     EXEC linksrv_demo 'Server2' 

Hay dos procedimientos almacenados. El procedimiento externo crea el servidor vinculado MYSRV para acceder al servidor remoto que queremos utilizar en este momento, y lo quita una vez terminada la consulta. El procedimiento interno ejecuta la consulta, y he incluido WITH RECOMPILE para que SQL Server no vuelva a utilizar un plan de consultas viejo creado para otro servidor.

Esta solución sólo funciona en estas circunstancias:

  • La persona que ejecuta el procedimiento debe tener los permisos para crear servidores vinculados - por omisión sysadmin y setupadmin tienen esos permisos - pues no sirve para los usuarios normales.
  • Ya que los servidores vinculados se definen globalmente al nivel del servidor, varias personas no pueden ejecutar el procedimiento a la vez - claro está que deberías utilizar el alias MYSRV solamente dentro de este procedimiento -.

Nota: si lo pruebas, es probable que funcione sin WITH RECOMPILE.. Puedes conseguir que funcione con la ejecución de sp_addlinkedserver en el mismo procedimiento que la ejecución de la consulta que utiliza el servidor vinculado, pero si éste no existe cuando SQL Server necesita crear un plan de consultas para el procedimiento pues no funcionará.

OPENQUERY

Muchas veces, el uso de las funciones de conjunto de filas OPENQUERY y OPENROWSET supone el uso del SQL dinámico, ya que su segundo argumento es una cadena de SQL y no admiten los variables. Resulta ser un poco incómodo, ya que puedes necesitar tres niveles anidados de comillas, pues la función quotestring() que ya hemos visto es muy útil:

DECLARE @remotesql nvarchar(4000),         @localsql  nvarchar(4000),         @state     char(2)  SELECT @state = 'CA' SELECT @remotesql = 'SELECT * FROM pubs.dbo.authors WHERE state = ' +                     dbo.quotestring(@state) SELECT @localsql  = 'SELECT * FROM OPENQUERY(MYSRV, ' +                     dbo.quotestring(@remotesql) + ')',  PRINT @localsql EXEC (@localsql) 

La función del sistema quotename() no es tan útil, puesto que la instrucción SQL sobrepasa frecuentemente el límite de 129 caracteres para el parámetro de entrada de quotename().

Longitud de columna dinámica

Digamos que tienes un procedimiento almacenado que devuelve unos datos, y se ejecutará directamente en el Query Analyzer - probablemente esun procedimiento para los sysadmins -. Para formatear los resultados, quieres cambiar la longitud de las columnas para que no haya ningunos espacios en blanco a finales de los resultados, pero tampoco quieres truncar los datos. Puedes realizar esta tarea con el SQL dinámico, y ya que se suele utilizar una tabla temporal, no hay problemas de permisos.

No doy un ejemplo, pero te aconsejo mirar código existente, en el procedimiento almacenado popular - pero no documentado - sp_who2. Puedes visualizar el código ejecutando exec master..sp_helptext sp_who2, o con el Object Browser en Query Analyzer o Enterprise Manager.

Hay otro ejemplo en mi website, en el procedimiento aba_lockinfo.

Menciones y contacto

Quisiera agradecerles a las personas siguientes que me han proporcionado valiosas sugerencias e ideas para este artículo: los SQL Server MVPs Tibor Karaszi, Keith Kratochvil, Steve Kass, Umachandar Jaychandran y Hal Berenson, además de Pankul Verma, Anthony Faull, Marcus Hansfeldt, Jeremy Lubich y Simon Hayes.

Quiero también agradecerles al ASP MVP Jongshin Kim su traducción coreana del artículo, a Frank Kalis su traducción alemán, y a Simon Hayes su traducción español.

Si tienes sugerencias o correcciones para el contenido, lenguaje o formateo de este artículo, por favor envíame un email: esquel@sommarskog.se . Si tienes preguntas técnicas que cualquier persona informada puede contestar, te aconsejo preguntarlas en los newgroups microsoft.public.sqlserver.programming o comp.databases.ms-sqlserver.

Historia de cambios

2005-04-17 - Nuevo ejemplo de EXEC y sp_executesql con un parámetro de salida. Añadí el uso de nvarchar(max) en el SQL 2005 p.ej. para quotestring.

2004-05-30 - Ahora disponible en castellano.

2004-02-08 – Ahora disponible en alemán. Pequeñas correcciones.

2003-12-02 – Ahora disponible en coreano. Añadí un ejemplo de utilizar una variable cursor con el SQL dinámico. Modifiqué la descripción del primer parámetro de sp_executesql.

Vuelve al home page de Erland