jueves, 17 de enero de 2013

Programación en SQL Server - parte 2



En la entrada anterior hablábamos de los aspectos iniciales de la sentecia SELECT, en ésta platicaremos acerca de las implicaciones que tiene la palabra reservada JOIN.

La palabra reservada JOIN nos sirve para establecer un criterio a través del cual elegiremos filas o datos que queremos recuperar de dos tablas que comparten valores en una o más columnas (podría ser un valor calculado al vuelo pero no se recomienda dado que significaría en una sobrecarga de trabajo)

Llamaremos tabla "izquierda" a la que aparece primero en la sentencia y tabla "derecha" la que aparece después, por ejemplo:

SELECT I.Col1, I.Col2, D.Col1, D.Col2
FROM Izquierda I
INNER JOIN Derecha D
ON I.Col1 = D.Col1

INNER JOIN nos devolverá únicamente aquellas filas que cumplan con el criterio o criterios definidos en la parte del "ON", en este caso sólo devolvería las filas que tengan el mismo valor en Col1 de la tabla Izquierda y Col1 de la tabla Derecha.

Es común que en las bases de datos entidad - relación se haga JOIN entre dos tablas relacionadas a través de una llave foránea, lo cual no es obligatorio; si esta tarea se ejecuta forma habitual entonces deberemos valorar el crear un índice que cubra las columnas que conforman a la llave foránea.    Por ejemplo, supongamos que tenemos una tabla de Categorías y otra tabla de Productos:

CREATE TABLE Categories(
    Id SMALLINT IDENTITY(1, 1),
    Name VARCHAR(10) NOT NULL,
    CONSTRAINT pkCategories PRIMARY KEY (Id)
)
GO
CREATE TABLE Products(
    Id SMALLINT IDENTITY(1, 1),
    Id_Category SMALLINT NOT NULL,
    Name VARCHAR(10) NOT NULL DEFAULT SUBSTRING(REPLACE(CAST(NEWID() AS VARCHAR(36)), '-', ''), 1, 10),
    CONSTRAINT pkProducts PRIMARY KEY (Id),
    CONSTRAINT fkProducts_Categories FOREIGN KEY (Id_Category) REFERENCES Categories (Id)
)

Y que han sido llenadas con el siguiente código:

INSERT INTO Categories (Name) VALUES ('Category 1')
INSERT INTO Categories (Name) VALUES ('Category 2')
INSERT INTO Categories (Name) VALUES ('Category 3')
INSERT INTO Categories (Name) VALUES ('Category 4')
INSERT INTO Categories (Name) VALUES ('Category 5')
GO
DECLARE @i SMALLINT = 1
WHILE @i <= 5000
BEGIN
    INSERT INTO Products (Id_Category)
    VALUES ((ABS(CHECKSUM(NEWID())) % 5) + 1)

    SET @i = @i + 1
END

Si el sistema pide que los productos sean filtrados por categoría para ser mostrados al cliente, tendríamos un query como este:

SELECT P.Name, C.Name AS Category
FROM Products P
INNER JOIN Categories C
ON P.Id_Category = C.Id
WHERE C.Id = 3

Si observamos el plan de ejecución del query, veremos que se está llevando a cabo un barrido del CLUSTERED INDEX pkProducts, lo cual implica en un trabajo prolongado y que consume recursos.


El costo aproximado de este query es de: 0.260062

Tal como lo había mencionado antes, si es habitual la ejecución de este query, es súper recomendable crear un índice que cubra las columnas que son utilizadas en la parte del "ON".    En este caso también incluiremos la columna "Name" en el covering index para que el resultado se tenga a la mano en la estructura misma del índice:


CREATE INDEX ixProducts_Category
ON Products (Id_Category)
INCLUDE (Name)

Calculemos de nuevo el plan de ejecución y veremos que ha cambiado el barrido físico por una búsqueda en el índice que acabamos de crear, si analizamos el costo reportado por SQL Server veremos que es de: 0.0799945, lo cual significa una mejora del 325.09%, que se verá reflejada en una rápida lectura y también en un menor consumo de recursos.



En la siguiente entrada veremos el uso del LEFT JOIN, espero te haya sido de utilidad.

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.