martes, 2 de abril de 2013

Programación en SQL Server - parte 3



En la entrada anterior vimos cómo funciona el INNER JOIN para cruzar la información de dos o más tablas, en esta entrada abordaremos el LEFT JOIN.

Supongamos que tenemos el siguiente query:

SELECT T1.Col1, T1.Col2
FROM Table1 T1
LEFT JOIN Table2 T2
ON T1.Col1 = T2.Col1

A la tabla Table1 se le considera tabla izquierda y a la tabla Table2 se le considera la tabla derecha, es importante tener esto en mente para poder identificar qué datos vamos a obtener del query.

LEFT JOIN devolverá todas las filas de la tabla izquierda y de la tabla derecha devolverá valores para sus columnas cuando se haya encontrado una correspondencia en el predicado ON, para aquellas filas de la tabla izquierda que no encuentren correspondencia en la tabla derecha, las columnas de la tabla derecha tendrán el valor NULL.

Por ejemplo, supongamos que tenemos las siguientes tablas:

CREATE TABLE Categories(
    Id TINYINT IDENTITY(1, 1),
    Name VARCHAR(10) NOT NULL,
    CONSTRAINT pkCategories PRIMARY KEY (Id)
)
GO
CREATE TABLE Products(
    Id TINYINT IDENTITY(1, 1),
    Id_Category TINYINT NOT NULL,
    Name VARCHAR(10) NOT NULL,
    CONSTRAINT pkProducts PRIMARY KEY (Id),
    CONSTRAINT fkProducts_Categories FOREIGN KEY (Id_Category) REFERENCES Categories (Id)
)
GO
INSERT INTO Categories (Name)
VALUES ('Category 1'), ('Category 2'), ('Category 3')
GO
INSERT INTO Products (Id_Category, Name)
VALUES (1, 'Product 1'), (1, 'Product 2'), (3, 'Product 3')


Podemos ver que la categoría "Category 2" no tiene productos, ¿qué pasa si nos pidieran un reporte donde mostremos la cantidad de productos que tiene cada categoría?, intentemos con el INNER JOIN y veamos el resultado:

SELECT C.Name, COUNT(*) AS ProductsCount
FROM Categories C
INNER JOIN Products P
ON C.Id = P.Id_Category
GROUP BY C.Name


El resultado es el siguiente:


Podemos ver que la categoría "Category 2" no aparece en el resultado, esto es debido a que INNER JOIN sólo mostrará aquellas filas que encontraron correspondencia directa y como no se encontró el Id de la categoría "Category 2" en la tabla Products, entonces no se toma en cuenta.

Cambiemos el query por un LEFT JOIN y veamos que ahora sí muestra el resultado tal como lo queremos:

SELECT C.Name, COUNT(*) AS ProductsCount
FROM Categories C
LEFT JOIN Products P
ON C.Id = P.Id_Category
GROUP BY C.Name


Veamos el resultado:


¿Qué pasó? ¡se supone que no hay productos en la categoría "Category 2"!, el problema radica en que COUNT(*) cuenta la fila, y como "Category 2" forma parte del resultado por ser tabla izquierda en un LEFT JOIN, entonces la cuenta, veamos el query sin el COUNT(*) y verifiquemos que en efecto hay una fila que tiene "Category 2":

SELECT C.Name AS Category, P.Name AS Product
FROM Categories C
LEFT JOIN Products P
ON C.Id = P.Id_Category


Veamos el resultado:


Aún cuando no tenemos un valor en la columna de la tabla derecha (Products) la función de agregación COUNT(*) devuelve 1 porque existe una fila que tiene "Category 2".    Vamos a cambiar nuestro query para que cuente los valores de la tabla derecha y así descarte los nulos:

SELECT C.Name, COUNT(P.Name) AS ProductsCount
FROM Categories C
LEFT JOIN Products P
ON C.Id = P.Id_Category
GROUP BY C.Name


El resultado será el siguiente:


¡Ahora sí!, este es justamente el resultado que estábamos buscando, la categoría "Category 2" no tiene productos y por lo tanto debe mostrar el número cero.

Espero te resulte de ayuda esta entrada y haya quedado claro cómo utilizar el LEFT JOIN, en la siguiente entrada veré una instrucción súper interesante llamada MERGE.

viernes, 8 de marzo de 2013

SLA del SAT



Hoy acudí al SAT a realizar un trámite y me encontré con la sorpresa de que no tenían sistema de forma local, es decir que no era un problema del SAT a nivel nacional, sólo de la oficina a la que acudí a mi cita.

Después de platicar por unos momentos con la persona encargada de darme lo que fui a solicitar me enteré que el sistema estaba caído desde que comenzó el día por lo que todos los que habían ido, los que estábamos y los que pasaran después de mí no podrían obtener lo que necesitábamos.

Me pregunté si el personal de sistemas del SAT tiene un SLA por cumplir, si es que tienen planes para la recuperación de desastres y así poder atender como se debe, al final ellos son nuestros proveedores de información y documentos y tenemos derecho a obtener un servicio de calidad.

El personal que labora ahí es sumamente atento, de eso no hay la menor duda, en todo momento están al pendiente si es que nesitas algo, revisan la papelería, te aconsejan, etc.; en esta entrada de blog quisiera analizar el actuar del personal de sistemas.

Una vez que me dijeron que no había sistema, me hicieron firmar un papel donde me avisaban que a más tardar en 72 horas hábiles (3 días) me entregarían lo que fui a solicitar, esto implica que durante esos 3 días podrían seguir teniendo tirado el sistema sin problema alguno.    Si a estos tres días le sumamos el día de hoy, estaríamos hablando de 4 días hábiles sin sistema, de aquí me desprende otra pregunta, ¿en qué empresa le permitirían al jefe de sistemas y a su personal el tener la operación detenida por 4 días seguidos?, estoy seguro que en ninguna que tenga más de 2 centavos de responsabilidad.

Saqué la cuenta de los días hábiles (lunes - viernes) que hay entre el 1 de enero del 2013 y el 31 de diciembre del 2013, son 261 días, si a esta cantidad le quitamos los días que están establecidos en la ley federal del trabajo en su artículo 74, entonces quedamos con 254 días.

No buscaré quitarle más días investigando en qué otras fechas no trabajan estas personas, no quiero que me duela el estómago del enojo.

Calculemos su SLA esperado al día de hoy, si 254 días de servicio son el 100% y le quitamos los 4 días que arbitrariamente se permiten perder quedamos con 250 días, lo cual nos da un SLA del 98.41% de disponibilidad.

¡Sí!, 98.41% es el porcentaje que, a lo máximo, podrían cumplir este año... veamos bien que es un SLA ¡de un nueve!, no es malo, es pésimo.

Lamentablemente el trabajo de la gente de sistemas de esta oficina es verdaderamente desastroso y patético, es un ejemplo perfecto de lo que es un servicio deficiente e incapaz de cubrir las necesidades que plantea su puesto.

De aquí se desprende una invitación a toda persona que se dedique a la administración de servidores y bases de datos a que se actualice, se prepare, estudie, intente e investigue diferentes técnicas para ofrecer un servicio de calidad y que, al menos, genere un SLA de dos nueves, sé que es una cantidad aún extremadamente baja, pero ir trabajando poco a poco irá subiendo ese número hasta llegar a niveles de calidad superior.

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.