Novedades de Transact-SQL 2008 (III)

Este es el último post sobre las novedades que presenta en lenguaje T-SQL para la versión 2008 de SQL Server.

Instrucción PIVOT

La instrucción PIVOT nos permitirá obtener un conjunto de resultados en un formato similar a una tabla dinámica de Excel, facilitando su análisis para la posterior toma de decisiones. Veamos un ejemplo, en el que obtendremos las ventas de productos por comercial, zona y año:

SELECT    soh.[SalesPersonID]
,c.[FirstName] + ' ' + COALESCE(c.[MiddleName], '') + ' '
+ c.[LastName] AS [FullName]
,e.[Title]
,st.[Name] AS [SalesTerritory]
,soh.[SubTotal]
,YEAR(DATEADD(m, 6, soh.[OrderDate])) AS [FiscalYear]
FROM [Sales].[SalesPerson] sp
INNER JOIN
[Sales].[SalesOrderHeader] soh
ON sp.[SalesPersonID] = soh.[SalesPersonID]
INNER JOIN
[Sales].[SalesTerritory] st
ON sp.[TerritoryID] = st.[TerritoryID]
INNER JOIN
[HumanResources].[Employee] e
ON soh.[SalesPersonID] = e.[EmployeeID]
INNER JOIN
[Person].[Contact] c
ON e.[ContactID] = c.ContactID

 Pivot01
En el resultado obtenido se muestran los datos deseados, pero… ¿Y si quisiéramos mostrar los ventas totales por comercial y año? Una forma rápida de resolverlo sería mediante el uso de la instrucción PIVOT, tal y como se muestra seguidamente:
 
SELECT * FROM 
(SELECT
soh.[SalesPersonID]
,c.[FirstName] + ' ' + COALESCE(c.[MiddleName], '') + ' '
+ c.[LastName] AS [FullName]
,e.[Title]
,st.[Name] AS [SalesTerritory]
,soh.[SubTotal]
,YEAR(DATEADD(m, 6, soh.[OrderDate])) AS [FiscalYear]
FROM [Sales].[SalesPerson] sp
INNER JOIN [Sales].[SalesOrderHeader] soh
ON sp.[SalesPersonID] = soh.[SalesPersonID]
INNER JOIN [Sales].[SalesTerritory] st
ON sp.[TerritoryID] = st.[TerritoryID]
INNER JOIN [HumanResources].[Employee] e
ON soh.[SalesPersonID] = e.[EmployeeID]
INNER JOIN [Person].[Contact] c
ON e.[ContactID] = c.ContactID
) AS source
PIVOT
(
SUM([SubTotal])
FOR [FiscalYear]
IN ([2002], [2003], [2004])
) as pvt

Pivot02
La transformación ha permitido poner en columnas los valores de los distintos años (2002, 2003 y 2004), y en las filas el comercial y su zona, mientras que en el área de valores hemos puesto las ventas. Cabe observar que aparecen valores NULL debido a que no todos los comerciales han realizado ventas durante alguno de los años.
 
Instrucción UNPIVOT

Mediante UNPIVOT podremos hacer el paso inverso al mostrado previamente. A partir de unos resultados mostrados en formato agrupado, podemos intentar mostrarlos en formato tabla. Para clarificar el ejemplo, en primer lugar guardaremos el resultado de la instrucción PIVOT (mostrado anteriormente) en una tabla (se llamará dbo.VentasComercial):

SELECT * 
INTO dbo.VentasComercial
FROM
(SELECT
soh.[SalesPersonID]
,c.[FirstName] + ' ' + COALESCE(c.[MiddleName], '') + ' '
+ c.[LastName] AS [FullName]
,e.[Title]
,st.[Name] AS [SalesTerritory]
,soh.[SubTotal]
,YEAR(DATEADD(m, 6, soh.[OrderDate])) AS [FiscalYear]
FROM [Sales].[SalesPerson] sp
INNER JOIN [Sales].[SalesOrderHeader] soh
ON sp.[SalesPersonID] = soh.[SalesPersonID]
INNER JOIN [Sales].[SalesTerritory] st
ON sp.[TerritoryID] = st.[TerritoryID]
INNER JOIN [HumanResources].[Employee] e
ON soh.[SalesPersonID] = e.[EmployeeID]
INNER JOIN [Person].[Contact] c
ON e.[ContactID] = c.ContactID
) AS source
PIVOT
(
SUM([SubTotal])
FOR [FiscalYear]
IN ([2002], [2003], [2004])
) as pvt
 
Ahora podemos ejecutar la instrucción UNPIVOT, accediendo a la información almacenada en la tabla dbo.VentasComercial.

SELECT  SalesPersonID,
Title,
SalesTerritory,
FiscalYear,
Sales
FROM
(SELECT SalesPersonID,
FullName,
Title,
SalesTerritory,
[2002],
[2003],
[2004]
FROM dbo.VentasComercial) as pvt
UNPIVOT (Sales FOR FiscalYear IN ([2002],[2003],[2004])) AS unpvt;

Unpivot01

Finalmente podemos observar como tenemos resultados similares al primer conjunto de resultados mostrado en este artículo, aunque en este caso sólo obtenemos un registro por comercial y año.

Funciones RANK y DENSE_RANK

Entre las distintas funciones de ranking que presenta SQL Server, vamos a ver en primer lugar la función DENSE_RANK. Mediante esta instrucción podemos obtener en qué posición se encuentra una fila respecto al conjunto total de resultados. En el ejemplo que se muestra seguidamente se realiza un ranking de productos según la cantidad que existe de cada uno de ellos en los distintos almacenes:

SELECT  i.ProductID, 
p.Name,
i.LocationID,
i.Quantity
,DENSE_RANK() OVER
(PARTITION BY i.LocationID ORDER BY i.Quantity) AS 'RANK'
FROM Production.ProductInventory i
INNER JOIN
Production.Product p
ON i.ProductID = p.ProductID
ORDER BY i.LocationID,i.Quantity

Rank01

Es importante destacar que se pueden producir empates, cuando existe la misma cantidad de productos distintos en un mismo almacén.

Así, también disponemos de la función RANK, que es similar a la anterior, aunque en este caso los números asignados en el ranking no son contiguos.

SELECT  i.ProductID, 
p.Name,
i.LocationID,
i.Quantity
,RANK() OVER
(PARTITION BY i.LocationID ORDER BY i.Quantity) AS 'RANK'
FROM Production.ProductInventory i
INNER JOIN
Production.Product p
ON i.ProductID = p.ProductID
ORDER BY i.LocationID,i.Quantity

Rank02

En este caso, como se produce un empate en el ranking 7, el siguiente numero asignado es el 9, hecho que no se producia con la función DENSE_RANK.

Funciones de Fecha y Hora

En la versión de SQL Server 2008 han aparecido nuevos tipos de datos para gestionar fechas y horas: datetime2, time, date y datetimeoffset , que permiten almacenar y manipular fechas en un formato más preciso que el tipo datetime. En el siguiente ejemplo vamos a mostrar como usar los distintos tipos de datos y las funciones SYSUTCDATETIME, SYSDATETIME y SYSDATETIMEOFFSET:

SELECT  SYSUTCDATETIME() AS UTC, 
SYSDATETIME() AS FECHA_HORA,
SYSDATETIMEOFFSET() AS DIF_HORARIA,
CONVERT(DATE,SYSDATETIME()) AS FECHA,
CONVERT(TIME,SYSDATETIME()) AS HORA

Date01

Podemos comprobar como podemos convertir un dato del tipo datetime2 (el que nos devuelve la función SYSDATETIME) a un tipo date o time para obtener unicamente la fecha o la hora.

Aunque existen más novedades, explicaré algunas de ellas en artículos más concretos.

Espero que todo lo que he contado os sirva… ¡Y enjoy T-SQL!

Tags: , , , , , ,

Novedades de Transact-SQL 2008 (II)

En este post seguiré explicando algunas de las novedades que presenta el lenguaje T-SQL en SQL Server 2008.

Extensión GROUPING SETS

Grouping Sets es una extensión de la instrucción Group By que nos permitirá obtener resultados agregados mediante una agrupación múltiple en única sentencia. Ciertamente la definición parece un poco complicada, y por eso más vale un ejemplo que mil palabras.

Mediante la siguiente instrucción obtendremos el precio medio de los productos, agrupados por año y subcategoría:

SELECT   ProductSubcategoryID,
YEAR(SellStartDate) AS Year,
AVG(ListPrice) AS AvgPrice
FROM Production.Product
WHERE ProductSubcategoryID IS NOT NULL
GROUP BY GROUPING SETS((ProductSubcategoryID),(YEAR(SellStartDate)))
ORDER BY ProductSubcategoryID,YEAR(SellStartDate)

GroupingSets01

 

 

 

 

 

 

Vemos como obtenemos los totales por año y por subcategoría de producto. Sin la extensión Grouping Sets, y para obtener el mismo resultado, hubiéramos tenido que crear una instrucción con dos consultas unidas mediante el operador UNION.

SELECT   NULL,
YEAR(SellStartDate) AS Year,
AVG(ListPrice) AS AvgPrice
FROM Production.Product
WHERE ProductSubcategoryID IS NOT NULL
GROUP BY YEAR(SellStartDate)
UNION
SELECT ProductSubcategoryID,
NULL AS Year,
AVG(ListPrice) AS AvgPrice
FROM Production.Product
WHERE ProductSubcategoryID IS NOT NULL
GROUP BY ProductSubcategoryID

Extensiones COMPUTE, ROLLUP y CUBE

Mediante la extensión COMPUTE de la instrucción SELECT es posible obtener un valor total a partir de una consulta y la operación especificada en la extensión. En el siguiente ejemplo obtendremos el precio medio de los productos agrupados por su color y los días que tardan en fabricarse. Adicionalmente obtendremos el precio medio de todos los productos listados:

SELECT   Color,
DaysToManufacture,
AVG(ListPrice) AS AvgPrice
FROM Production.Product
WHERE Color IS NOT NULL
AND DaysToManufacture IS NOT NULL
GROUP BY Color,DaysToManufacture
COMPUTE AVG(AVG(ListPrice))

Compute01

Aunque esta consulta puede sernos muy útil, no lo será tanto si ésta se debe ser llamada desde una aplicación, ya que nos devolverá dos conjuntos de resultados distintos. Además, como bien indica la ayuda sobre la instrucción COMPUTE, se mantiene por un tema de compatibilidad, pero en futuras versiones no será soportada y en su lugar se deberá utilizar la extensión ROLLUP. El siguiente ejemplo utiliza esta extensión:

SELECT   Color,
DaysToManufacture,
AVG(ListPrice) AS AvgPrice,
GROUPING(DaysToManufacture) AS Grouped
FROM Production.Product
WHERE Color IS NOT NULL
AND DaysToManufacture IS NOT NULL
GROUP BY Color,DaysToManufacture WITH ROLLUP
Order By Color,DaysToManufacture

Rollup01
 
Podemos observar como mediante la instrucción ROLLUP se realizan todas las combinaciones de valores de la columna color con el resto de columnas. En aquellas columnas donde se muestra el valor NULL significa que es un campo de valor agregado o total. Aun así, se puede utilizar la función GROUPING para saber si un valor es totalizado o no por esa columna.
 
Por último, también podemos utilizar la extensión CUBE, que realiza una función similar a la extensión ROLLUP, con la diferencia de que la primera realiza todas las combinaciones posibles entre los distintos valores de las columnas especificadas en la consulta, y no con una única columna. Veamos un ejemplo:
 
SELECT   Color,
DaysToManufacture,
AVG(ListPrice) AS AvgPrice,
GROUPING(Color) AS GroupedColor,
GROUPING(DaysToManufacture) AS GroupedDays
FROM Production.Product
WHERE Color IS NOT NULL
AND DaysToManufacture IS NOT NULL
GROUP BY Color,DaysToManufacture WITH CUBE
ORDER BY Color,DaysToManufacture


Cube01
 
A diferencia del ejemplo anterior, en este caso obtenemos el precio medio de los productos segun los días de fabriación, sin tener en cuenta el color. Así mismo, introducimos dos columnas GROUPING, para saber si el resultado obtenido es un valor agrupado por alguna de las dos columnas (color, DaysToManufacture).
 
Ciertamente, con la instrucciones usando ROLLUP y CUBE no hemos obtenido exactamente el mismo resultado que con la instrucción COMPUTE. En esta última instrucción obteníamos el precio medio para todos los productos, independientemente del color y de los días de fabricación (obtuvimos el valor de 607,5081).
 
Con la instrucción ROLLUP el valor obtenido es de 847,2869 (el valor obtenido cuando las columnas color y DaysToManufacture tienen el valor NULL). Esto es debido a que en la instrucción ROLLUP también se tiene en cuenta el valor del precio medio del producto cuando la columna DaysToManufacture es NULL, cuando esto no debería ser así. Por ese mismo motivo, deberíamos escribir una nueva consulta utilizando las CTE (Expresiones Comunes de Tablas, o Common Table Expressions), instrucción que ya apareció en la versión 2005 de SQL Server.
 
WITH Resumen (Precio,Color,Dias)
AS
(
SELECT AVG(ListPrice),
Color,
DaysToManufacture
FROM Production.Product
WHERE Color IS NOT NULL
AND DaysToManufacture IS NOT NULL
GROUP BY Color,DaysToManufacture WITH ROLLUP
)
SELECT AVG(Precio) FROM Resumen WHERE Dias IS NOT NULL

Espero haberos aportado un poco de luz sobre el tema. Igualmente os recomiendo ejecutar las consultas y analizar los resultados obtenidos para comprender definitivamente las diferencias entre las distintas instrucciones.

¡Enjoy (again) T-SQL!

Tags: , , , ,

Novedades de Transact-SQL 2008 (I)

En este post voy a explicar las principales novedades o funcionalidades que incorpora SQL Server 2008 desde un punto de vista de programación mediante el lenguaje Transact-SQL (T-SQL).

Os recomiendo instalar la base de datos de ejemplo AdventureWorks2008, que se encuentra aquí.

Declaración de variables e inicializaciones

Ya es posible declarar una variable y asignarle al mismo tiempo un valor:

DECLARE @intNumero INT=0
 
En este caso declaramos una variable de tipo entero y se le asigna el valor 0.

Constructores de fila

Mediante los constructores de fila es posible insertar distintas filas en una tabla mediante una única sentencia. El ejemplo se basa en una tabla creada previamente, que se incluye en el script:

--CREACIÓN DE LA TABLA
CREATE TABLE dbo.Paises
(
IDPais VARCHAR(2) PRIMARY KEY,
Descripcion VARCHAR(500),
FechaModif DATETIME
)

--INSERT MEDIANTE CONSTRUCTORES DE FILA
INSERT INTO dbo.Paises
VALUES ('MA','Malta',SYSDATETIME()),
('IR','Ireland',SYSDATETIME()),
('FR','France',SYSDATETIME())

En este caso creamos una tabla que guarda información de paises, y en una sola instrucción insert agregamos 3 registros en la tabla.

Cabe indicar que el número máximo de filas a insertar en una instrucción es de 1.000, y que la instrucción se ejecuta como una única transacción (si alguno de los insert no es correcto, no se realiza ninguno de ellos).

Instrucción OUTPUT

Mediante la instrucción OUTPUT podemos acceder a los registros afectados por una operación de insert, update, delete o merge. Es importante notar que esta instrucción ya estaba disponible en SQL Server 2005.

--DECLARAMOS UNA VARIABLE DE TIPO TABLA
DECLARE @tblEliminados TABLE(Codigo VARCHAR(2),Nombre VARCHAR(500))

--ELIMINAMOS TODOSO LOS REGISTROS DE LA TABLA Y LOS GUARDAMOS EN LA VARIABLE
DELETE FROM dbo.Paises
OUTPUT deleted.IDPais, deleted.Descripcion INTO @tblEliminados

--MOSTRAMOS LOS REGISTROS ELIMINADOS
SELECT * FROM @tblEliminados

En este ejemplo podemos comprobar como solo guardamos los datos de las columnas IDPais y Descripción sobre la variable de tipo tabla creada previamente (en las columnas codigo y nombre).

Instrucción MERGE

Mediante esta instrucción es posible definir qué acción se debe llevar a cabo sobre los registros de una tabla cuando éstos se encuentran (match) o no sobre los registros de una tabla origen distinta.

En el siguiente ejemplo añadiremos paises a nuestra tabla a partir de una tabla origen (person.CountryRegion de la base de datos AdventureWorks), siempre y cuando no existan previamente. Si se produce este último caso, entonces unicamente actualizaremos su fecha de modificacion.

MERGE dbo.Paises AS target
USING
(
SELECT CountryRegionCode,
Name
FROM Person.CountryRegion
) AS source (codigo,nombre)
ON target.IDPais=source.codigo
WHEN MATCHED THEN UPDATE SET FechaModif=SYSDATETIME()
WHEN NOT MATCHED THEN INSERT VALUES (codigo,nombre,SYSDATETIME());
 
Es importante observar la sintaxis de la instrucciones update e insert, ya que en ninguna de ambas se especifica la tabla donde se realiza la operación (se supone que es la tabla de destino o target, que en nuestro caso es la tabla dbo.Paises).
 
Esta instrucción merge también se puede usar conjuntamente con la instrucción output, de forma que podamos capturar la información de los registros afectados por la operación.
 
MERGE dbo.Paises AS target
USING
(
SELECT CountryRegionCode,
Name
FROM Person.CountryRegion
) AS source (codigo,nombre)
ON target.IDPais=source.codigo
WHEN MATCHED THEN UPDATE SET FechaModif=SYSDATETIME()
WHEN NOT MATCHED THEN INSERT VALUES (codigo,nombre,SYSDATETIME())
OUTPUT $action, Inserted.IDPais, Inserted.Descripcion,
Inserted.FechaModif, Deleted.FechaModif;

En este caso, tenemos acceso a una columna especial llamada $action, que nos permite saber qué operación se ha realizado (insert, update o delete). En la tabla inserted accedermos a los registros añadidos o actualizados (valor nuevo) a la tabla destino, mientras que en la tabla Deleted accederemos a los registros eliminados o actualizados (valor antiguo).
 
En el próximo post presentaré otras novedades que creo que os pueden ser de mucha utilidad.
 
¡Enjoy T-SQL!

Tags: , , , , ,

Moving to Microsoft Visual Studio 2010

Como algunos de vosotros ya sabéis, el lanzamiento de Visual Studio 2010 está al caer. Según Microsoft, esta nueva versión del producto incorpora cierta novedades que permitirán desarrollar más y mejor, aumentando significativamente la productividad.

En cualquier caso, siempre es recomendable tener algun libro de referencia que nos oriente sobre las novedades del producto. Por eso no quiero dejar pasar la oportunidad de presentaros el libro “Moving to Microsoft Visual Studio 2010, de Microsoft Press, que está disponible en una primera versión borrador, y lo más interesante, ¡es gratis!. Podéis descargaros los primeros capítulos del libro en formato XPS aquí.

El libro está enfocado a ayudar a migrar a la nueva tecnología a aquellos programadores que utilizan las versiones anteriores de Visual Studio (2003, 2005 y 2008). Para cada una de las versiones, se va a explicar las diferencias que existen en la parte de negocio y datos, diseño y presentación, y optimización y depuración de las aplicaciones.

Así que ahora ya no habrá excusas para utilizar la nueva versión de Visual Studio. Podéis encontrar la lista de novedades completa en este vínculo y en este otro.

¡Enjoy Visual Studio!

Tags: , ,

Sharepoint 2010 (III)

Para finalizar esta serie de artículos sobre Sharepoint os voy a presentar algunas novedades en cuánto a las herramientas que existen a su alrededor, su administración así com el desarrollo de soluciones mediante programación:

  • Herramientas:
    • Sharepoint Designer 2010: Es una nueva versión de la misma herramienta que ya existe para la versión 2007, en la que se ha mejorado la edición de código HTML, así como la creación de flujos de trabajo con acceso a orígenes de datos externos (mediante el protocolo REST).

    • InfoPath Forms Services: Básicamente se presenta la misma funcionalidad que en la versión 2007, aunque se ha facilitad la creación de formularios así como la creación de reglas de negocio sin apenas escribir código.
    • Access Services: Mediante esta herramienta es posible publicar aplicaciones basadas en Microsoft Access en un sitio de Sharepoint.
    • Business Connectivity Services: Se trata de la evolución de los Business Data Catalogs o Catálogos de Datos Profesionales en la versión 2007. Ahora es posible leer, actualizar, crear y eliminar datos, ya que se crea una lista externa mapeada con un tipo de contenido externo. Además se pueden editar los datos off-line, para que se sincronizen posteriormente.
  • Administración:
    • Escalabilidad: Se ha flexibilizado la gestión de los servicios compartidos, con lo que ahora es más sencillo añadir servicios. Además se han facilitado las configuración y las operaciones de copia de seguriadad y restauración de SQL Server.
    • Monitorización: Se han añadido nuevas herramientas y servicios que permiten mantener la instalación de Sharepoint en un estado óptimo. Se han aumentado las capacidades de registro de actividad de Sharepoint, y la posterior explotación de estos datos mediante informes, con lo que es más fácil encontrar posibles causas de errores o bajadas de rendimiento del servidor.

Espero que esta série de artículos os sirva para haceros una idea de lo que incorpora el nuevo Sharepoint 2010, aunque como siempre, lo mejor es probarlo.

¡Enjoy MOSS!

Tags: , , ,