Mostrando entradas con la etiqueta SQL Denali. Mostrar todas las entradas
Mostrando entradas con la etiqueta SQL Denali. Mostrar todas las entradas

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

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

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.

miércoles, 16 de marzo de 2011

Contained Databases SQL Denali (2011)

Una de las novedades, a mi ver, mas interesantes del SQL 2011 son las bases autocontenidas (Contained Databases). En las versiones actuales hay cierta información que se guarda en la base, y otra que se guarda en la instancia, como por ejemplos los logins o los jobs. Todo esto seguirá existiendo pero ahora habrá una nueva forma de guardar las cosas y es dentro de la misma base. Esto va a significar una mejora muy importante cuando uno realiza un movimiento de una base de un servidor a otro, permitiendo que toda la lógica de negocio correspondiente a la base viaje con esta, disminuyendo de esta forma las probabilidades de olvidar algo.

Logins
Una de las caractecterísticas mas importantes son los logins. Las bases contenidas permiten que el login lo maneje la misma base y no la instancia. Comunmente cuando uno realiza una migración los usuarios de la base se encuentran asociados a un login de la instancia (tanto sea uno de seguridad integrada o un login sql), y al momento de migrar esta relación se pierde dejando al usuario huerfano. Para solucionar esto es necesario al momento de restorear ejecutar un script que se encargue de mapear nuevamente el usuario con el login correspondiente. Esto no sería necesario en una base contenida visto que toda la información de login se encuentra almacenada en si misma. Es muy importante resaltar que para que esto funcione al momento de establecer la conexion a la base hay que indicar como base default dicha base y no la master como suele venir por defecto.
NOTA: No tuve tiempo de probar cuando es seguridad integrada como se comporta al mover de un servidor a otro y mas allá de que el usuario se mantiene relacionado con el login, ver si el login se encuentra bien definido o requiere algo extra.

Linked servers
Los linked servers también pueden ser almacenados en la base de este nuevo modelo. Es sumamente util esto cuando son utilizados por la aplicación dueña de la base. Los beneficios son notables al momento de puesta en producción inicial, movimientos de base, y para tener mas aislados los elementos de cada base/aplicación. Es una función que me parece va a volverse un MUST en el diseño de bases de datos.

Jobs
Otro elemento que puede pertenecer a la base. En este caso creo que el mayor beneficio es el aislar la lógica de la aplicación. ¿Todos mis jobs van a pertenecer a alguna base ahora? la respuesta es NO. Los jobs de mantenimiento, por ejemplo, va a seguir siendo mejor mantenerlos en la instancia. Ejemplo si tengo un job para hacer backup de mis bases todos los días a las 11PM ese job está bien que se mantenga como un job de la instancia. Lo mismo los de reindexado, actualización de indices etc. En cambio si tengo un job que hace algo propio de la aplicación (ejemplo todas las noches actualiza el estado de los usuarios) ese si debe ir en la base.

Manos a la obra!
El primer paso es habilitar nuestra instancia para utilizar Conteined Databases. El código es el siguiente:
sp_configure 'show advanced', 1;
RECONFIGURE WITH OVERRIDE;
go
sp_configure 'contained database authentication', 1;
RECONFIGURE WITH OVERRIDE;
go
Y luego crear la base, indicando que será una base contenida:
CREATE DATABASE MiBase CONTAINMENT = PARTIAL;
go
Con esto ya tenemos nuestra base lista!!!
Saludos y espero que les sirva. Para mas información: http://msdn.microsoft.com/en-us/library/ff929071%28v=SQL.110%29.aspx .
Andrés

jueves, 6 de enero de 2011

SQL Server Denali - Sequence

Estuve probando el SQL 11 y una de las cosas que mas contento me puso es la incorporación de las SECUENCIAS tal como lo maneja Oracle. ¿Qué es esto? básicamente es un objeto en el motor que podemos crear y nos arroja números de forma ordenada según el criterio que hayamos definido. El principal objetivo es no depender de claves de tipo IDENTITY.
En este artículo hablaré de 3 cosas:
1) Como se utilizan las secuencias (sequence)
2) Comparar con Oracle el manejo de secuencias
3) Stress test comparando secuencias con campos identity

Un ejemplo de como utilizar este nuevo objeto sería el siguiente:

USE SQLDENA
GO

-- La forma tradicional de hacer un "autonumerico" es
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[PruebaSecuencia]') AND type in (N'U'))
DROP TABLE [dbo].PruebaSecuencia
GO

CREATE TABLE PruebaSecuencia(
id INT PRIMARY KEY IDENTITY,
dato1 VARCHAR(100)
)
GO

-- Pero ahora con las secuencias lo podemos hacer de la siguiente forma
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[PruebaSecuencia]') AND type in (N'U'))
DROP TABLE [dbo].PruebaSecuencia
GO

-- Creamos la tabla que SIN campo identity
CREATE TABLE PruebaSecuencia(
id INT PRIMARY KEY,
dato1 VARCHAR(100)
)
GO

-- Creo la secuencia, se puede definir como se incrementa, etc
-- Como se puede ver son muy flexibles permitiendo configurar como se van a comportar
CREATE SEQUENCE dbo.NuevaSec
AS INT
START WITH 1
INCREMENT BY 1
MINVALUE 1
MAXVALUE 999999
CACHE 20
;
GO

-- Obtengo unos valores de ejemplo
SELECT NEXT VALUE FOR dbo.NuevaSec
GO 4

-- Reinicio la secuencia para el ejemplo
ALTER SEQUENCE dbo.NuevaSec RESTART
GO

-- Ejemplo de como la usaria
INSERT INTO PruebaSecuencia
select next value for dbo.NuevaSec as nro, 'Valor1' as dato1

-- O tambien...
INSERT INTO PruebaSecuencia (id, dato1)
VALUES (next value for dbo.NuevaSec, 'Valor1')

-- Que valor ingrese ultimo?
select current_value from sys.sequences where name = 'NuevaSec'

-- Esto me permite pasarle el valor a otra rutina que tenga que insertar en base a lo ingresado


-- La forma de realizar esto mismo en ORACLE hubiera sido:
/* PRUEBA ORACLE */
CREATE TABLE PruebaSecuencia(
id INT PRIMARY KEY,
dato1 VARCHAR2(100)
)

CREATE SEQUENCE NuevaSec
START WITH 1
INCREMENT BY 1
MINVALUE 1
MAXVALUE 999999
CACHE 20;

-- Obtengo unos valores de ejemplo
SELECT NuevaSec.NEXTVAL FROM DUAL

-- Ejemplo de como la usaria
INSERT INTO PruebaSecuencia
select NuevaSec.NEXTVAL as nro, 'Valor1' as dato1 FROM DUAL

-- O tambien...
INSERT INTO PruebaSecuencia (id, dato1)
VALUES (NuevaSec.NEXTVAL, 'Valor1')

-- Que valor ingrese ultimo?
select NuevaSec.CURRVAL FROM DUAL

/* FIN PRUEBA ORACLE */

Comparando ambas formas creo que por suerte mantuvieron una sintaxis muy similar en lo que corresponde a la creación del objeto, y como oracle no tiene su hermoso CREATE OR REPLACE para las secuencias entonces es totalmente consistente. En lo que respecta a su uso diario, me parece mucho mas comoda y agradable la sintaxis de Oracle, principalmente la forma de ver el valor actual que es algo que no entiendo como no incorporaron. Por otra parte en Oracle es muy incomodo reiniciar una secuencia (es necesario restarle el valor actual) , mientras que SQL ha simplificado esta tarea para evitarnos dolores de cabeza.

Volviendo al SQL Server, realice un stress test con ambas tablas, una con identity y otra sin, utilizando las siguientes instrucciones
-- test1
INSERT INTO PruebaSecuencia (dato1)
VALUES ('Valor1')
GO 50000

-- test2
INSERT INTO PruebaSecuencia (id, dato1)
VALUES (next value for dbo.NuevaSec, 'Valor1')
GO 50000

Los resultados fueron parejos tardando un poco menos el ejemplo con la secuencia pero no de forma significativa. Es importante notar que la prueba no la realicé en un gran servidor sino en una pc virtual, y según varios test esta diferencia suele ser mayor.