miércoles, 8 de febrero de 2012

Nueva funcion CHOOSE en SQL 2012

En esta oportunidad veremos la función CHOOSE es una de las novedades de SQL 2012. Nos permite pasar un número (primer parámetro) y seleccionar un valor en base a éste.
Este mismo funcionamiento lo podíamos obtener con una tabla de códigos con la cual hacer el INNER JOIN, pero de esta forma podemos hacerlo mas simple. Veamos un ejemplo.

-- =============================================
-- Create:        Andrés Aiello
-- Create date: 18/01/12
-- Description: Nueva funcion CHOOSE en SQL 2012
-- =============================================

-- Primero creamos una tabla con valores de ejemplo para poder probar la funcion
CREATE TABLE #PruebaCHOOSE(
id INT PRIMARY KEY, codigo INT
)

INSERT INTO #PruebaCHOOSE VALUES
(1, -2),
(2, 3),
(3, 2),
(4, 1),
(5, 5),
(6, 2),
(7, 1)

-- Utilizamos el campo codigo para seleccionar una de las tres posibles descripciones
SELECT id, CHOOSE(codigo, 'CODIGO1', 'CODIGO2', 'CODIGO3')
FROM #PruebaCHOOSE

DROP TABLE #PruebaCHOOSE

Como podemos ver en el ejemplo el uso es muy simple, y en caso de que el índice no se encuentre entre 1 y la cantidad de opciones retorna null.
Esta función requiere siempre que como mínimo se le pasen dos parámetros, el primero el del índice y el segundo un código (al menos uno).

Pro
Muy simple de utilizar y muy clara
Contra
Va contra la normalización
No genera error de fuera de índice. Esto es muy personal pero me parece que es para problemas que no genere una excepción cuando el valor ingresado no corresponde a una de las opciones


viernes, 3 de febrero de 2012

Lanzamiendo online de SQL2012

El 7 de marzo es el lanzamiento online del SQL2012! les dejo un link con la agenda para que puedan ir palpitandolo:

http://www.sqlserverlaunch.com/LATAM/agenda

LAG y LEAD, ver el registro anterior o siguiente en SQL 2012

El SQL 2012 sigue teniendo novedades interesantes para mostrar, así que continuaré con la introducción a las nuevas funciones TSQL. Hoy es el turno de LAG y LEAD.
Una cosa que siempre me molestó es cuando un cliente decía "pero esto es fácil, en el excel lo hago" y quizá en SQL no era tan simple de expresar. Un caso común de esto es cuando una "celda" se calculaba utilizando valores de su registro pero también del anterior.
La forma habitual de hacer esto en sql es con un self join, y ahí empezar a remarla. En la versión 2012 contamos con una forma mucho mas cómoda y es con las funciones LAG y LEAD.
La función LAG nos permite consultar el registro inmediatamente anterior al que estamos procesando, y si lo deseamos tambien dos anteriores o el número deseado. Veamos con un ejemplo que es mas claro.

-- =============================================
-- Create:        Andrés Aiello
-- Create date: 18/01/12
-- Description: Hacer un cálculo en base al registro anterior en SQL 2012: LAG y LEAD
-- =============================================

-- Primero creamos una tabla con valores de ejemplo para poder probar la funcion
CREATE TABLE #PruebaLAG_LEAD(
id INT PRIMARY KEY, dia DATE, ventas INT
)

INSERT INTO #PruebaLAG_LEAD VALUES
(1, '20120101', 10),
(2, '20120102', 11),
(3, '20120103', 13),
(4, '20120104', 8),
(5, '20120105', 10),
(6, '20120106', 15),
(7, '20120107', 10)

/*
Dada la siguiente tabla deseamos saber la diferencia de ventas de un día con el anterior, así que con los valores de ejemplo obtendriamos:
(1, '20120101', NULL),
(2, '20120102', 1),
(3, '20120103', 2),
(4, '20120104', -5),
(5, '20120105', 2),
(6, '20120106', 5),
(7, '20120107', -5)
*/
-- En la forma tradicional esto lo podríamos hacer así:
SELECT t1.id, t1.dia, t1.ventas, t2.ventas ventaAnterior, t1.ventas - t2.ventas ventaDiferencia
  FROM #PruebaLAG_LEAD t1
 LEFT JOIN #PruebaLAG_LEAD t2 ON t1.id -1 = t2.id

-- Es importante resaltar el null del primer registro, causado por el LEFT JOIN. Esto se debe a que en mi primer registro no tiene sentido comparar con el valor anterior, se va fuera de la lógica de negocio.

-- Ahora veamos como podríamos escribir esto mismo en SQL 2012

SELECT *, LAG(ventas, 1, null)OVER(ORDER BY dia) as ventaAnterior, 
    ventas - LAG(ventas, 1)OVER(ORDER BY dia) as ventaDiferencia
  FROM #PruebaLAG_LEAD

DROP TABLE #PruebaLAG_LEAD


Como ven es muy comodo de utilizar y viene en diferentes sabores. Los tres parámetros que toma (los últimos dos opcionales) son:
LAG(campo, cantidad de registros anteriores a buscar, valor default si es nulo)

En caso de que no se indique cuantos registros atras mirar se asume uno. Y en caso de no indicar un valor default es null (tal como en el ejemplo que utilice una vez null de forma explícita y otra implícita).

Lo único que no vimos es que pasaría si quisiera ignorar el registro inicial, para no tener ese valor nulo. En las versiones anteriores me bastaría con cambiar el LEFT JOIN por un INNER JOIN, pero ahora debo poner un poco mas de mano y utilizar una CTE.

-- =============================================
-- Create:        Andrés Aiello
-- Create date: 18/01/12
-- Description: Hacer un cálculo en base al registro anterior en SQL 2012: LAG y LEAD
-- =============================================

-- Primero creamos una tabla con valores de ejemplo para poder probar la funcion
CREATE TABLE #PruebaLAG_LEAD(
id INT PRIMARY KEY, dia DATE, ventas INT
)

INSERT INTO #PruebaLAG_LEAD VALUES
(1, '20120101', 10),
(2, '20120102', 11),
(3, '20120103', 13),
(4, '20120104', 8),
(5, '20120105', 10),
(6, '20120106', 15),
(7, '20120107', 10)

-- Como ignorar el registro inicial para que no quede el NULL

;WITH cte AS(
SELECT ROW_NUMBER()OVER(ORDER BY dia) rn, *, LAG(ventas, 1, null)OVER(ORDER BY dia) as ventaAnterior, 
    ventas - LAG(ventas, 1)OVER(ORDER BY dia) as ventaDiferencia
  FROM #PruebaLAG_LEAD
)
SELECT * 
  FROM cte
WHERE rn>1

DROP TABLE #PruebaLAG_LEAD


La función LEAD es equivalente pero para el siguiente registro en lugar del anterior así que no entraremos en mas detalle.

Pro
Hace que sea mucho mas simple el tipo de cálculos de valores precedentes
Es mas declarativo y eficiente que la vieja forma
Es muy flexible, puede ser variable el segundo campo, pudiendo utilizar algo rebuscado como comparar todo contra el principio de mes
Contra
Es incomodo si no se quiere incluir el registro inicial

Mas info de LAG: http://msdn.microsoft.com/es-us/library/hh231256(v=sql.110).aspx
Mas info de LEAD: http://msdn.microsoft.com/es-us/library/hh213125(v=sql.110).aspx

lunes, 30 de enero de 2012

Saber el fin de mes en SQL 2012: EOMONTH

Buenas a todos!!! En este articulo continuaremos contando las novedades del TSQL de SQL 2012.
En esta oportunidad quiero presentarles la función EOMONTH. Esta función se puede utilizar de dos maneras, con un solo parámetro o con dos.
Cuando la utilizamos con un solo parámetro, éste debe ser de tipo fecha y la función lo que retorna es el último día del mes correspondiente al parámetro. Esto en versiones anteriores de SQL se podía hacer de varias formas, pero de todas era bastante engorroso.
En el ejemplo veremos alguna de esas alternativas.
La segunda opción, muy práctica por cierto, es pasandole la fecha y otro parámetro que indica cuantos meses sumar o restar. Esto lo que hará es retornar el fin de mes siguiente. Veamos como funcionan.

-- =============================================
-- Create:        Andrés Aiello
-- Create date: 18/01/12
-- Description: Saber el fin de mes en SQL 2012
-- =============================================

-- Primero declaramos una variable de tipo fecha para la prueba. Puede ser datetime sin inconveniente.
DECLARE @date DATE;
SET @date = GETDATE();

-- Veamos una de las opciones de como calcular el principio de mes y fin de mes con la sintaxis permitida en versiones anteriores de SQL.
-- Repito, esta es una forma de hacerlo pero hay varias
SELECT @date AS FechaOriginal,
    DATEADD(DAY,-1 * (DATEPART(DAY, @date))+1, @date) AS PrincipioDeMes,
    DATEADD(DAY, -1, DATEADD(MONTH, 1, DATEADD(DAY,-1 * (DATEPART(DAY, @date))+1, @date))) AS FinDeMes
        

-- Ahora con la función EOMONTH podemos expresarlo de forma directa
SELECT EOMONTH ( @date ) as FinDeMes,
       EOMONTH ( @date, 1 ) as FinSiguienteMes,
       EOMONTH ( @date, -1 ) as FinMesAnterior;

-- Y si en cambio no queremos el fin de mes sino el comienzo, podemos hacer lo siguiente
-- -> Calcular el fin del mes anterior y sumarle un dia
SELECT DATEADD(DAY, 1, EOMONTH ( @date, -1 )) as PrincipioDeMes;


Pro

  • Permite escribir de forma muy declarativa y simple el fin de mes, algo que es muy utilizado
  • La sintaxis es muy intuitiva

Contra
  • No entiendo porque no se aplica lo mismo para principio de mes, esto hacer perder ortogonalidad. Podria ser otra o un parámetro que indique si se desea el fin o el comienzo de mes

Para mas información: http://msdn.microsoft.com/en-us/library/hh213020%28v=sql.110%29.aspx

martes, 24 de enero de 2012

Funciones de fechas en SQL 2012

En éste articulo hablaremos de las nuevas funciones para manejo de fechas que se incoporan en SQL 2012. Son un conjunto de funciones que se llaman xxxFROMPARTS y lo que permiten es ingresar los valores numéricos que forman cada parte de una fecha y retornar en el tipo fecha que corresponda.
Estas funciones son 6, una para que tipo de fecha que maneja el SQL Server. Tomemos como ejemplo la primera para poder fijar la idea.
Si yo tengo tres campos numericos, en uno guardo el dia, en otro el mes y en otro el año, antes debíamos hacer varias operaciones para poder pasar eso a una fecha (string mediante), lo cual siempre me pareció extraño, porque el camino inverso (dada una fecha obtener el número del mes) era posible facilmente con la función DATEPART.
Ahora utilizando la función DATEFROMPARTS le pasamos esos 3 argumentos y nos retorna algo de tipo fecha.
Si en lugar de hacerlo para una fecha lo queremos hacer para una hora, tenemos TIMEFROMPARTS, y así para que tipo de datos "tiempo" que tiene el SQL Server.
Veamos el ejemplo:

-- =============================================
-- Create:        Andrés Aiello
-- Create date: 13/01/12
-- Description: Manejo de fechas en SQL 2012
-- =============================================

SELECT DATEFROMPARTS             ( 2010, 12, 31 ) AS Salida;
SELECT TIMEFROMPARTS             ( 23, 59, 59, 0, 0 ) AS Salida;

SELECT SMALLDATETIMEFROMPARTS    ( 2010, 12, 31, 23, 59 ) AS Salida
SELECT DATETIMEFROMPARTS         ( 2010, 12, 31, 23, 59, 59, 0 ) AS Salida;
SELECT DATETIME2FROMPARTS        ( 2010, 12, 31, 23, 59, 59, 0, 0 ) AS Salida;
SELECT DATETIMEOFFSETFROMPARTS   ( 2010, 12, 31, 14, 23, 23, 0, 12, 0, 7 ) AS Salida;



Pros
  • Es muy cómodo de utilizar
  • Era algo deseable visto que el camino inverso (fecha->número) ya era posible con DATEPART
  • Deja de ser necesario y trabajoso pasar por un VARCHAR para poder transformar una fecha desde sus partes numéricas

Contras
  • Para una transformación es DATEPART pero la vuelta es con otra, no junta los conceptos
  • Muchas funciones para lo mismo en lugar de hacer una sobrecarga y que lo defina el motor
  • Funciones muy "sensibles". Siempre esperan todos los parámetros. Calculo que ésto es consecuencia del punto anterior

Espero sus opiniones.
Saludos!

martes, 17 de enero de 2012

OFFSET y FETCH, paginando sin el TOP en SQL 2012

Siguiendo con los artículos sobre nuevas funcionalidades para desarrolladores en SQL 2012 ahora les presento el concepto de paginación.
Hay otras bases del mercado que ya hace bastante tiempo tienen este concepto, pero en SQL Server si queriamos no traer toda la consulta sino solamente una parte la única herramienta con la que contabamos era la sentencia TOP (o algunas trampas para poder simular la paginacion). La sentencia TOP nos permitía traer solamente los N primeros valores de una consulta. Pero es muy habitual tener una interfaz de cara al cliente que le muestra un gran resultado por páginas, entonces desearía poder traer solamente lo que voy a mostrar, y para la primer página me sirve el TOP pero para el resto no.
Con el objetivo de apalear este problema es que se incorporó OFFSET y FETCH. Al utilizar estas sentencias en lugar de devolver todos los valores, devuelve a partir del valor indicado en OFFSET. ¿Cuantos valores retirna? la cantidad que se indique en FETCH.
Para que esto funcione es necesario que la consulta tenga un ORDER BY. Veamos un ejemplo.

-- =============================================
-- Create:        Andrés Aiello
-- Create date: 13/01/12
-- Description: Paginacion en SQL 2012
-- =============================================

-- Llenemos una tabla con valores para poder probar
CREATE TABLE #Temp(id INT PRIMARY KEY, campo2 INT, campo3 VARCHAR(100))
DECLARE @i INT
SET @i = 0
WHILE @i<100
BEGIN
    INSERT INTO #Temp
    SELECT @i, @i*7, 'Fila ' + CAST(@i AS VARCHAR)
    SET @i = @i + 1
END
GO

-- Primera prueba: ver el contenido
SELECT * FROM #Temp
GO

-- Forma vieja de paginar
SELECT TOP 10 * 
  FROM #Temp
ORDER BY campo2

SELECT TOP 10 * 
  FROM #Temp
 WHERE campo2>=70
ORDER BY campo2

-- Forma nueva de paginar
-- Obtengo los primeros 10 resultados
SELECT * 
  FROM #Temp
ORDER BY campo2
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY;

-- Obtengo los siguientes 10!!!
SELECT * 
  FROM #Temp
ORDER BY campo2
OFFSET 10 ROWS
FETCH NEXT 10 ROWS ONLY;
GO

DROP TABLE #Temp
GO



Según la documentación oficial se recomienda utilizar FETCH en lugar del viejo TOP. El motivo es que con el TOP es necesario ejecutar tantas consultas como páginas si deseen, y siempre es necesario hacer nuevamente una búsqueda. Con el FETCH el motor se encarga de que cuando volvamos a pedir algo no requiera reprocesarlo sino utilizar lo que ya se calculó.
Para que esto funcione correctamente se deben cumplir algunas simples condiciones:

  1. La informacion a consultar no debe cambiar entre consulta y consulta
  2. El ORDER BY debería ser sobre una columna (o varias) que sea única

Si todo esto se cumple (lo cual no es complicado) podremos comenzar a disfrutar de los beneficios del FETCH.

viernes, 13 de enero de 2012

Manejo de excepciones en SQL 2012

Antes que nada quiero saludar a todo el mundo luego de un mes de descanzo totalmente alejado de la pc! Ahora con todas las ganas de comenzar el 2012! Ahora a comenzar con el artículo.
En SQL 2008R2 o inferior la forma que teniamos de hacer el manejo de errores era utilizando el TRY y CATCH y luego en caso de ser necesario generar un error con RAISE ERROR. Cualquiera que esté familiarizado con el paradigma de objetos también lo está con el concepto de "lanzar excepciones". Este paso es lo que se ha implementado en esta nueva versión de SQL con la instrucción THROW.
Veamos un ejemplo de como manejabamos las excepciones antes:
-- =============================================
-- Create:        Andrés Aiello
-- Create date: 13/01/12
-- Description: Ejemplo de manejo de errores en SQL2012
-- =============================================

BEGIN TRY
 SELECT 1/0
END TRY
BEGIN CATCH
    DECLARE @ErrMsg nvarchar(4000), @ErrSeverity int
    SELECT @ErrMsg = ERROR_MESSAGE(),
        @ErrSeverity = ERROR_SEVERITY()

    RAISERROR(@ErrMsg, @ErrSeverity, 1)
END CATCH
GO


En cambio ahora con esta nueva instrucción sería:
-- =============================================
-- Create:        Andrés Aiello
-- Create date: 13/01/12
-- Description: Ejemplo de manejo de errores en SQL2012
-- =============================================

BEGIN TRY
 SELECT 1/0
END TRY
BEGIN CATCH
     THROW 51000, 'Mensaje de error a elección!', 1;
END CATCH
GO


Nuestra lógica no sufrió grandes cambios pero esto permite empezar a pensar en un paradigma de excepciones nuestro código.
Algunas diferencias entre el comportamiento de una y otra son:
RAISE ERROR
  • El error debe estar definido en la sys.messages
  • Los mensajes pueden utilizar el caracter % para ser parametrizados
  • Se puede definir la severidad del error, siendo esta cualquier número, incluyendo las mas graves
  • No se puede arrojar en cadena (hacia los llamadores) la excepción. Cuando se genera una nueva "pisa" la anterior
En cambio con la nueva instrucción THROW
  • El mensaje de error se define al momento de lanzar la excepción
  • Los mensajes NO pueden ser parametrizados. Tener en cuenta que al generarse en el momento los mensajes, esto no debería ser un gran problema
  • La severidad del error es siempre la misma, severidad 16
  • Un sp que captura una excepción puede relanzarla tal como en cualquier entorno de objetos
A mi ver es algo muy cómodo. Lo bueno es que no deberán salir todos corriendo a cambiar su código porque aún se mantendrá compatibilidad con ambas.
Espero que les haya servido.

Para mas info:
http://msdn.microsoft.com/en-us/library/ee677615%28v=sql.110%29.aspx

martes, 29 de noviembre de 2011

Nuevas funciones TRY_PARSE y TRY_CONVERT en SQL 2012

Hay muchas funciones nuevas en SQL 2012 apuntadas al desarrollo, y me gustaría ir dedicandole un lugar. La primera que veremos es TRY_PARSE y TRY_CONVERT que permiten ejecutar lo mismo que sus predecesoras pero sin dar error cuando la conversión no se puede realizar. Veremos algunos ejemplos de su aplicación:

-- =============================================
-- Create:        Andrés Aiello
-- Create date: 17/11/11
-- Description: Ejemplo de manejo de errores en SQL2012 aca Denali
-- =============================================

-- Llenemos una tabla con valores para poder probar
CREATE TABLE #Temp(id INT PRIMARY KEY, campo2 INT, campo3 VARCHAR(100))
INSERT INTO #Temp SELECT 1, 1, '1'
INSERT INTO #Temp SELECT 2, 1, '001'
INSERT INTO #Temp SELECT 3, 1, 'fake1'
INSERT INTO #Temp SELECT 4, 1, 'fullfake'
INSERT INTO #Temp SELECT 5, 1, '01/31/2012'
INSERT INTO #Temp SELECT 6, 1, '31/01/2012'

-- Primera prueba: ver el contenido
SELECT * FROM #Temp

-- Segunda prueba: Castear la columna 3 a entero usando la vieja forma
SELECT CONVERT(INT, campo3) FROM #Temp
/* Resultado:
Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the varchar value 'fake1' to data type int.
*/

-- Tercera prueba: Hacer lo mismo pero con Try_Convert
SELECT TRY_CONVERT(INT, campo3) FROM #Temp
-- No hay error!!! Los dos primeros registros se convirtieron y los siguientes 3 son nulos

-- Cuarta prueba: Hacer lo mismo pero con fechas
SELECT CONVERT(DATE, campo3) FROM #Temp
/* Resultado:
Msg 241, Level 16, State 1, Line 1
Conversion failed when converting date and/or time from character string.
*/

-- Cuarta prueba: Hacer lo mismo pero con Parse
SELECT PARSE(campo3 AS DATETIME) FROM #Temp
-- Error!!! pero si usamos TRY_PARSE...
SELECT id, TRY_PARSE(campo3 AS DATETIME USING 'en-US') FROM #Temp
-- La salida parsea los valores que pudo y deja en nulo el resto!

-- Y si cambiamos la referencia cultural...
SELECT id, TRY_PARSE(campo3 AS DATETIME USING 'es-ES') FROM #Temp
-- Obtenemos otro de los registros!!! sin tener que recordar los odiosos codigos 101 etc

DROP TABLE #Temp


Mas info: http://msdn.microsoft.com/en-us/library/hh213126%28v=sql.110%29.aspx

miércoles, 23 de noviembre de 2011

Listar todas las fechas de un intervalo en TSQL

Hace poco en un foro alguien preguntó como hacer una consulta SQL para obtener todas las fechas en un intervalo dado, y como me gustó la solución la reescribi de forma mas generica y me gustaría compartirla con todos


-- Declaro las variables
DECLARE @desde DATETIME
DECLARE @hasta DATETIME
-- Seteo un valor inicial
SELECT @desde='20110101',@hasta='20111101';
-- Primero calculo la cantidad de dias y luego listo tantos numeros como diferencia de dias
WITH 
    CantDias AS (SELECT DATEDIFF(DAY, @desde, @hasta) Cantidad),
    Numeros AS(
        SELECT 1 numero
        UNION ALL
        SELECT n.numero + 1 numero
          FROM Numeros n, CantDias c 
        WHERE n.numero<=c.Cantidad
)
SELECT DATEADD(DAY, Numeros.numero-1, @desde) FROM Numeros OPTION (MAXRECURSION 0);




jueves, 17 de noviembre de 2011

Microsoft SQL Server 2012 Release Candidate 0 (RC0)

Ya se encuentra disponible el RC0 del SQL2012!!! para el que lo quiera bajar dejo el link:
http://www.microsoft.com/download/en/details.aspx?id=28145

Entre esta semana y la que viene estaré subiendo algunos pequeños artículos apuntando a las novedades, pero para variar voy a enfocar en los cambios que afectan a los desarrolladores.