miércoles, 24 de agosto de 2011

Restore de CDC en SQL 2008

El cdc es una feature muy buena del SQL2008 visto que de forma sencilla nos permite llevar un registro de los cambios en una tabla, tanto sea para cuestiones de auditoría, procesamientos parciales de información, etc.
Así mismo, cuando se trabaja con cdc es normal que se deseen probar, testear, desarrollar procedimientos que utilicen esta información y naturalmente no queremos que accedan al ambiente productivo hasta estar testeadas. Aquí es cuando nos topamos que si hacemos un restore de una base con cdc en otra instancia no aparecen las tablas correspondientes. Para evitar esto debemos incluir la opción WITH KEEP_CDC al momento de hacer restore. No se si será poco usada, pero no se asusten si no es coloreada en el Managment Studio, funciona a la perfección.
El código debería quedar algo así:

RESTORE DATABASE [AIELLO_DB]
FROM DISK = N'D:\Backup\[AIELLO_DB][SQL2008].bak'
WITH
KEEP_CDC,
FILE = 1,
MOVE N'AIELLO_DB'
TO N'D:\MSSQL10.MSSQLSERVER\MSSQL\DATA\AIELLO_DB.mdf',
MOVE N'AIELLO_DB_log'
TO N'D:\MSSQL10.MSSQLSERVER\MSSQL\DATA\AIELLO_DB_log.ldf',
MOVE N'AIELLO_DB_cdc'
TO N'D:\MSSQL10.MSSQLSERVER\MSSQL\DATA\AIELLO_DB_cdc.ndf',
NOUNLOAD,
REPLACE,
STATS = 10

Con esto tendrán la información para poder explotarla, pero no se activará la captura de datos. Si desean que esto ocurra hay que incluir los jobs correspondientes con sys.sp_cdc_add_job aplicando tanto 'capture' como 'cleanup'.
Saludos,
Andrés
Para mas información:

jueves, 11 de agosto de 2011

SQL2008: Entendiendo el Waitresource

Introduccion
Al ver los xml de bloqueos o deadlocks muchas veces aparecen números casi inentendibles, y uno de los importantes es el WAITRESOURCE. Este número codifica que objeto de la base de datos está participando en la pelea por el bloqueo. El recurso puede principalmente ser:
  • Una tabla
  • Una página
  • Una clave
Puede ser algunas cosas mas pero para simplificar el análisis nos enfocaremos en estos.
Manos a la obra
Lo primero que debemos hacer es identificar cual de las tres cosas es, lo cual es fácil leyendo el texto de wait resourse que puede ser:
“OBJECT: 19:1275867612:10”
“PAGE: 12:1:79868773”
“KEY: 12:397371816017920 (760119e06bff)”
En el primer caso es cuando se trata de una tabla. Para este caso (y para los siguientes) lo primero que debemos hacer es analizar el primer número antes del separador (:) . Este número en todos los casos indica el id de la base de datos, por lo tanto debo hacer la siguiente consulta para obtenerla:
SELECT * FROM sys.sysdatabases WHERE Dbid=19
Lo cual me da por resultado Mibase. Recordemos esto porque es común a los tres casos.
Ahora siguiendo con el análisis del primer caso la información brindada se debe analizar de la siguiente manera:

OBJECT: dbId:ObjectId:IndexId
Con la salvedad de que indexId vale 0 cuando se trata del heap y 1 cuando se trata de un índice cluster. En otro caso figurará el número del índice.
Para obtener esta información en un formato útil debería hacer las siguientes consultas:
-- Nota: debo estar situado en MiBase
SELECT OBJECT_NAME(1275867612) 
-- O bien
SELECT * FROM MiBase.sys.all_objects WHERE object_id = 1275867612
-- Y para buscar el índice en caso de ser mayor que 1:
SELECT * FROM MiBase.sys.indexes WHERE object_id=1275867612
De esta forma ya sabemos que table y que índice participaba en el bloqueo.
Para el caso 2 (página) comenzaremos averiguando la base de la misma forma y la siguiente información nos viene como:
PAGE: dbId:FileId:PageId
Si no tenemos nuestra base con varios archivos sino todo en uno del primary el segundo campo siempre será 1.
Para analizar el último debemos obtener la información de la página (79868773 en el ejemplo). Para esto tenemos dos formas:

DBCC TRACEON ( 3604 )
DBCC PAGE (12,1,79868773)
o
DBCC PAGE (12 , 1, 79868773) WITH TABLERESULTS,NO_INFOMSGS
En el primer caso es necesario el traceon porque sino no podemos ver la salida, la cual saldrá en modo texto. En el segundo no es necesario visto que la salida la veremos en modo tabla.
Sea cual fuera el caso que elegimos debemos buscar el renglon (o registro) que tenga la siguiente información:

m_objId (AllocUnitId.idObj) = 1051306955 m_indexId (AllocUnitId.idInd) = 7
y con esto obtengo el ObjectId para poder continuar mi análisis como en el caso anterior.
Por último nos queda el caso del índice (KEY: 12:397371816017920 (760119e06bff)). En este caso debemos leerlo como:

KEY: dbId:hObjecto (hash)
El hash puede ser ignorado, visto que no tenemos función para transformarlo (es la gracia de los hash!), así que nos quedaremos con el hObj. Este número se encuentra almacenado en la msdb así que lo podemos utilizar para calcular el objetcId:

SELECT * FROM MiBase.sys.partitions
WHERE hobt_id = 397371816017920
De esta consulta obtenemos el object_id y podemos hacer nuestro análisis de siempre.

martes, 26 de julio de 2011

Traduciendo jobs names de SQL

Muchas veces estamos monitoreando en tiempo real nuestro SQL y hay cosas interesantes para observar. Este pequeño articulo es la puerta de entrada al de la semana que viene donde profundizaremos este tema.
Por ahora lo que quiero mencionar es simplemente un pequeño y usual problema . La forma mas simple es mediante el sp_who2, el cual en la columna BlkBy nos indicará si un proceso se encuentra proqueado y en la columna ProgramName podremos obtener que programa es la pobre víctima. Es muy importante que si este proceso fue lanzado por un job no veremos el nombre sino una secuencia extraña de números que debemos traducir, por ejemplo:
SQLAgent - TSQL JobStep (Job 0xF64F718235C7154DB6F21B5935D7218A : Step 1)

Para traducir esto debemos tomar la parte "numerica" del mensaje y copiarla en la siguiente consulta:

SELECT name
FROM msdb.dbo.sysjobs
WHERE job_id = CAST( 0xF64F718235C7154DB6F21B5935D7218A AS UNIQUEIDENTIFIER)
así obtendremos el nombre del job en cuestión.
Con esta info ahora solo nos resta cruazarla con las tablas del sistema para saber si un spid en particular está molestando o no, pero como dije eso lo veremos la semana siguiente.
Saludos,
Andrés

lunes, 18 de julio de 2011

Cluster desde powershell


Cuando se trabaja con SQL en entornos de alta disponibilidad es muy comun trabajar con windows en cluster y tener que lidiar con varias tareas que involucran tanto sea al sql como al cluster.
La forma tradicional de como hacer esto es con el comando Cluster.exe, pero para esto deberíamos habilitar el cmd shell lo cual nunca es recomendable.
Por suerte desde 2008 tenemos de forma nativa en los jobs incorporar rutinas de powershell, pero hasta ese entonces la única salida era seguir acudiendo al cluster.exe.
Esto ha cambiado con la salida de Windows Server 2008 R2 (no confundir con SQL Server 2008 R2!!!), donde se ha incorporado un modulo de cluster al powershell que hace muy comoda su adminstración.
Para utilizar este modulo primero debemos entrar a nuestra consola de powershell y ejecutar el siguiente comando:

Import-Module FailoverClusters

Con esto se incorporarán las funciones de cluster y podremos utilizarlas. En mi caso me ha servido porque en conjunto con SQL 2008 puedo llamarlas desde un job de sql configurandolo con rutina powershell y

esto es flexible y seguro.

Les adjunto un link de como pasar las tareas de Cluster.exe a powershell:

Como usarlo: http://technet.microsoft.com/en-us/library/ee619751%28WS.10%29.aspx

Mapeo: http://technet.microsoft.com/en-us/library/ee619744%28WS.10%29.aspx

====================================================================


When working with SQL in high availability is very common to work over windows cluster and dealing with various tasks involving both sql server and the windows cluster.
The traditional way of how to do this is with the command Cluster.exe, but for this should enable the cmd shell it is never recommended.
Luckily since 2008 We have powershell natively in jobs routines, but the only way out was to keep going to the cluster.exe.
This has changed with the release of Windows Server 2008 R2 (not to be confused with SQL Server 2008 R2!!!), which has a powershell module of the cluster that makes it very comfortable its management.
To use this module must first enter our powershell console and run the following command:

Import-Module FailoverClusters

This will incorporate the functions of cluster and we use them. In my case I have served it in conjunction with SQL 2008 I can call them from a sql job of setting it powershell with routine and that is

flexible and secure.

I attached a link of how to change tasks Cluster.exe to powershell:

How to use: http://technet.microsoft.com/en-us/library/ee619751%28WS.10%29.aspx
Mapping: http://technet.microsoft.com/en-us/library/ee619744%28WS.10%29.aspx

jueves, 7 de julio de 2011

Restore de FILESTREAM

El filestream es una funcionalidad nueva del SQL2008 que a veces es discutida pero a mi ver es muy buena en muchos casos. En esta oportunidad me gustaría hablar sobre un caso interesante y es como hacer para restorear una base que tiene campos filestream pero que no ha sido diseñado pensando en esto. ¿A que me refiero? Uno puede diseñar la base desde un principio alojando las tablas que contienen archivos en filegroups diferentes para asegurarse que en un restore parcial no afecten, pero no siempre las cosas nacen en este orden, muchas veces las aplicaciones vienen de hace tiempo y "llegan" al sql 2008 y en la tabla que se encuentra el binario tambien se encuentra otra información indispensable para el funcionamiento del sisetema. En estos casos no restorear el filegroup en cuestión es lo mismo que no restorear nada. Para estos casos el filestream es una buena solución visto que en cierto modo puedo hacer un filegroup "vertical".
Supongamos una tabla "cliente" que tenga los campos id, nombre, apellido, Documento, siendo el último un VARBINARY(MAX). Si esta tabla la pongo en un filegroup diferente y hago un restore del resto obtendría error al realizar la siguiente consulta:

SELECT id, nombre, apellido FROM Cliente


Restore con PARTIAL

Ahora supongamos que mi campo base tiene definido FILESTREAM y el campo Documento se encuentra albergado de esa forma. En este caso ante un problema por el cual deseo recuperar mi base podría hacer lo siguiente:

RESTORE DATABASE [AIELLO_DBA]
FROM DISK = N'D:\Prueba_fs.bak'
WITH REPLACE, PARTIAL, RECOVERY


Y ahora la consulta antes mencionada funcionaría sin problemas, solamente obtendría un error si intento acceder al campo documento, como sería con la siguiente consulta:

SELECT id, nombre, apellido, Documento FROM Cliente

Msg 670, Level 16, State 1, Line 2
Large object (LOB) data for table "dbo.Cliente" resides on an offline filegroup ("AIELLO_DBA_FS") that cannot be accessed.

Lo que logré es poner mi base en linea en un tiempo mucho menor. Pero lamentablemente para recuperar todo debo volver a hacer el restore full en otro momento.


Restore operativo por partes
No todo está perdido!! como dice el dicho, hecha la ley hecha la trampa, entonces lo que hice es lo siguiente.

En mi backup nocturno hago lo siguiente:

-- =============================================
-- Author: Andrés Aiello
-- Create date: 07/07/2011
-- Backup full poniendo readonly el filestream
-- =============================================

ALTER DATABASE [AIELLO_DBA] MODIFY FILEGROUP [AIELLO_DBA_FS] READONLY
BACKUP DATABASE [AIELLO_DBA]
TO DISK = N'D:\Prueba_fs_readonly.bak'
WITH NOFORMAT, NOINIT, NAME = N'PRUEBA FS', SKIP, NOREWIND, NOUNLOAD, STATS = 10
ALTER DATABASE [AIELLO_DBA] MODIFY FILEGROUP [AIELLO_DBA_FS] READWRITE


Por lo tanto el filegroup de los documentos es guardado como readonly. Al momento de hacer restore lo puedo hacer de dos formas:

--============== MODO 1 - Full
-- 7:38 min

RESTORE DATABASE [AIELLO_DBA]
FROM DISK = N'D:\Prueba_fs_readonly.bak'
WITH REPLACE, RECOVERY
ALTER DATABASE [AIELLO_DBA] MODIFY FILEGROUP [AIELLO_DBA_FS] READWRITE


Esta forma hace un restore completo de la forma tradicional, dejando el servicio bajo el tiempo que dure el restore (en una base donde el mayor porcentaje del espacio son archivos esto puede crecer mucho).
Y el plan b...

-- =============================================
-- Author: Andrés Aiello
-- Create date: 07/07/2011
-- Restore inteligente
-- =============================================
--============== MODO 2 - Sin los binarios
-- 0:46 
RESTORE DATABASE [AIELLO_DBA]
FROM DISK = N'D:\Prueba_fs_readonly.bak'
WITH REPLACE, PARTIAL, RECOVERY
ALTER DATABASE [AIELLO_DBA] SET RECOVERY FULL WITH NO_WAIT
-- Base online sin los binarios!!!
-- Recuperar todo...
-- 8:30
RESTORE DATABASE [AIELLO_DBA]
FILEGROUP='AIELLO_DBA_FS'
FROM DISK = N'D:\Prueba_fs_readonly.bak'
WITH REPLACE, RECOVERY
ALTER DATABASE [AIELLO_DBA] MODIFY FILEGROUP [AIELLO_DBA_FS] READWRITE
De esta forma la base en solo 45 segundos se encuentra operativa!!! y mientras se va utilizando se va recuperando en background los documentos del otro filegroup. Para evitar inconsistencias mientras se recupera esa parte el filegroup se mantiene en readonly.
Andrés

viernes, 8 de abril de 2011

CREATE USER WITHOUT LOGIN

Una instrucción no muy utilizada es la de create user without login. ¿En que consiste? tal como su nombre lo indica lo que hace es crear un usuario sin asociarlo a un login en el servidor, ahora veamos porque querríamos utilizar esto.
Antes que nada vayamos a la sintaxis

CREATE USER user_name
WITHOUT LOGIN
WITH DEFAULT_SCHEMA = [dbo]


Este usuario podrá recibir permisos, ser incluido en roles y todo lo que considere necesario, pero nadie podrá loguearse con dicho usuario. Un posible uso (aunque a mi ver no recomendado) es cuando aún no se el nombre del login que tendrá y quiero dejarlo listo para una vez definido todo modificar el login asociado y ya tengo todo andando. Esto lo podré hacer con la sentencia:

EXEC sp_change_users_login 'update_one', @username, @loginname

¿Porque digo que no recomiendo esto? porque en estos casos una mejor práctica es asignar los permisos a un role, y una vez definido y creado el usuario simlemente se lo agrega al role. Es mucho mas comoda la administración de permisos si uno utiliza roles.

EXECUTE AS

El uso mas interesante viene de la mano de la opción EXECUTE AS. Esta opción me permite hacer que un procedimiento, función o trigger se ejecute con los permisos de otro usuario que no sea necesariamente el que lo invocó. De esta forma podría por ejemplo hacer un procedimiento que muestre información que de otra forma el usuario no hubiera podido ver. Al no estar este usuario asociado a ningún login no se corre peligro de que alguien se loguee con dicho usuario y escale permisos.
La forma de hacer esto es, por ejemplo:
CREATE PROCEDURE dbo.procedimiento
WITH EXECUTE AS 'UsuarioSinLogin'
AS
TRUNCATE TABLE ...
GO
En este ejemplo un usuario con permisos de datareader y ejecución sobre el stored puede tener una forma comoda y segura de limpiar una tabla en particular.

Huerfanos

Hay un caso muy particular en el cual mucha gente se topa con la sentencia WITHOUT LOGIN sin desearlo, y a veces sin entender porque. Supongamos que tenemos el usuario1 y login1 en la base de datos1 en el servidor1. Mediante backup y restore movemos la base al servidor2. El usuario, como muchos ya saben, queda huerfano de su login, y es necesario utilizar la función nombrada en la primer parte para volver a asociarlo (esto se debe a los ids internos de los usuarios). Si deseamos hacer un script completo de la base nos vamos a encontrar que los logins van a aparecer como WITHOUT LOGIN y esto se debe a que al no coincidir el id interno el motor asume que no tiene login. Es importante considerar esto antes de ponerse a buscar como loco que procedimiento hace un EXECUTE AS o algo similar. En la página de microsoft se puede encontrar un script muy simple para corregir estos casos cuando el usuario y el login tienen el mismo nombre.


Para mas información:
http://msdn.microsoft.com/en-us/library/ms173463.aspx
http://msdn.microsoft.com/es-es/library/ms174378.aspx
http://msdn.microsoft.com/en-us/library/ms188354.aspx

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

martes, 25 de enero de 2011

Sp_who en Oracle

En el tiempo que llevo trabajando con SQL Server creo que el comando que mas veces ejecuté es el sp_who o su versión extendida sp_who2. Este comando cuando se está administrando una base es muy comodo por su sencillez y nos da un buen resumen de que está ocurriendo en el servidor. Claramente cuando hay alguna situación fuera de lo normal esto es solo la puerta de entrada para despues controlar las DMVs, los contadores del sistema operativo, etc, pero siempre es un buen comienzo. Muchas personas les pasa que acostumbradas a ambientes SQL Server se sientan frente a un sistema Oracle y buscan algo equivalente y no lo encuentran, así que veamos como sería una consulta equivalente en Oracle.
No contamos con un stored procedure prearmado que nos brinde la información, pero al igual que en SQL Server, esta información se nutre de las sesiones, así que consultaremos la V_$SESSION al igual que el sp_who original.
En este caso para que les sea mas comodo acomodé los campos para que queden igual que en la versión original.

SELECT sid, status, Username, terminal, blocking_session_status, schemaname, a.name Command_Action, logon_time, program
FROM SYS.V_$SESSION s
INNER JOIN AUDIT_ACTIONS a ON s.command=a.action;

Como podrán ver aparte de consultar la vista de sesiones hice un join con la tabla AUDIT_ACTIONS que es la que contiene las descripciones de cada evento para que sepamos que está haciendo la session.
Lo que recomiendo es guardar esta consulta en un script a mano y poco a poco irse internalizando mas con la SYS.V_$SESSION visto que tiene mucho para darnos. Quien no esté acostumbrado se va a sorprender al encontrar muchos mas campos que en su equivalente de SQL Server.
Pueden encontrar mas detalles en:
http://download.oracle.com/docs/cd/B19306_01/server.102/b14237/dynviews_2088.htm

Por último si lo que se desea es hacer algo mas semejante al sp_who2 y no tienen ganas de internalizarse en los demas campos, puede hacerse una función que nos retorne los resultados de la consulta. Esta función sería:

-- Creacion de funcion que retorna como resultado una tabla
CREATE OR REPLACE function sp_who2 return sp_who2_tab PIPELINED
IS
CURSOR cur0
IS
SELECT sid, status, Username, terminal, blocking_session_status, schemaname, a.name Command_Action, logon_time, program
FROM SYS.V_$SESSION s
INNER JOIN AUDIT_ACTIONS a ON s.command=a.action;

out_rec sp_who2_rec := sp_who2_rec(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
BEGIN
OPEN cur0;
LOOP
FETCH cur0 INTO out_rec.sid, out_rec.status, out_rec.Username,
out_rec.terminal, out_rec.blocking_session_status, out_rec.schemaname, out_rec.Command_Action, out_rec.logon_time, out_rec.program;
EXIT WHEN cur0%NOTFOUND;
PIPE ROW (out_rec);
END LOOP;
CLOSE cur0;
RETURN;

END;

Y como sucede con las funciones que retornan tablas en Oracle, la forma de utilizarla sería:
SELECT * FROM TABLE(sp_who2);
Claramente a esto hay que sumarle la creación de los objetos necesarios (record y table).
Espero que les haya servido!

lunes, 17 de enero de 2011

Drop and Create

Muchas veces al armar los scripts figuran creación de objetos, y obviamente es deseable que nuestro script no falle por mas que dicho objeto ya se encuentre creado. En el caso de los stored procedures, en Oracle, tenemos la opción de CREATE OR REPLACE, pero cuando nos manejamos con tablas esta opción, lamentablemente, no existe ni en Oracle ni en SQL Server.
Para eliminar y crear una tabla desde cero en SQL Server es bastante conocido el método (de echo en la versión 2008 en adelante ya lo tenemos a un click del managment). El código es:

-- Prueba 1: Controlo si existe la tabla, si es necesario la elimino y la vuelvo a crear
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.prueba_creacion') AND type in (N'U'))
DROP TABLE dbo.prueba_creacion

CREATE TABLE prueba_creacion(clave INT);

De esta forma controlamos si la tabla existe, y en caso afirmativo la eliminamos. Luego podemos proceder a crearla sin problema.
Otra es la historia cuando la tabla es una tabla temporaria. Lo intuitivo sería ahcer lo mismo con el nombre de la tabla temporaria, pero esto arrojaría error. ¿porqué? porque la tabla temporaria se encuentra alojada en la tempdb. El código para poder realizar lo mismo con tablas temporarias sería:

-- Prueba 2: Controlo si existe la tabla temporaria, si es necesario la elimino y la vuelvo a crear
IF NOT OBJECT_ID('tempdb.dbo.#prueba_creacion_temp') IS NULL
DROP TABLE dbo.#prueba_creacion_temp

CREATE TABLE #prueba_creacion_temp(clave INT);

Pueden ver que se código no importa cuantas veces se ejecute nunca arrojará error.
Por último es interesante resaltar que en oracle no podemos realizar ninguno de estos controles debido a que el parser arrojará error la primera vez al hacer un DROP TABLE de una tabla que aún no existe. Por este motivo la forma de realizar lo mismo en Oracle sería:

-- Prueba 3: Controlo si existe la tabla en oracle, si es necesario la elimino y la vuelvo a crear

BEGIN
EXECUTE IMMEDIATE 'DROP TABLE prueba_creacion';
EXCEPTION WHEN OTHERS THEN NULL;
END;

CREATE TABLE prueba_creacion(clave INT);

De esta forma se ejecutará siempre el DROP TABLE y arrojará error de sintaxis cuando no exista la tabla, pero el error será en el sql dinámico y no en todo el script, y estará capturado por el WHEN OTHERS, haciendo que sea transparente para el resto de la ejecución.

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.