miércoles, 19 de diciembre de 2012

Programación en SQL Server



Últimamente me he dado cuenta (tristemente) que la gente no le da la importancia debida a las diferentes técnicas y herramientas de programación con las que se cuentan en SQL Server, es preocupante que los "DBA" de las empresas tengan técnicas paupérrimas para obtener la información que requieren y peor aún que piensen que es la única y mejor manera sin haber tenido contacto con literatura más avanzada del tema.

Con este conjunto de entradas no quiero ser agresivo y mucho menos engreído, el objetivo es dar una serie de consejos que puedan guiar al programador a crear las estructuras necesarias, utilizar las técnicas óptimas y proponer las tareas de análisis que hagan en conjunto una técnica de programación aceptable, no podría decir que la mejor porque al final estoy 100% seguro que su servidor no tendrá siempre la mejor respuesta, sería egocentrismo.

¿Por dónde empezar?, analicemos la sentencia SELECT y veamos muchos puntos interesantes que podemos desprender de este tema.

Es más que conocido que la sentencia SELECT es utilizada para obtener información de la base de datos y dado que tiene una sintaxis tan sencilla luego se olvidan las implicaciones que se pueden tener al escribir una sentencia de pobre análisis y peor ejecución.

Recordemos que estamos trabajando en bases de datos OLTP cuyo objetivo es establecer una infraestructura que permita la escritura rápida de datos y dar herramientas para poder realizar lecturas rápidas, pero dejaremos en claro que su objetivo es la escritura rápida.

La sentencia más básica tendría esta estructura:

SELECT * FROM Productos

"Productos" es el nombre de la tabla de la que queremos obtener la información y "*" significa que queremos obtener todas las columnas de la tabla.

¿Implicaciones?, muchas, el primer punto importante es que debemos dejar en claro que jamás deberemos obtener columnas que no vamos a utilizar, en este caso si sólo necesitamos el id, nombre, color y precio; pues sólo debemos mencionar esas columnas:

SELECT Id, Nombre, Color, Precio
FROM Productos

Una segunda implicación de utilizar el "*" en lugar de sólo las columnas que vamos a utilizar, es que de forma inequívoca se realizará un barrido al índice CLUSTERED de la tabla, es decir que se hará un barrido fila a fila de la tabla, lo cual dependiendo de la cantidad de filas puede tener consecuencias negativas.

Si no vamos a utilizar todos los productos, es imprescindible el filtrar los datos desde el manejador de base de datos, es decir que no deberemos enviar toda la información al cliente y posteriormente realizar el filtrado, todo esto gastaría recursos de forma inútil (a menos que haya razones de mucho peso para hacerlo)    La sentencia para filtrar los productos cuyo precio sea mayor a $100.00 sería la siguiente:

SELECT Id, Nombre, Color, Precio
FROM Productos
WHERE Precio > 100

Es importante notar que no siempre que exista un índice sobre las columnas que aparecen en la parte del WHERE éste va a ser utilizado:
  1. Si la cantidad de productos que cumplen con la condición es grande respecto al total de filas en la tabla, entonces el manejador ejecutará un barrido fila a fila.
  2. Si la cantidad de productos que cumplen con la condición es pequeña respecto al total de filas en la tabla, entonces el índice será utilizado.
Uno podría pensar que si son muchos, pues los regresamos todos, total así fue requerida la sentencia ¿no?, bueno, supongamos que entramos a una tienda de discos y buscamos el género "banda", ¿imaginan cuántos discos van a salir?, ¿es posible que alguien los vea todos?, generalmente no pasa así, por lo tanto es imperativo que ofrezcamos un resultado paginado para evitar que se envíen resultados que no van a ser utilizados.

En entradas posteriores estaremos trabajando más sobre la sentencia SELECT.

jueves, 7 de junio de 2012

Comenzando con OpenStreetMap



He tenido por fin el tiempo para crear una entrada donde exploremos de forma muy rápida la forma en la que podemos crear un mapa utilizando OpenStreetMap y OpenLayers.

¿Por qué utilizar OpenStreetMap?, simplemente porque Google quiere cobrarle a las empresas que monten en su sitio web un mapa que vean mucho los usuarios, por esa sencilla situación he decidido investigar cómo es que funciona OpenStreetMap.

Para poder comenzar tendremos que descargar la librería que nos ayudará a controlar nuestro mapa de OpenStreetMap en la página openlayers.org.  Ahí encontraremos una liga para descargar la librería.

Una vez descargado el zip, vamos a descomprimirlo y en un sitio web nuevo creado con Visual Studio vamos a agregar los siguientes archivos y carpetas:

- build\OpenLayers.zip
- theme
- img

OpenLayers.js deberá quedar ubicado en la raíz.

Vamos a agregar una página HTML (MyMap.htm) donde agregaremos referencia al script de OpenLayers:

<script language="javascript" type="text/javascript" src="OpenLayers.js"></script>

Ahora agregamos un elemento DIV que nos permita dibujar el mapa sobre él:

<div style="width: 800px; height: 600px;" id="myMap"></div>

¡Comenzamos con la magia de OpenLayers!.  Creamos una instancia del objeto OpenLayers.Map pasándole como parámetro el identificador del objeto donde queremos que cargue el mapa (myMap), le agregamos una capa que esté conectada con OpenStreetMap y centramos el mapa en México con un zoom de 5.    Finalmente agregamos un control que nos permite rastrear las coordenadas donde está ubicado el mouse en el mapa y listo:

<script language="javascript" type="text/javascript">
    var map = new OpenLayers.Map('myMap'); //Identificador del elemento DIV
    map.addLayer(new OpenLayers.Layer.OSM()); //OpenStreetMap
    map.setCenter(new OpenLayers.LonLat(-11437415.414775, 2727283.1688355), 5);
    map.addControl(new OpenLayers.Control.MousePosition());
</script>

Una excelente referencia de cómo conocer cada uno de los objetos que tiene OpenLayers es la página de la documentación de la API y también en esta página tenemos una guía sencilla.

En las entradas siguientes de OpenStreetMap y OpenLayers iremos avanzando poco a poco en la exploración de estas herramientas.

Espero te haya sido de utilidad.

jueves, 24 de mayo de 2012

Easing Functions



El trabajo ha comido bastante mi cerebro y había estado llegando muy cansado a la casa, es por eso que no había podido publicar algo reciente, pero hoy que tengo unos minutos libres quise escribir una entrada que aborde una de las características de jquery que más me gusta, las funciones easing.

Como ya sabemos, jquery agrega una función llamada animate a cualquier objeto jquery.   Vayamos construyendo un ejemplo sencillo:

1. Creamos un nuevo sitio web (al mío lo llamé EasingFunctions) en Visual Studio.
2. Creamos la carpeta js y ahí metemos la librería jquery y el plugin easing de jquery.
3. Agregamos una página HTML y la llamaremos TestFunctions.htm
4. Agregamos referencia a jquery.js y a jquery.easing.1.3.js.    Aquí es importante recordar que HTML se va intepretando línea a línea por lo que es necesario que la primera referencia sea hacia jquery y la segunda sea al plugin de funciones.

<script language="javascript" type="text/javascript" src="js/jquery.js"></script>
<script language="javascript" type="text/javascript" src="js/jquery.easing.1.3.js"></script>

5. Busquemos algunas imágenes para hacer un pequeño slider en nuestra página y así vayamos viendo cómo es que se comportan algunas funciones del plugin.    Vayan a Google y busquen 5 imágenes de lo que quieran (yo voy a utilizar de los Simpson) que sean de 400x300 y las nombraremos 1.jpg, 2.jpg, 3.jpg, 4.jpg y 5.jpg y las guardaremos en la carpeta img de nuestro proyecto.

Hasta ahora nuestro explorador de soluciones debería verse así:



6. Vamos a construir el código HTML para meter botones de "adelante" y "atrás" y ver la galería fotográfica.   Notemos que el secreto, al menos en la solución que yo propongo, es colocar un div con un overflow en hidden para que los elementos que estén más allá de las dimensiones del div estén ocultos.

<table cellspacing="0" cellpadding="0">
    <tr>
        <td align="left"><input type="button" value="Anterior" /></td>
        <td align="right"><input type="button" value="Posterior" /></td>
    </tr>
    <tr>
        <td colspan="2">
            <div style="width: 400px; height: 300px; overflow: hidden; position: relative;">
                <table cellspacing="0" cellpadding="0" style="position: absolute; left: 0px; top: 0px;">
                    <tr>
                        <td><img src="img/1.jpg" /></td>
                        <td><img src="img/2.jpg" /></td>
                        <td><img src="img/3.jpg" /></td>
                        <td><img src="img/4.jpg" /></td>
                        <td><img src="img/5.jpg" /></td>
                    </tr>
                </table>
            </div>
        </td>
    </tr>
</table>

De una vez he puesto las imágenes en un arreglo horizontal en una tabla.    También deberemos notar que el div tiene un posicionamiento relativo de manera tal que los elementos que contenga estén posicionados respecto al div y no respecto al documento completo, tal y como lo estamos haciendo con la tabla que de inicio estará en la coordenada (0,0) del div.

Ahora crearemos un script sumamente básico que lo que hará es mover la tabla de manera tal que se vea el cambio de imágenes.   Modificaremos la declaración de nuestros botones y quedarán de la siguiente manera:

<input type="button" value="Anterior" onclick="showPrevious()" />
<input type="button" value="Posterior" onclick="showNext()" />

Y el script lo que hará es mover la tabla hacia la izquierda o la derecha dependiendo del botón que hayan presionado:

<script language="javascript" type="text/javascript">
    var currentPic = 0;
    var picCount = 5;
    var picWidth = 400;
    function showPrevious() {
        if (currentPic == 0)
            currentPic = picCount - 1;
        else
            currentPic--;
        $('#tblPics').css('left', -currentPic * picWidth);
    }
    function showNext() {
        currentPic = (currentPic + 1) % picCount;
        $('#tblPics').css('left', -currentPic * picWidth);
    }
</script>

He declarado algunas variables que guarden los valores importantes de manera tal que después podamos agregar fotos sin problema alguno.

Ejecutemos nuestra página y veamos cómo van cambiando las fotografías dependiendo del botón que hayamos presionado.

Con un simple cambio en el código, ahora lo que haremos es hacer el movimiento de la tabla de forma animada.    Utilizaremos la función animate y nuestro código deberá quedar de la siguiente manera:

Antes: $('#tblPics').css('left', -currentPic * picWidth);
Ahora: $('#tblPics').animate({ 'left': -currentPic * picWidth });

Probemos nuestra página y veremos que el cambio de foto a foto ya se ve mucho mejor.

El parámetro que le estamos pasando a la función animate, es un objeto que contiene todos aquellos atributos de la hoja de estilo de nuestra tabla que queramos modificar de forma animada.

De inicio, jquery utiliza una función de animación llamada "linear", esto significa que no tiene aceleración.    Modifiquemos nuestra llamada a la función animate hacia el segundo valor que por defecto trae jquery:

Antes: $('#tblPics').animate({ 'left': -currentPic * picWidth });
Ahora: $('#tblPics').animate({ 'left': -currentPic * picWidth }, 'normal', 'swing');

Si ejecutamos de nuevo la página no veremos un cambio muy significativo dado que el ancho de las fotografías es algo pequeño, pero ahora que utilicemos las funciones que tiene el plugin podremos observar grandes cambios.

Probemos la animación "easeInCubic":

$('#tblPics').animate({ 'left': -currentPic * picWidth }, 'normal', 'easeInCubic');

Al ejecutar la página veremos que al principio comienza lento y conforme va pasando el tiempo comienza a acelerar.    En lo personal, me gusta mucho la función easeInCirc.

Para obtener la lista completa de las funciones a las que tenemos acceso a través de este plugin, abrimos el archivo jquery.easing.1.3.js y ahí aparecerán todas las funciones easing a las que se les ha asignado un nombre en el plugin.

Ya dependerá del gusto de nuestro cliente cuál utilizaremos, pero en las funciones easing tenemos un aliado que nos permitirá darle una vida y presentación diferente a nuestras páginas.

Podrán ver este pequeño ejercicio en ejecución en esta dirección web:

http://www.jorgetoriz.com/easingfunctions/testfunctions.htm

Espero les haya sido de mucha utilidad.

lunes, 9 de abril de 2012

OpenStreetMap



Pues hoy me entero de la ruptura entre Wikipedia y Google Maps dado el cambio en la política de uso de Google donde establece una cantidad máxima de peticiones hechas desde una página web, en caso de que la página supere este límite Google cobrará una lana para seguir dando el servicio.

Sucede que Wikipedia es la sexta página más visitada del mundo (dato extraído de alexa.com) y es evidente que la cantidad de visitas que tienen los mapas es enorme.

¿Qué es OpenStreetMap?, es un mapa mundial que permite la edición por parte de sus usuarios, algo muy parecido a Wikipedia pero orientado hacia los mapas.

Siendo sincero, intenté editar un mapa y en menos de 2 minutos terminé hecho bolas con puntos donde no quería y al final cerré sin guardar cambios.    Pero bueno, creo que eso se corrige leyendo bien los tutoriales que da la página para la edición de su contenido.

¿Me gustó? mmm podría decir que sí, pero creo que los mapas aún están muy pobres y esto supongo que se debe a la poca afluencia o número de usuarios que ha tenido el sitio (en México) por lo que lo único que nos queda hacer es echarle la mano y completar los mapas para que sean tan exactos y precisos como nos lo permita el conocimiento de nuestra ciudad.

Espero pronto puedan liberar una API en javascript para agregar marcadores en los mapas y así poder dar por sustituído a Google Maps y repartir este pastel entre más jugadores.    Y bueno, no es que esté 100% en contra de Google, sólo que también debe haber más participación por otros proveedores o páginas para que la competencia sea mucho más reñida y así nosotros tengamos servicios de mayor calidad y menor precio.

Y tú, ¿ya contribuíste con OpenStreetMap?, te invito a que modifiques el mapa de la zona por donde vives y así generemos mapas muy precisos de la ciudad que habitamos.

--Actualización 17:13

Hace unos minutos recibí una respuesta por parte de la cuenta @openstreetmapes donde me hicieron saber que existen dos librerías javascript que permiten operar los marcadores que se colocan en un mapa de OpenStreetMap: Leaflet y OpenLayers.

Mil gracias por la respuesta a @openstreetmapes y pues con todo gusto a participar en tan interesante proyecto.

En la siguiente entrada publicaré un ejercicio donde se ocupe un mapa de OpenStreetMap y también se agreguen marcas en tiempos de ejecución utililizando la librería javascript que me recomendaron tan amablemente.

viernes, 30 de marzo de 2012

JSON 2



En esta segunda entrada veremos cómo podemos enviar un objeto de forma asíncrona utilizando JSON.

Tomando el ejercicio anterior como preámbulo, utilizaremos el mismo objeto Product para realizar el envío de información.

El primer problema que podemos encontrar es que la notación JSON no es tan fácil de generar (no es algo del otro mundo pero definitivamente quita mucho tiempo).    Para facilitarnos el trabajo existe una librería llamada json.js que podemos descargar de aquí.

Esta librería expone una función llamada stringify que nos ayudará a traducir un objeto javascript a su notación JSON correspondiente.

Vamos a modificar nuestro handler AddProduct.ashx para que reciba el objeto y simule meterlo a una base de datos.    Primero debemos agregar referencia al espacio de nombres donde está definido el objeto JavaScriptSerializer:

using System.Web.Script.Serialization:

En el procedimiento ProcessRequest vamos a recibir el objeto JSON y lo vamos a "deserializar" al objeto Producto que tenemos en nuestra página web.

public void ProcessRequest(HttpContext context)
{
    string jsonProduct = context.Server.UrlDecode(Uri.UnescapeDataString(context.Request.Form.ToString()));
    JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
    DataObjects.Product product = jsSerializer.Deserialize<DataObjects.Product>(jsonProduct);
    System.Threading.Thread.Sleep(2000);
}

En la primera línea de código estamos primero decodificando con la función UnescapeDataString para quitar todos los caracteres que comienzan con "%", por ejemplo %20 que es el espacio en blanco; después estamos utilizando la función UrlDecode para cambiar todos los "+" por espacios en blanco.    Al final de estas dos funciones obtenemos el código JSON que nos fue enviado desde el cliente.

Con la tercera línea de código estamos tomando el código JSON y lo estamos convirtiendo en el objeto Product que le corresponde en nuestro proyecto.    Las características del objeto Product permiten realizar esta operación (propiedades públicas de lectura y escritura cuyos nombres coinciden con las propiedades del objeto JSON).

Finalmente estamos llamando a la función Sleep para simular que estamos llevando a cabo un proceso de registro.

Ahora vamos a ajustar nuestro archivo Products.htm para que haga la llamada al servidor.   Primero agregaremos una referencia a la librería json.js:

<script type="text/javascript" language="javascript" src="js/json.js"></script>

Después agregaremos un botón para ejecutar la función que enviará el objeto al servidor:

<input type="button" value="Send product" onclick="return sendProduct()" />

Y finalmente crearemos nuestro function sendProduct que será la encargada de realizar el POST al servidor:

function sendProduct() {
    var myProduct = new Product(1, 'Product 1');
    $.ajax({
        'type': 'POST',
        'dataType': 'json',
        'data': JSON.stringify(myProduct),
        'url': '/Handlers/AddProduct.ashx',
        'success': sendProduct_success,
        'error': sendProduct_error
    });
}
function Product(id, name) {
    this.Id = id;
    this.Name = name;
}
function sendProduct_success(d) {
    alert('El producto ha sido registrado');
}
function sendProduct_error(d) {
    alert('Mmm... algo anda mal');
}

Podemos darnos cuenta que:
1. He creado un objeto personalizado llamado "Product" que está definido de igual forma que el que tenemos en DataObjects.Product en el servidor, esto es para evitar problemas de serialización/deserialización.
2. El tipo de la llamada es POST.
3. Estamos especificando que el tipo de información que estamos enviando es json.
4. Utilizamos la función JSON.stringify para traducir nuestr objeto myProduct a JSON.

Espero les haya sido de mucha utilidad.

domingo, 25 de marzo de 2012

Privacidad en facebook



Hubo cambios en las políticas de privacidad en facebook que pueden revelar nuestra información a aplicaciones aún cuando nosotros no lo hayamos autorizado de forma expresa.

Funciona así:
  1. Un amigo tuyo le da los permisos que la aplicación le está pidiendo.
  2. La aplicación podrá ver y tener acceso a la información que puede ver tu amigo.
¿Cómo arreglarlo?
  1. Inicio.
  2. Configuración de la privacidad.
  3. Aplicaciones y sitios web.
  4. Cómo las personas usan tu información en las aplicaciones que utilizan.
  5. Limpiar todas las casillas de verificación y guardar cambios.

Es importante limitar el acceso que tienen las aplicaciones a nuestra información... cuidado con su privacidad.

sábado, 24 de marzo de 2012

JSON



Cuando queremos implementar un modelo de comunicación asíncrono entre una página web y el servidor tenemos al menos dos opciones: JSON y XML a través de requests con distintos verbos dependiendo del objetivo de la llamada o ejecución.    Aún cuando sea algo muy sabido creo que es necesario mecionar que generalmente los verbos se utilizan de la siguiente forma:

GET: Leer
POST: Agregar / Actualizar
DELETE: Eliminar
PUT: Agregar / Actualizar

En este ejercicio estaremos ocupando los dos verbos más comunes que son el GET y el POST.    Para preparar el entorno agregaremos los siguientes elementos:

1. Crear carpeta js.
2. Agregar jquery.js a la carpeta js.
3. Crear carpeta Handlers.
4. Agregar Products.ashx y AddProduct.ashx a la carpeta Handlers.
5. Crear carpeta DataObjects.
6. Agregar clase Product a la carpeta DataObjects.
7. Agregar archivo Products.html al proyecto (opté por un HTML para dejar en claro que todo se hará a través de requests asíncronos y no a través de posts de una WebForm)

Si vemos el explorador de soluciones (Ctrl + Alt + L) veremos un arreglo de archivos como este:


Vamos a agregarle dos propiedades públicas de lectura y escritura a nuestra clase Product:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Ahora vamos a crear el código necesario para que Products.ashx devuelva la lista de productos en JSON.    Para empezar vamos a agregar dos espacios de nombres, el que contiene a JavaScriptSerializer y a nuestra clase Product:

using System.Web.Script.Serialization;
using SendReceiveJSON.DataObjects;

Los handlers reciben las peticiones a través del procedimiento ProcessRequest.   Notemos que por defecto ya viene un código que dice el tipo MIME que se va a devolver y el contenido de la respuesta.    El tipo MIME de JSON es "application/json" por lo que deberemos fijar este valor en la propiedad ContentType de la propiedad Response.

context.Response.ContentType = "application/json";

Ahora vamos a llenar una lista de objetos Product y la vamos a convertir en JSON utilizando el objeto JavaScriptSerializer:

List<Product> result = new List<Product>();
for (int i = 0; i < 10; i++)
{
    result.Add(new Product() { Id = i + 1, Name = "Product " + (i + 1).ToString() });
}
JavaScriptSerializer js = new JavaScriptSerializer();
context.Response.Output.Write(js.Serialize(result));

Si ejecutamos directamente el handler Products.ashx desde el navegador deberemos ver un resultado como este:


Para terminar este primer ejercicio vamos a crear el código necesario en nuestra página Products.htm para que llame al handler y muestre el listado de productos.    Para esto necesitamos un botón y un contenedor donde insertaremos una tabla con los productos.

<input type="button" value="Get products" onclick="return getProducts()" />
<hr />
<div id="divProducts"></div>

En nuestra función getProducts haremos la llamada a Products.ashx (recuerden agregar el script jquery en el head):

<script language="javascript" type="text/javascript">
    function getProducts() {
        $.ajax({
            'type': 'GET',
            'url': '/Handlers/Products.ashx',
            'success': getProducts_success,
            'error': getProducts_error
        });
        return false;
    }
    function getProducts_success(d) {
    }
    function getProducts_error(d) {
    }
</script>

Con la función $.ajax (de jquery) podemos realizar peticiones asíncronas (con el verbo type) al recurso web identificado determinado a través de la propiedad url.    Cuando la ejecución es exitosa se mandará a llamar a la función getProducts_success y cuando haya un error se ejecutará la función getProducts_error.

Vamos a modificar las funciones getProducts_success y getProducts_error para darle significado al ejercicio:

function getProducts_success(d) {
    var products = '<table cellspacing="0" cellpadding="5">';
    products += '<tr><th>Id</th><th>Name</th></tr>';
    for (var i = 0; i < d.length; i++) {
        products += '<tr>';
        products += '<td>' + d[i].Id + '</td>';
        products += '<td>' + d[i].Name + '</td>';
        products += '</tr>';
    }
    products += '</table>';
    products = $(products);
    $('#divProducts').append(products);
}
function getProducts_error(d) {
    alert('There was an error during execution process');
}

Notemos que estamos ocupando las propiedades públicas de nuestro objeto Product (Id y Name), esto se debe a que jquery realiza la decodificación de JSON y genera el arreglo de objetos necesarios para mostrar la información.    Es por eso que se llama JSON (JavaScript Object Notation)

En la siguiente entrada veremos cómo enviar un objeto Product al servidor a través de un POST en notación JSON.

miércoles, 21 de marzo de 2012

Mensaje Javascript 2



En esta segunda entrada referente a cómo mostrar un mensaje al usuario de un sitio web, le agregaremos tres parámetros a nuestra función showMessage:

1. Tipo de mensaje, el cual determinará el color de fondo del contenedor del mensaje.
2. Tiempo, será la cantidad de milisegundos que el mensaje deberá estar visible.
3. Callback, será una referencia a una función que se desee ejecutar después de que el mensaje se destruya.

Tal como en el ejercicio anterior, iremos cubriendo uno a uno de los elementos hasta tener el script completo.

Para manejar un tipo de mensaje, vamos a agregar un parámetro msgType que podrá tener como valores: 'error', 'success' y habrá un color default para cuando no se reciba este parámetro.

function showMessage(msg, msgType)

Ahora modificaremos un poco el código que establece el estilo de nuestra variable message y esto se debe a que el color de fondo dependerá del tipo de mensaje:

message.css({
    'height': '20px',
    'width': '100%',
    'position': 'fixed',
    'top': '-28px',
    'left': '0px',
    'margin': '0px',
    'background-color':
        msgType == 'error' ? '#d83a28' : (msgType == 'success' ? '#5aa250' : '#5f8be3'),
    'color': '#FFFFFF',
    'font-size': '15px',
    'text-align': 'center',
    'padding-top': '4px',
    'padding-bottom': '4px',
    'font-family': 'Verdana'
}).text(msg);

Se diría que le estamos asignando el color de una forma poco elegante, pero podríamos hacerlo un poco mejor si creamos una función llamada getMsgColor que reciba como parámetro el tipo de mensaje a mostrarse y nos devuelva el código del color.    Con este cambio el código quedaría de la siguiente manera:

function getMsgColor(msgType) {
    switch (msgType) {
        case 'error': return '#d83a28';
        case 'success': return '#5aa250';
        default: '#5f8be3';
    }
}

function showMessage(msg, msgType) {
    var message = $('<div></div>');
    message.css({
        'height': '20px',
        'width': '100%',
        'position': 'fixed',
        'top': '-28px',
        'left': '0px',
        'margin': '0px',
        'background-color': getMsgColor(msgType),
        'color': '#FFFFFF',
        'font-size': '15px',
        'text-align': 'center',
        'padding-top': '4px',
        'padding-bottom': '4px',
        'font-family': 'Verdana'
    }).text(msg);
    ... aquí continúa la función showMessage

La ventaja es que el código queda mucho más limpio y será muy fácil el agregar un nuevo tipo de mensaje con su color respectivo.    Probemos nuestra nueva función con el botón que ya tenemos (del ejercicio anterior):

<input type="button" value="Show message" onclick="return showMessage('Código y datos', 'success')" />

Para cubrir el segundo punto agregaremos otro parámetro llamado timeout que determinará el tiempo (en milisegundos) que deberá mostrarse el mensaje:

function showMessage(msg, msgType, timeout)

Y antes de mostrar nuestor mensaje verificaremos si recibimos un valor para nuestro parámetro timeout o bien le asignamos el tiempo default:

if (!timeout) timeout = 3500;
message.animate(
    { 'top': '0px' },
    'normal',
    null,
    function() {
        window.setTimeout(function() {
            message.animate({ 'top': '-28px' }, 'normal', null, function() { message.remove(); });
        }, timeout);
    }
);

Finalmente también nuestra función queremos que pueda mandar a llamar a una función determinada por el usuario para cuando haya sido destruído el mensaje.    Vamos a agregar un parámetro llamado callback:

function showMessage(msg, msgType, timeout, callback)

Al final el código de nuestro ejercicio debería quedar como el que a continuación coloco:

<input type="button" value="Show message" onclick="return showMessage('Código y datos', 'success', 2000, sayHello)" />
<script type="text/javascript">
    function getMsgColor(msgType) {
        switch (msgType) {
            case 'error': return '#d83a28';
            case 'success': return '#5aa250';
            default: return '#5f8be3';
        }
    }
    function showMessage(msg, msgType, timeout, callback) {
        var message = $('<div></div>');
        message.css({
            'height': '20px',
            'width': '100%',
            'position': 'fixed',
            'top': '-28px',
            'left': '0px',
            'margin': '0px',
            'background-color': getMsgColor(msgType),
            'color': '#FFFFFF',
            'font-size': '15px',
            'text-align': 'center',
            'padding-top': '4px',
            'padding-bottom': '4px',
            'font-family': 'Verdana'
        }).text(msg);
        $(document.body).append(message);
        if (!timeout) timeout = 3500;
        message.animate(
            { 'top': '0px' },
            'normal',
            null,
            function() {
                window.setTimeout(function() {
                    message.animate({ 'top': '-28px' }, 'normal', null, function() { message.remove(); if (callback) callback(); });
                }, timeout);
            }
        );
    }
    function sayHello() {
        alert('Hola mundo!');
    }
</script>

Espero les sea de mucha utilidad.

jueves, 15 de marzo de 2012

Mensaje Javascript



Un problema o dilema común en los sitios web, es el decidir la estrategia para mostrar mensajes al usuario, ya sean informativos, correctivos, de alarma, etc.

La función alert de javascript se ha convertido en un dolor de cabeza ya que afecta la navegabilidad de nuestro sitio, aparte hay que tomar en cuenta que el icono del diálogo muestra algo que le puede dar a entender al usuario que algo malo está pasando.

A mí me ha gustado mucho utilizar una alternativa que me permite deslizar un mensaje en la parte superior y que después de un tiempo éste desaparece de forma automática.    No impide la navegabilidad del sitio, no es agresivo y es muy sencillo de programar.

Comencemos por colocar un elemento DIV en la parte superior de un documento HTML (con referencia a jquery) en blanco:

<div style="height: 20px; width: 100%; position: fixed; top: 0px; left: 0px; margin: 0px; background-color: #264182; color: #FFFFFF; font-size: 15px; text-align: center; padding-top: 4px; padding-bottom: 4px; font-family: Verdana;">Código y Datos</div>

Tenemos un contenedor de 20px de alto con un posicionamiento de tipo fixed que nos permitirá mostrar el mensaje sin importar la posición del scroll en el navegador del visitante.   Le hemos dado un padding superior e inferior para centrar verticalmente el mensaje.

Ahora vamos a agregar un botón que nos permita experimentar un poco con jquery para ir construyendo el código necesario para mostrar nuestro mensaje.

<br /><br /><input type="button" value="Hide message" onclick="return hideMessage()" />

He colocado unos saltos de línea para que el mensaje no tape a nuestro botón.

Ahora lo que haremos es una pequeña animación que moverá el mensaje hacia arriba hasta que desaparezca:

<script type="text/javascript">
    function hideMessage() {
        $('#message').animate({
            'top': -28
        });
        return false;
    }
</script>

Veamos que estamos moviendo 28px y no 20px, esto se debe a que tenemos que sumar los 4px del padding superior y los 4px del padding inferior que actualmente tiene nuestro elemento DIV.

Visitemos nuestra página y veremos que ya se oculta el mensaje.

Ahora agregaremos otro botón para mostrar el mensaje:

<input type="button" value="Display message" onclick="return displayMessage()" />

Y agregaremos su función (dentro del bloque <script>) javascript correspondiente:

function displayMessage() {
    $('#message').animate({
        'top': 0
    });
    return false;
}

Si volvemos a ejecutar nuestra página de prueba, veremos que ya podemos ocultar el mensaje mediante una animación y también podemos mostrarlo mediante una animación.

Bueno, tenemos las dos funciones básicas para mostrar el mensaje

Ahora generamos otro archivo HTML (con referencia a jquery) donde tendremos nuestro código final y agregamos un botón para mandar a llamar a nuestra función:

<input type="button" value="Show message" onclick="return showMessage('Código y datos')" />

Nuestra función showMessage(msg) deberá:

1. Construir un elemento DIV.
2. Establecer todas las propiedades para su estilo.
3. Ingresar el texto del mensaje en el elemento DIV.
4. Agregar nuestro elemento DIV al documento.
5. Animar el elemento DIV para que se muestre, después de 3.5 segundos animar el elemento DIV para ocultarlo y descargarlo del DOM (destruirlo)

Iremos construyéndola paso a paso para hacer claro el ejemplo:

<script type="text/javascript">
function showMessage(msg) {
//1.
    var message = $('<div></div>');

//2. Notar que la propiedad top comienza con un valor de -28px para que no se vea el elemento DIV al momento de inyectarlo al DOM.
    message.css({
        'height': '20px',
        'width': '100%',
        'position': 'fixed',
        'top': '-28px',
        'left': '0px',
        'margin': '0px',
        'background-color': '#264182',
        'color': '#FFFFFF',
        'font-size': '15px',
        'text-align': 'center',
        'padding-top': '4px',
        'padding-bottom': '4px',
        'font-family': 'Verdana'
    });

//3.
    message.text(msg);

//4.
    $(document.body).append(message);

//5.
    message.animate(
        { 'top': '0px' },
        'normal',
        null,
        function() {
            window.setTimeout(function() {
                message.animate({ 'top': '-28px' }, 'normal', null, function() { message.remove(); });
            }, 3500);
        }
    );
</script>

Es probable que el punto 5 resulte un tanto confuso debido a su redacción, pero no es tan complejo como parece:

1. El primer parámetro de la función animate nos permite determinar las propiedades del estilo que queremos modificar
2. El segundo parámetro dicta la velocidad de la animación.
3. El tercer parámetro es el nombre de la función que determina el tipo de animación a aplicar (funciones matemáticas que calculan cómo se moverá el objeto, jquery tiene la animación linear por defecto).
4. El cuarto parámetro es la función que se ejecutará una vez que la animación haya finalizado, a este parámetro se le llama callback.

En el callback de la animación estamos agregando una función que se ejecutará en 3500 milisegundos.

Se podrá notar que volvemos a ocupar la función animate para ocultar el elemento DIV y su callback destruye el objeto message.

En la siguiente entrada le agregaremos algunas opciones a nuestra función para hacerla mucho más llamativa.

miércoles, 14 de marzo de 2012

MultiUpload dinámico



Hay ocasiones en las que no sabemos de forma exacta cuántos archivos va a cargar el usuario a nuestra aplicación web, por ejemplo una galería fotográfica, donde le damos la posibilidad de cargar 1 o más archivos.

El dolor de cabeza radica en "1 o más" dado que lo que menos queremos es que el usuario tenga que estar dando clic en un botón "Agregar" para que se haga un POST al servidor y generemos otro control de tipo FileUpload y así pueda cargar más fotografías.

En esta entrada explicaré de una forma sencilla y sumamente básica cómo podemos generar controles FileUpload del lado del cliente y así evitemos dar viajes sin sentido al servidor y por lo tanto mejorar la experiencia de nuestro usuario.

La herramienta más sencilla (y versátil) que he encontrado para poder modificar la página desde el navegador del cliente es jquery, por lo que agregaremos el siguiente elemento script en el head de nuestra página Default.aspx (suponiendo que ya cuentan con él y lo han colocado en la carpeta js de nuestro proyecto Web):

<script language="javascript" type="text/javascript" src="js/jquery.js"></script>

Y colocaremos la siguiente interfaz:

<table>
    <tr>
        <td colspan="2"><input type="button" value="Add" onclick="return addUploadField()" /></td>
    </tr>
    <tr>
        <td><input type="file" name="file0" /></td>
        <td></td>
    </tr>
    <tr id="trUploadRow">
        <td colspan="2" align="right"><asp:Button ID="cmdUpload" runat="server" Text="Upload" OnClick="cmdUpload_Click" /></td>
    </tr>
</table>

Tenemos un botón "Add" que nos servirá para agregar un nuevo control de carga de archivos, en las filas siguientes se agregarán los controles de carga de archivos junto con un botón para eliminar el control (si es que el usuario ya no quiere cargar ese archivo) y un botón ASP .NET que ejecutará el POST de la página para la carga de los archivos.

Notemos que es muy importante que nuestros controles de carga de archivos (input de tipo file) deben de contar con el atributo "name" para que éstos sean reconocidos por la propiedad Files del objeto Request.

Ahora escribiremos la función javascript addUploadField que será la encargada de crear los controles de carga:

<script language="javascript" type="text/javascript">
    var currentIndex = 0;
    function addUploadField() {
        currentIndex++;
        var newRow = '<tr>';
        newRow += '<td><input type="file" name="file' + currentIndex + '" /></td>';
        newRow += '<td><input type="button" value="Remove" onclick="return removeUploadField(this)" /></td>';
        newRow += '</tr>';
        newRow = $(newRow);
        newRow.insertBefore($('#trUploadRow'));
    }
    function removeUploadField(e) {
        $(e.parentNode.parentNode).remove();
    }
</script>

La variable currentIndex me ayudará a darle un nombre único a cada control que se agregue.   En la función addUploadField estoy declarando una variable newRow donde estaré concatenando el código HTML necesario para crear el control de carga y el botón de eliminar.

Al atributo name le estoy asignando un valor único al concatenar la variable currentIndex que se incrementa cada vez que el usuario agregue un nuevo control.

En el botón "Remove" estoy llamando a una función "removeUploadField" que será la encargada de eliminar la fila.

Una vez que tengo el código HTML necesario para representar a mi control de carga y mi botón de eliminar, convertiremos el código en objetos jquery para poder manipularos de una forma mucho más simple.    Lo interesante del objeto jquery es que utilizando su funcion insertBefore lo podemos colocar justo arriba del botón "Upload".

En la función removeUploadField únicamente recibimos como parámetro el botón que está llamando a la función, y dado que HTML es un árbol de nodos, podemos obtener a través de la propiedad "parentNode" al objeto TR que queremos eliminar.

Ahora vayamos al archivo Default.aspx.cs donde escribiremos el código de nuestro manejador del evento Click del botón Upload:

using System.IO;
protected void cmdUpload_Click(object sender, EventArgs e)
{
    foreach (string file in Request.Files)
    {
        if (Request.Files[file].ContentLength > 0)
        {
            if (File.Exists(Server.MapPath("~/files/" + Request.Files[file].FileName)))
                File.Delete(Server.MapPath("~/files/" + Request.Files[file].FileName));
            Request.Files[file].SaveAs(Server.MapPath(("~/files/" + Request.Files[file].FileName)));
        }
    }
}

Si ejecutamos la aplicación veremos que aún cuando la estructura de los objetos necesarios para llevar a cabo la carga de los archivos es la adecuada, éstos no son recibidos de forma correcta.    Pongamos un punto de interrupción en la línea del "foreach" y exploremos la propiedad "ContentType" del objeto Request, veremos que tiene el valor "application/x-www-form-urlencoded" y para que los archivos sean transferidos de forma correcta el ContentType debe ser "multipart/form-data".

Se me ocurren dos maneras de corregir este problema:

1. Modificando la etiqueta form de Default.aspx agregándole el atributo "enctype" con el valor "multipart/form-data" de manera tal que quede así:
<form id="form1" runat="server" enctype="multipart/form-data">

2. En el procedimiento Page_Load verificar el valor de la propiedad ContentType del objeto Request y corregirlo si es necesario:

protected void Page_Load(object sender, EventArgs e)
{
    if (form1.Enctype != "multipart/form-data")
        form1.Enctype = "multipart/form-data";
}

Ocupen la que gusten, las dos opciones resultan en la carga correcta de archivos.

Espero haya sido de mucha utilidad esta entrada y les ayude a arrancar con controles propios y extender su funcionalidad tanto como ustedes gusten.

martes, 28 de febrero de 2012

Criptografía



Navegando por ahí encontré este video que nos explica de una forma sumamente simple cómo es que funciona la criptografía en internet, comenzando con una conexión segura utilizando un algoritmo asimétrico para establecer una llave simétrica.

Los algoritmos de llave asimétrica son los más seguros pero consumen muchos recursos dada la complejidad de su cifrado y descifrado.

Los algoritmos de llave simétrica son los más rápidos pero presentan la problemática de compartir de una forma segura la llave que nos ayudará para cifrar y descifrar.

Espero les sea de mucha utilidad.

viernes, 24 de febrero de 2012

Ícono en control personalizado



Es común que no nos preocupemos por darle una imagen significativa (metáfora visual) a nuestros controles dado que como desarrolladores creemos que son cosas que no interesan mucho, total, funcionan y para eso fueron creados ¿no?

No quiero iniciar una discusión acerca de si es bueno o no, cada quien tiene sus razones y serán tan válidas como sus clientes (otros desarrolladores que utilicen sus controles) acepten las imágenes asociadas a los controles.

Asignarle un ícono diferente al del engrane con el que aparecen de inicio no es complicado, lo único que debemos hacer es:
  1. Generar una imagen de 16x16 en mapa de bits
  2. Guardarle con el mismo nombre de nuestro control, por ejemplo, si nuestro control se llama AsyncSchedule, el archivo deberá llamarse AsyncSchedule.bmp
  3. Agregamos el archivo al proyecto y en sus propiedades (seleccionar archivo en el explorador de soluciones - clic derecho - propiedades) elegimos que es un recurso incrustado.
  4. Recompilamos nuestro ensamblado.
  5. Eliminamos el control de la barra de herramientas y lo volvemos a agregar.
¡Lixto!

En lo personal me gusta que tengan imágenes, le da variedad al contenido de mi barra de herramientas.

martes, 21 de febrero de 2012

Controles extendidos



Controles personalizados es uno de los temas más interesantes y desafiantes de la programación en ASP .NET, todo esto debido a la complejidad y versatilidad que se tiene para poder crearlos.

Podemos dividir los controles personalizados en las siguientes categorías (las pondré en inglés dado que la mayor referencia de información la encontrarán en ese idioma):

1. Web controls.    También llamados "from the scratch", debemos crear cada pedazo de código HTML que conforme a
nuestro control.
2. Composite controls.    Conformado a partir de la agrupación de controles existentes.
3. Inherited controls (extended controls).    Se le agregan o modifican las características funcionales o visuales de controles que ya existen.
4. Templated controls.    Permiten la personalización de los componentes que los conforman... a mi forma de ver, estos son los más complejos.
5. Web user controls.    Recomendados para controles que sólo se utilizarán en un proyecto.

Esta entrada está dedicada a explorar de una forma simple los controles compuestos (composite controls) a través de la creación de un control que nos ayudará a cargar un archivo al servidor sin tener que esperar a que el usuario dé clic en un botón o link en especial para poder enviar el archivo al servidor.

Los controles compuestos son fáciles de identificar dado que heredan de la clase CompositeControl
(System.Web.UI.WebControls)

Vamos a crear un proyecto de tipo "Biblioteca de clases" en Visual Studio y lo llamaremos "MyControls", allí agregamos una clase que llamaremos "ImmediateUploader".

Hacemos referencia al ensamblado System.Web y agregamos las siguientes líneas a nuestro archivo ImmediateUploader.cs:

using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

Ahora haremos que nuestra clase ImmediateUploader herede de CompositeControl:

public class ImmediateUploader : CompositeControl

Recordemos que los controles compuestos son aquellos que están formados a partir de controles ya existentes, es decir que toman las características y ventajas que tienen y los agrupan de tal forma de exponer la funcionalidad deseada.

En nuestro caso queremos mostrar un control donde el usuario pueda elegir un archivo de su computadora e inmediatamente después de darle clic al botón "Abrir" u "Ok" comience la carga del archivo.

De primera vista es fácil identificar que necesitaremos un control FileUpload pero para enviar el post de la página con el archivo seleccionado utilizaremos un botón oculto el cual disparará el evento deseado.

En los controles personalizados existe un método llamado CreateChildControls el cual es llamado cuando se necesita que el control genere la jerarquía de controles que lo conformarán.    Dado que CompositeControl viene vacío, aprovecharemos este método para agregar los controles FileUpload y Button:

protected override void CreateChildControls()
{
  FileUpload fu = new FileUpload();
  fu.Width = this.Width;
  fu.ID = "FileUpload";
 
  Button upload = new Button();
  upload.Style.Add(HtmlTextWriterStyle.Display, "none");
  upload.Click += new EventHandler(upload_Click);
  upload.ID = "Upload";
 
  Controls.Add(fu);
  Controls.Add(upload);
 
  fu.Attributes.Add("onchange", "raiseUploadClick_" + this.ClientID + "('" + upload.ClientID + "')");
}

Notemos lo siguiente:

1. Estamos ocultando el botón al asignarle a su estilo un "display" un valor de "none".
2. Estamos agregando el manejador del evento Click del botón para enterarnos del momento en que se realiza el POST por parte del navegador.
3. Estamos agregando un atributo "onchange" a nuestro control FileUpload para que nos avise a través de una función javascript que el usuario seleccionó un archivo.

Para avisarle a la página (donde se estará utilizando nuestro control) que se ha elegido un archivo, nuestro control lanzará un evento llamado FileUploaded.    Para lograrlo escribiremos el siguiente código:

public delegate void FileUploadedHandler(object sender, HttpPostedFile postedFile);
public event FileUploadedHandler FileUploaded;
void OnFileUploaded(HttpPostedFile postedFile)
{
  FileUploadedHandler handler = FileUploaded;
  if (handler != null)
    handler(this, postedFile);
}
void upload_Click(object sender, EventArgs e)
{
  if ((FindControl("FileUpload") as FileUpload).HasFile)
  {
    OnFileUploaded((FindControl("FileUpload") as FileUpload).PostedFile);
  }
}

Por último, vamos a modificar la forma en que nuestro control realizará el Render de su estructura:

protected override void Render(HtmlTextWriter writer)
{
  base.Render(writer);
  StringBuilder sb = new StringBuilder();
  sb.Append("<script type=\"text/javascript\">");
  sb.Append("function raiseUploadClick_" + this.ClientID + "(b){");
  sb.Append("document.getElementById(b).click()");
  sb.Append("}");
  sb.Append("</script>");
  writer.Write(sb.ToString());
}

La llamada base.Render hace que cada uno de los controles que se encuentran en la propiedad Controls de nuestro control compuesto sea traducido al HTML que le corresponde y escrito en el HtmlTextWriter.

Podemos ver que después de la llamada a base.Render estamos escribiendo una función de javascript para que se lance el post de nuestra página a través de la llamada del evento click del botón oculto.

Compilemos nuestro proyecto (Shift + Ctrl + B) y para probarlo haremos los siguiente:

1. Agregaremos un sitio web a nuestra solución (File - Add - New Web Site)
2. Abrimos en modo diseño nuestra página Default.aspx
3. Desplegamos la barra de herramientas y vamos a ver que aparece un control llamado ImmediateUploader, lo arrastramos a nuestra página.

Ejecutemos nuestro sitio web y veremos que al elegir un archivo y abrirlo se dispara el POST de la página.

Cerremos el navegador y finalicemos nuestro ejercicio cachando el evento y agregando metadatos a nuestro control.

Abrimos a nuestra clase ImmediateUploader.cs y agregamos el atributo "DefaultEvent" a nuestra clase de la siguiente forma:

[DefaultEvent("FileUploaded")]
public class ImmediateUploader : CompositeControl

Y también agregaremos un atributo "Description" a nuestro evento de forma tal que el programador que utilice nuestro control entienda para qué sirve o cuándo se dispara nuestro evento:

[Description("Raised when end user select a file from its computer")]
public event FileUploadedHandler FileUploaded;

Compilamos de nuevo el proyecto (Shift + Ctrl + B) y abrimos "Default.aspx" en vista diseño para dar doble clic sobre nuestro control ImmediateUploader.    Veremos que nos crea un procedimiento (Default.aspx.cs) que estará ejecutándose cuando el evento FileUploaded de
nuestro control se haya disparado:

protected void ImmediateUploader1_FileUploaded(object sender, HttpPostedFile postedFile)
{
}

¿Qué podemos hacer con postedFile?, depende del objetivo del control en la página, eso queda a su criterio dependiendo de las necesidades u objetivos de su página.

sábado, 18 de febrero de 2012

Volver a crear y sp_recompile



4. Recrear el procedimiento almacenado.

Otra opción que tenemos para obligar a que el plan de ejecución vuelva a calcularse es actualizar o volver a crear el procedimiento almacenado.

No me gustaría plantear estas dos opciones como posibles soluciones al problema de la actualización del plan de ejecución, y esto es dado que no siempre tendremos acceso al
código fuente de los objetos que componen a la base de datos.

Supongamos que estamos trabajando en la optimización de acceso a datos de una empresa de administración de inversiones y muchos de sus procedimientos fueron creados con la
opción WITH ENCRYPTION para proteger su código fuente, es muy probable (o seguro) que no te den acceso al código fuente de los procedimientos dado que podrían tener información
confidencial.

Más que como solución, deberemos entender que esto es una implicación, deberemos aplicar los cambios a los objetos cuando el sistema o el servidor se encuentre en una ventana de
mantenimiento y así afectar en lo mínimo el tiempo de respuesta del servidor.

5. Ejecutar el procedimiento almacenado sp_recompile

Este procedimiento recibe como parámetro el nombre de un objeto (no solamente de un procedimiento almacenado).

Al ejecutarlo con el nombre de un procedimiento almacenado como parámetro, estaremos indicando que su plan de ejecución vuelva a calcularse.

Pero la utilidad de este procedimiento va mucho más allá.    Supongamos que hicimos cambios o mejoras en la estructura de almacenamiento u ordenamiento de una tabla en
particular, si queremos que todos los objetos "precompilados" vuelvan a calcular su plan de ejecución para que tomen en cuenta las recientes modificaciones en la estructura de
nuestra tabla, basta con ejecutar el procedimiento sp_recompile pasándole como parámetro el nombre de nuestra tabla, veamos un ejemplo.

Supongamos que tenemos la siguiente estructura de información:

CREATE TABLE Students(
  Id SMALLINT IDENTITY(1, 1) NOT NULL,
  FirstName VARCHAR(30) NOT NULL,
  LastName VARCHAR(30) NOT NULL,
  DOB DATE NOT NULL,
  Notes VARCHAR(72) NOT NULL,
  RegistrationDate DATE NOT NULL,
  CONSTRAINT pkStudents PRIMARY KEY (Id)
)

Y la llenamos con datos:

DECLARE @FirstName VARCHAR(30)
DECLARE @LastName VARCHAR(30)
DECLARE @RegistrationDate DATE
DECLARE @DOB DATE
DECLARE @Notes VARCHAR(64)
DECLARE @i SMALLINT = 0
DECLARE @Length INT
WHILE @i < 10000
BEGIN
  SET @Length = ABS(CHECKSUM(NEWID()) % 8) + 1
  SET @FirstName = SUBSTRING(REPLACE(CAST(NEWID() AS VARCHAR(36)), '-', ''), 1, @Length)
  SET @Length = ABS(CHECKSUM(NEWID()) % 8) + 1
  SET @LastName = SUBSTRING(REPLACE(CAST(NEWID() AS VARCHAR(36)), '-', ''), 1, @Length)
  SET @Length = ABS(CHECKSUM(NEWID()) % 80)
  SET @RegistrationDate = DATEADD(DAY, @Length, '2012-02-01')
  SET @Length = ABS(CHECKSUM(NEWID()) % 800)
  SET @DOB = DATEADD(DAY, @Length, '1980-01-01')
  SET @Notes = CAST(NEWID() AS CHAR(36)) + CAST(NEWID() AS CHAR(36))
 
  INSERT INTO Students (FirstName, LastName, DOB, Notes, RegistrationDate)
  VALUES (@FirstName, @LastName, @DOB, @Notes, @RegistrationDate)
 
  SET @i = @i + 1
END

Creamos nuestro procedimiento almacenado y lo ejecutamos:

CREATE PROC pGetStudentsByDate(
  @RegistrationDate DATE
)
AS
BEGIN
  SELECT Id, FirstName, LastName
  FROM Students
  WHERE RegistrationDate = @RegistrationDate
END
GO
pGetStudentsByDate '2012-03-24'

Ahora veamos el plan de ejecución de nuestro procedimiento, podemos abrir un nuevo editor para queries y escribimos el código para ejecutar nuestro procedimiento almacenado,
damos clic derecho sobre el editor y elegimos la opción "Display Estimated Execution Time".    Veremos que nuestro procedimiento almacenado está llevando a cabo un barrido
completo de la tabla a través del índice pkStudents.

Vamos a crear un índice sobre nuestra tabla para optimizar la búsqueda de alumnos por fecha de registro:

CREATE NONCLUSTERED INDEX ixStudents_RegistrationDate
ON Students (RegistrationDate)
INCLUDE (FirstName, LastName)

Si volvemos a mostrar el plan de ejecución de nuestro procedimiento, veremos que no ha cambiado; entonces utilizaremos sp_recompile para obligar al cálculo de un nuevo plan de
ejecución, pero lo haremos sobre la tabla para demostrar que también funciona de esa forma:

sp_recompile 'dbo.Students'

Si volvemos a nuestra ventana del query y mostramos el plan de ejecución de nuestro procedimiento almacenado, veremos que ahora está utilizando nuestro índice (seek) para obtener los registros deseados (ixStudents_Registration)

Espero les sea de mucha utilidad.

jueves, 16 de febrero de 2012

WITH RECOMPILE y FREEPROCCACHE



Continuaremos viendo las opciones que tenemos para forzar la compilación de un procedimiento almacenado, y tomando la lista de la entrada anterior (RECOMPILE o no RECOMPILE) comenzaremos con la opción WITH RECOMPILE durante la ejecución de un procedimiento.

2. Ejecutar el procedimiento almacenado con la opción RECOMPILE.

Una vez que ya tenemos el plan de ejecución de nuestro procedimiento almacenado, éste no se calculará de nuevo de forma automática.

¿Por qué nos interesaría forzar la compilación de nuestro procedimiento?

Supongamos que después de un proceso de análisis en el uso de la información, se deberminó crear una vista indexada que tomara las columnas Name y City de nuestra tabla Customers (clic aquí para ver el código para la creación de los elementos) pero como nuestro plan de ejecución ya está calculado, éste jamás tomará en cuenta nuestra nueva estructura de datos.

Para generar la vista indexada que cubra la propuesta del párrafo anterior podemos hacer lo siguiente:

CREATE VIEW vCustomers
WITH SCHEMABINDING
AS
      SELECT Id, Name, City
      FROM dbo.Customers
GO
CREATE UNIQUE CLUSTERED INDEX vCustomers_Id
ON vCustomers (Id)
GO
CREATE INDEX ixVCustomers_City
ON vCustomers (City)
Ejecutemos el código que modifica nuestro procedimiento almacenado para que no tenga el modificador RECOMPILE:
ALTER PROC pFindCustomers(
      @Column VARCHAR(30),
      @Condition VARCHAR(10)
)
AS
BEGIN
      IF @Column = 'City'
      BEGIN
            SELECT Id, Name, City, RegistrationDate
            FROM Customers
            WHERE City = @Condition
      END
      ELSE IF @Column = 'RegistrationDate'
      BEGIN
            SELECT Id, Name, City, RegistrationDate
            FROM Customers
            WHERE RegistrationDate = @Condition
      END
END

Si ejecutamos una búsqueda por fecha de registro y posteriormente vemos el plan de ejecución de la búsqueda por ciudad, vamos a ver que es el mismo.

Bien, en una nueva ventana del Management Studio vamos a escribir la siguiente sentencia, damos clic derecho sobre el editor y después elegimos “Display estimated execution plan”:

pFindCustomers 'City', '0014' WITH RECOMPILE

Veremos que con ahora ya se estará haciendo uso de nuestra vista indexada

3. Ejecutar DBCC FREEPROCCACHE

La ejecución de esta sentencia eliminará todos los planes de ejecución que hayan sido almacenados obligando a que se vuelva a calcular cada uno de ellos conforme se vayan necesitando.

Dado que cada query será “recalculado”, veremos una degradación importante en el rendimiento de nuestro gestor de bases de datos por lo que recomiendo que se use muy poco.

¿Cuándo lo utilizaría yo?, después de aplicar un “service pack” a la base de datos con mejoras de rendimiento y modificación de estructuras de almacenamiento.

En la siguiente entrada abordaremos los puntos 4 y 5 de la lista.

Comenzando en Blogger


Desde hace tiempo tenía la idea de comenzar un blog que abordara temas de desarrollo web y bases de datos pero entre el trabajo y la flojera inherente al tiempo de descanso simplemente no había podido aterrizar este pequeño y sencillo proyecto.

Creo que ahora es el momento, para darle sentido a esta vida uno debe de cumplir hasta con los objetivos más sencillos que se haya planteado y esa es la razón que me ha impulsado a comenzar con este blog.

Sería obtuso pensar que un comentario emitido será aceptado por todas las personas que lo lean, y teniendo eso en cuenta, no busco el crear empatía con el visitante, simplemente compartir un poco de conocimiento, generar diferencia de opiniones y así enriquecer el contenido de este blog.

¡Bienvenido!... ¡y que viva México!

miércoles, 15 de febrero de 2012

RECOMPILE o no RECOMPILE



La ventaja de utilizar procedimientos almacenados es que el proceso de la planeación de la ejecución del código que lo compone se realiza una sola vez (durante su primera ejecución) y el plan es almacenado para futuras referencias.

El proceso de planear la ejecución de un query es un tanto lento dado que se deben de explorar las estructuras de datos que existen alrededor de la información que se desea consultar, sean las tablas mismas, índices clustered, índices nonclustered, índices de cobertura, vistas indexadas, estadísticas, etc.

Es importante tomar en cuenta que el plan de ejecución habrá sido calculado según la cantidad de información y las estructuras previamente mencionadas al momento de su ejecución.

Dependiendo de la estructura o de la lógica de nuestro procedimiento almacenado deberemos estudiar la posibilidad de obligar a que el plan de ejecución sea recalculado.

Para lograr este objetivo existen muchas opciones:

1. Crear el procedimiento almacenado con la opción RECOMPILE.
2. Ejecutar el procedimiento almacenado con  la opción RECOMPILE.
3. Ejecutar DBCC FREEPROCCACHE.
4. Recrear el procedimiento almacenado.
5. Usar el procedimiento almacenado sp_recompile.

En esta entrada del blog abordaré el punto 1, en unos días terminaré de comentar los siguientes puntos.

1. Crear procedimiento almacenado con la opción RECOMPILE.

Es probable que muchas personas entren en conflicto con esta opción dado que uno de los objetivos principales de los procedimientos almacenados es que su plan de ejecución quede almacenado y así nos evitemos la tarea de estar recalculando el plan de ejecución cada vez que se haga uso de ellos.

Para aterrizar esta idea, utilizaré la siguiente estructura:

CREATE TABLE Customers(
      Id INT IDENTITY(1, 1) NOT NULL,
      Name VARCHAR(80) NOT NULL,
      City VARCHAR(4) NOT NULL,
      RegistrationDate DATE NOT NULL,
      CONSTRAINT pkCustomers PRIMARY KEY (Id)
)
GO
CREATE NONCLUSTERED INDEX ixCustomers_City
ON Customers (RegistrationDate)
La llenaremos con datos ficticios a través del siguiente query:
DECLARE @i INT = 0
DECLARE @Name VARCHAR(80)
DECLARE @City VARCHAR(4)
DECLARE @RegistrationDate DATE
WHILE @i < 30000
BEGIN
      SET @Name = REPLACE(CAST(NEWID() AS VARCHAR(36)), '-', '')
      SET @City = SUBSTRING(REPLACE(CAST(NEWID() AS VARCHAR(36)), '-', ''), 1, 4)
      SET @RegistrationDate = DATEADD(DAY, CHECKSUM(NEWID()) % 15, GETDATE())
      INSERT INTO Customers (Name, City, RegistrationDate)
      VALUES (@Name, @City, @RegistrationDate)
      SET @i = @i + 1
END

Supongamos que nuestro procedimiento almacenado recibe como parámetro la columna bajo la cual se realizará una búsqueda de información.

CREATE PROC pFindCustomers(
      @Column VARCHAR(30),
      @Condition VARCHAR(10)
)
AS
BEGIN
      IF @Column = 'City'
      BEGIN
            SELECT Id, Name, City, RegistrationDate
            FROM Customers
            WHERE City = @Condition
      END
      ELSE IF @Column = 'RegistrationDate'
      BEGIN
            SELECT Id, Name, City, RegistrationDate
            FROM Customers
            WHERE RegistrationDate = @Condition
      END
END

Ejecutemos nuestro procedimiento almacenado para buscar a los clientes de la ciudad “0014” (busquen alguna ciudad que exista en los registros de la tabla para que al menos vean resultados)

pFindCustomers 'City', '0014'

Ahora, exploremos los queries que han sido ejecutados para encontrar el plan de ejecución almacenado.

SELECT C.plan_handle, T.text, P.query_plan
FROM sys.dm_exec_cached_plans C
CROSS APPLY sys.dm_exec_sql_text(C.plan_handle) T
CROSS APPLY sys.dm_exec_query_plan(C.plan_handle) P

Buscaremos en la columna “text” algo que comience con “CREATE PROC pFindCustomers …” y daremos clic en la celda de la columna query_plan.

Sólo demos un vistazo superficial al plan de ejecución para verificar algunas tareas que está llevando a cabo: ixCustomers_City (seek) y pkCustomers (scan)

Ahora en una nueva ventana del Management Studio escribimos lo siguiente:

pFindCustomers 'RegistrationDate', '2012-02-07'

Demos clic derecho y elegimos la opción “Display estimated execution plan” y veremos que el plan es el mismo que habíamos visto antes, entonces sin importar que tengamos una cantidad enorme de posibilidades de búsqueda, siempre se utilizará el mismo plan de ejecución el cual sólo era óptimo para la primera ejecución.

Para este tipo de problemas, podemos modificar nuestro procedimiento almacenado para que cada vez que se ejecute se recalcule el plan de ejecución:

ALTER PROC pFindCustomers(
      @Column VARCHAR(30),
      @Condition VARCHAR(10)
)
WITH RECOMPILE
AS
BEGIN ...

He colocado puntos suspensivos dado que lo demás sigue igual.

En una nueva ventana escribimos el query para buscar por ciudad y elegimos la opción “Display Estimated Execution Plan”, veremos que es el mismo plan que ya habíamos visto previamente.

Ahora volvamos a la ventana donde está nuestro query para la búsqueda por “RegistrationDate” y elegimos de nuevo la opción “Display Estimated Execution Plan”… ¡zaz!, podemos ver que el plan de ejecución es diferente y está optimizado para la búsqueda que ejecutamos.

En la siguiente entrada veremos los siguientes puntos de la lista.

martes, 14 de febrero de 2012

Fuentes de información


Dicen que la información es poder, y tener buenas fuentes creo que es una herramienta básica para mantenernos al día, o lo más cercanamente posible a ello.
A continuación presento una lista de las páginas donde usualmente ando navegando para estar al pendiente de las tendencias de los temas que me interesan (y de vez en cuando de los que no)
  1. www.techrepublic.com
  2. www.techworld.com
  3. www.pcnews.com
  4. www.diarioti.com
Ahora que si el objetivo es aprender pues creo que www.experts-exchange.com es una excelente opción, éste es un sitio donde las empresas o algunos usuarios pagan por poder publicar sus preguntas o problemas y los expertos se pelean por ver quién da la solución.
Seguramente pensarán, ¿por qué aprender solucionando problemas de los demás?, por el simple hecho de que al plasmar una idea, los demás expertos te dirán por qué sí o por qué no es una buena opción tu opinión... y a través de ello podrás tener una retroalimentación enorme.
Y bueno, dado que toda mi vida profesional he trabajado con tecnologías Microsoft, estoy plenamente convencido de que la documentación en línea es una excelente fuente de información, lo que es el MSDN.
Quisiera terminar esta entrada con un comentario que alguna vez me dijo una persona a la que respeto y admiro mucho: "la mejor tecnología, es la que uno domina"