Grimpi IT Blog

Julio 22, 2008

Nueva version de Open DBDiff

Archivado en: Open DbDiff, SQL Server — grimpi @ 3:29 am

Bueno gente, disculpen que haya “desaparecido” casi 2 meses. Pero ando con mucho laburo, otro blog que mantener, proyectos particulares, facultad, ustedes me entienden.

En fin, entre tantas cosas que vengo haciendo, una es una herramienta de comparacion y sincronizacion de schemas de bases de datos para SQL Server 2005. A principios de año, subi una version a codeplex. Ahora, recien salida del horno, esta disponible la Beta 3 de dicha aplicacion.

Que mejoras trae? Un varias:

1) Montón de bugs arreglados, especialmente en la comparacion de UserTypes. De todas las herramientas que probé de este tipo, no encontre ninguna sola, que sincronize siempre bien en todas las situaciones, incluyendo dependencias, los UserTypes. Open DBDiff en este aspecto, funciona mejor incluso que la herramienta de Red Gate y de Visual Studio.

2) Tambien arreglé bugs y lios con las depedencias para los objetos XML Schema, Assemblys y Foreing Keys.

3) Mejoras en la sincronizacion de Store Procedures, Funciones y Vistas. Agregue soporte para Funciones y Vistas con la opcion SCHEMABINDING.

4) Agregue soporte para CLR-UDT, Users y Roles.

5) Una mejor vista grafica, incluyendo un arbol de objetos, con las diferencias entre los 2 schemas.

En fin, esta nueva version, realmente esta mucho mas acabada. El que esta en el tema, le recomiendo bajarla. Como siempre, se aceptan comentarios, criticas y sugerencias.

El link para bajarla es este: http://www.codeplex.com/OpenDBiff/Release/ProjectReleases.aspx?ReleaseId=15533

Mayo 9, 2008

Vardecimal

Archivado en: SQL Server — grimpi @ 7:27 pm

A partir de SQL Server Service Pack 2, se incorpora un nuevo tipo de almacenamiento para los campos decimales, llamado vardecimal. Que es el vardecimal?
Bueno, actualmente los tipos de dato numeric o decimal son de longitud fija. Si declaramos un NUMERIC(38,0), el espacio físico en disco que va a ocupar dicho campo es de 17 bytes, sin importar el valor que tengamos almacenado.
Este es el tamaño que ocupan los tipos de datos numeric/decimal según su precisión:
Precision   Storage (Bytes)
1 to 9                5
10 to 19             9
20 to 28           13
29 to 38           17

El vardecimal lo que haría entonces es transformar los tipos de datos numeric/decimal de longitud fija a longitud variable en función del valor que tenemos almacenado en dicho campo, interesante no?.
Se podría decir que a muy alto nivel, el vardecimal es al decimal y numeric, lo que el varchar es el char.
La ventaja de este nuevo tipo de almacenamiento es obviamente el ahorro de espacio en disco y tener registros más chicos, lo que podría impactar en forma beneficiosa en una reducción de operaciones de I/O, haciendo más rápidas las lecturas.
Sin embargo es importante aclarar, que a diferencia de char/varchar, el vardecimal no es un nuevo tipo de dato, sino un atributo de la tabla, por lo tanto y esto es una limitación, en una tabla con varios campos numeric/decimal, o todas las columnas son de longitud fija o todas son de longitud variable.
Esta característica también se mantiene en SQL Server 2008 CTP6.

Bueno, como hacemos para usar el vardecimal?
En primer lugar, debemos habilitar a la base para que permita este tipo de operaciones, para eso ejecutamos esta instrucción:

exec sp_db_vardecimal_storage_format ‘DATABASE_NAME’, ‘ON’

Esto lo único que hace es habilitar el uso del vardecimal y cambiar la versión de la base de datos, para evitar que se atache la base a un servidor cuya versión sea inferior a SQL Server 2005 Service Pack 2.
Luego, habilitamos a las tablas que queremos, para que usen vardecimal, para eso, hay que ejecutar la siguiente instrucción:

exec sp_tableoption ‘NOMBRE DE LA TABLA’, ‘vardecimal storage format’,‘on’

Al ejecutar esta instrucción, todas las columnas numeric se transformaran en longitud variable. Por lo tanto, si nuestra tabla tiene millones de registros, esta operación puede llegar a durar un buen tiempo y un aspecto importante es que bloquea toda la tabla y los índices con este tipo de campos, para cualquier tipo de operación, por lo cual, por lo cual, hay que ejecutar esta operación en un horario donde no tenga impacto.
También podemos ejecutar la operación inversa, o sea, transformar todas las columnas numeric de longitud variable, en longitud fija. Para eso, hay que ejecutar el mismo SP, pero con el valor off.

exec sp_tableoption ‘NOMBRE DE LA TABLA’, ‘vardecimal storage format’,‘off’

Ahora bien, antes de ir corriendo a nuestro servidor para habilitar esta fantástica nueva feature del SQL 2005 SP2, tenemos que pensar si realmente nos trae un beneficio el uso del vardecimal. Por ejemplo, si tenemos una tabla con una columna cuyo tipo de dato sea NUMERIC(15,0) y la mayoría de los valores de esta columna tengan entre 12 y 15 dígitos, el ahorro de espacio que vamos a tener seria mínimo. Ahora, si la dispersión en el tamaño de los valores es importante, entonces conveniente evaluar el uso del vardecimal.
Para tener una aproximación del ahorro que ganaríamos habilitando este nuevo tipo de formato, SQL nos provee el siguiente SP, que calcula la potencial reducción de espacio en una tabla:

exec sys.sp_estimated_rowsize_reduction_for_vardecimal ‘NOMBRE DE LA TABLA’

Consideraciones y restricciones a tener en cuenta:
1) Como dijimos antes, el uso del vardecimal solo esta disponible a partir del Service Pack 2 Enterprise Edition y Developer Edition.
2) Si se habilita el vardecimal, el backup de la base no podrá ser restoreado en un servidor SQL Server 2005 que no sea SP2.
3) Como ya deberían saber, en SQL Server el tamaño de un registro no puede superar los 8060 bytes (aunque en determinadas situaciones hay una opción para superar este limite).
Por lo tanto, si tenemos un vardecimal cuyo valor máximo podría potencialmente exceder los 8060, SQL Server no permitirá la habilitación del vardecimal en dicha tabla.

Links:
http://msdn.microsoft.com/en-us/library/bb508963.aspx

Mayo 3, 2008

Listar todos los ítems de un Enum vía reflection

Archivado en: .NET, Reflection — grimpi @ 2:59 am

Post simple y práctico, como listar todos los ítems de un Enum vía reflection:

public static IEnumerable GetEnumList()
{
    Type enum = typeof(T);
    Array enums = Enum.GetValues(enum);
    List enumValList = new List(enums.Length);
    foreach (int val in enumValArray)
    {
        enumValList.Add((T)Enum.Parse(enum, val.ToString()));
    }
    return enumValList;
}

public static class Program
{
    public enum ObjectType
    {
        Item1 = 1,
        Item2 = 2,
        Item3 = 3
    }

    static void Main()
    {
        foreach (ObjectType state in GetEnumList())
        {
	    System.Console.Out.WriteLine(GetEnumDescription(state));
        }
    }
}

Abril 23, 2008

Tablas temporales versus Variables de Tabla

Archivado en: SQL Server — grimpi @ 5:40 pm

Una duda bastante comun entre los desarrolladores que trabajan con SQL Server, es saber
distinguir entre una tabla temporal y una variable de tabla.
Ambas estructuras presentan varias diferencias importantes, y fueron hechas para resolver distintos problemas, aunque en muchas situaciones, su uso puede superponerse.
Veamos algunas características de las variables de tabla y sus diferencias con las tablas temporales:

1) Contra lo que muchos suponen, el contenido de una variable de tabla no siempre esta completamente en memoria. En caso de que se inserte una alta cantidad de registros a la variable, se almacenará en tempdb. En cambio una tabla temporal siempre se guarda en tempdb.
Además, cuando se crea una variable tabla, es como si se efectuara cualquier otra operación DDL, por lo tanto la información de la metada se guarda en las tablas de catalogo.
Veamos este código:

DECLARE @TablaTemporal TABLE (ID int)
SELECT * FROM tempdb.sys.tables

Si ejecutamos esto, vemos que en la vista de catalogo sys.tables, aparece el objeto #XXXX, que es la variable de tabla que creamos.

2) No hay rollback en variables de tabla. Las operaciones contra un objeto de este estilo generan menos bloqueos y hacen menor uso del log de transacciones.
En caso de que se haga un INSERT/UPDATE/DELETE contra la variable, dentro de una transacción, al hacer ROLLBACK, el contenido seguirá siendo el mismo.
La ventaja de esto, es que obviamente es más rápido y consume menos recursos. La desventaja es que si necesitamos que sea transaccional la operación, no lo podemos hacer con este tipo de estructuras.
Ejemplo:

DECLARE @TablaTemporal TABLE (ID int)
BEGIN TRANSACTION
INSERT INTO @TablaTemporal (ID) VALUES (1)
INSERT INTO @TablaTemporal (ID) VALUES (2)
INSERT INTO @TablaTemporal (ID) VALUES (3)

Como podemos ver, el resultado devuelto en el ultimo SELECT es el mismo que el devuelto dentro de la transacción, por lo tanto, no efectuó el ROLLBACK.

3) No se pueden agregar índices en variables de tabla. Esto es una gran desventaja a la hora de trabajar con un conjunto gran de datos. Sin embargo, existe una manera de tener índices. Para eso, hay que definir una PRIMARY o UNIQUE KEY cuando se crea la tabla, aunque claro, no siempre este workaround nos sirve. No es posible declarar un NON-CLUSTERED index en una variable de tabla.
Con una tabla temporal en cambio, se puede efectuar las mismas operaciones que una tabla común, incluyendo el poder agregar índices.
Ejemplo de como crear una variable de tabla con índices:

DECLARE @TablaTemporal TABLE (ID int, PRIMARY KEY ([ID]))

Si ejecutamos la consulta que esta mas arriba, vamos a ver el índice de la variable de tabla en las vista de catalogo de tempdb.

4) Otro ítem importantes es que un store procedure que haga uso de tablas temporales, puede que se recompile seguido (dependiendo de como varían las estadísticas), mientras que un store que haga uso de variables de tabla no va sufrir recompilaciones. Si el store es ejecutado muy seguido o es lo suficientemente largo para que se note el tiempo de recompilación, puede llegar a ver una diferencia de performance.
De todos modos, en SQL Server 2005 se mejoro la forma en que se precompilan los store procedures que usan tablas temporales, mejorando la performance…

5) Las variables de tabla no usan paralelismo (multiple threads) en su plan de ejecución. En tablas grandes o en alta concurrencia, esto impacta en la performance, haciéndolas mas lentas en comparación con las tablas temporales.

6) Las variables de tabla tampoco recolectan estadísticas. Esto puede ser bueno y malo a la vez. En caso de tener tablas chicas, las estadísticas no son tan necesarias, por lo cual, el no tener que recolectarlas hace las operaciones de escritura mas rápidas.
Pero por otro lado, al no tener estadísticas, el plan de ejecución que genera el SQL puede no ser el más óptimo.

7) El scope de una tabla temporal es la conexión a la base. Todos los objetos de la base, puede ver el contenido de esta tabla, mientras persista la misma conexión.
Mientras que el scope de una variable de tabla es local solo el store procedure en que se la llama, al igual que una variable común.

CONCLUSION:
No se puede decir que las tablas temporales son peores o mejores que las variables de tabla. Siempre hay que evaluar las 2 alternativas.
Sin embargo, podríamos afirmar que, en operaciones con muchos datos, las tablas temporales tienen más herramientas para ofrecer mejor performance, mientras que para procesos chicos, con pocos registros, las variable de tabla son mejores, al tener menos overhead.

SELECT * FROM @TablaTemporal
ROLLBACK TRANSACTION

SELECT * FROM @TablaTemporal

SELECT * FROM tempdb.sys.indexes I INNER JOIN tempdb.sys.tables T
ON I.object_id = t.object_id

Abril 9, 2008

El patrón DAO

Archivado en: .NET, Patrones, SQL Server — grimpi @ 1:12 am

Yo no soy un fanático de implementar montón de patrones y capas a los sistemas. Hoy en día hay sistemas que para desarrollar una función, requieren escribir en hasta 7 archivos diferentes.
Muchas veces complica más las cosas de lo necesario.
Sin embargo, uno de los patrones en mi opinión “fundamentales” del desarrollo de software, es el DAO (Data Access Object).
El DAO maneja la conexión con la fuente de datos para obtener y almacenar datos.

El uso de este patrón ofrece varios beneficios para la persistencia de datos:
* Sirve para separar el acceso a datos de la lógica de negocio. Algo altamente recomendable en sistemas medianos o grandes, o que manejen lógica de negocio compleja.
* Encapsula la fuente de datos. Esto es especialmente beneficioso en sistemas con acceso a múltiples entradas.
* Oculta la API con la que se accede a los datos. Por ejemplo en .NET si usamos siempre OleDb y queremos cambiar al NHibernate.
* Centraliza Todos los Accesos a Datos en un Capa Independiente

Cuando trabajamos con DAO, trabajamos en un mundo donde desconectado, donde nuestros datos se deben persistir en objetos.
Por lo tanto, cuando nos piden realizar una operación, se abre la conexión a la base, se ejecuta el comando, si es una operación de lectura, se vuelca el contenido hacia una estructura de datos y se cierra la conexión.

Transacciones:
Un problema muy común cuando se trabaja con este patrón, es como usar las transacciones.
Se deben ejecutar todas las operaciones dentro de un método?
Puede un DAO invocar métodos en otro DAO? Quien y como se deben manejar las transacciones?
Bueno, todo esta problemática que define el límite de la transacción se llama “Transaction demarcation”.
Existen 2 modelos diferentes que aplican según la necesidad de la operación transaccional que se va a efectuar.
Uno hace al objeto DAO responsable de la transacción, el otro desplaza la transacción al objeto que esta llamando el método DAO.
El primer caso (y el más usual) se puede codificar de la siguiente manera en C#:

public class ProductoDAO
{
    public void Eliminar()
    {
        SqlConnection connection = new SqlConnection(“CONEXION”));
        conn.open();
        using (SqlTransaction trans = new SqlTransaction(conn))
        {
            SqlCommand com = new SqlCommand(“DELETE FROM P1″, conn, trans);
            com.Execute();
            SqlCommand com2 = new SqlCommand(“DELETE FROM P2″, conn, trans);
            com2.Execute();
            SqlCommand com3 = new SqlCommand(“DELETE FROM P3″, conn, trans);
            com3.Execute();
            trans.Commit();
        }

conn.close();
}

}

En este ejemplo, para eliminar todos los productos y tablas relacionadas, se llama al objeto que maneja las transacciones de ADO.NET.
Con lo cual es perfectamente lógico encapsular el borrado de todas las tablas en un mismo método DAO, ya que se está afectando solamente la entidad Producto.

Pero supongamos que queremos eliminar transaccionalmente además de todos los productos, todas las ventas y todas las agencias.
Seria correcto meter la lógica del borrado de Ventas y Agencias dentro del método EliminarProducto? Como funcionar, funcionaria obviamente, pero estaríamos mezclando operaciones de distintas entidades y peor aún, estaríamos agregando métodos poco reutilizables y confusos.
Entonces lo correcto en este caso sería meter la lógica de la transacción fuera del DAO.
Para poder implementar esto, es necesario usar Transacciones distribuidas que .NET se implementan con EnterpriseService (En Java existe JTA). En caso de que usemos SQL Server 2005, podemos simplificar su considerablemente.

public class Negocio

{
    public static void EliminarTodo
    {
        using (TransactionScope scope = new TransactionScope())
        {
            VentasDAO.Eliminar();
            AgenciasDAO.Eliminar();
            ProductoDAO.Eliminar();
            scope.Complete();
        }
    }
}

Pasaje entre capas:
Ahora bien, ya sabemos porque debemos usar DAO y que beneficios nos trae.
Pero como deben ser el pasaje de datos con esta capa? Qué tipo de estructura usar?
En .NET existen varias variantes:

1) Dataset (tipado o no tipado)
2) XML
3) Data Transfer Objects (o objetos de Entidad)

Dependiendo de las particularidades de la aplicación, una opción puede ser mejor que la otra.
Definitivamente en la mayoría de los casos, usar Dataset no es la mejor práctica. Estamos de acuerdo que tal vez sea la opción más “cómoda”, pero los dataset son objetos pesados y lentos (en comparación con un Datareader), tienen montón de información, propiedades y métodos que no necesitamos.
Pero lo peor no es eso, la principal desventaja es que fuerza a toda la aplicación a usar un objeto de una tecnología especifica.

La más común de todas, es usar los Data Transfer Objects (DTO), también conocido como Value Object (VO). Es el típico patrón asociado a DAO.
Que son los DTO? Bueno, no son más que simples objetos que solo tienen getters y setters, que sirven para transportar la información de una capa a otra. El usar este patrón, ganamos performance, ya que al ser objetos livianos ocupan menos memoria, además de que nos permite abstraernos de cualquier tecnología especifica.

Recomendaciones a la hora de implementar DAO:

1) Combinar con el patrón singleton:
Tiene sentido tener múltiples instancias de DAO en memoria?
En la mayoría de los casos no, por eso yo por lo menos, suelo declarar todas mis clases DAO como static.

2) Crear una interfaz y un factory:
Todos los DAO generalmente tienen métodos comunes a todos, como DeleteById, GetById, GetAll, etc.
Declarar una interfaz IDAO, nos permitiría hacer un factory de DAOs.

3) Evitar el uso de dataset:
Si, como dije antes, es cómodo usarlos, nos ahorran tiempo de desarrollo. Pero a un costo alto.

Links:
http://www.ibm.com/developerworks/java/library/j-dao/
http://www.miguelmatas.es/blog/2007/11/13/mejorando-nuestro-dao-y-dto/
http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.htm

Abril 3, 2008

Soluciones para actualizar un registro si existe, sino insertar en SQL Server.

Archivado en: SQL Server — grimpi @ 2:06 am

Muchas veces cuando trabajamos con ABMs o algún proceso de escritura en la base de datos, al actualizar los registros, debemos establecer si vamos a efectuar un INSERT o un UPDATE. O sea, tenemos que determinar si el registro existe o no, para saber que operación se va a efectuar en la base de datos.

Generalmente se suele encapsular toda esta lógica dentro de un SP, algo que considero una muy buena practica, ya que nos desentendemos del lado de la aplicación, si se va a efectuar una operación de inserción o de modificación.

Primera solución:

Ahora bien, dentro del Store Procedure, lo solemos hacer para determinar la operación, es el famoso IF EXISTS.
Ejemplo:

IF EXISTS(SELECT ID FROM TABLA WHERE ID = @ID)
    INSERT INTO TABLA (Campo1,ID) VALUES (@Valor,@ID)
ELSE
UPDATE
TABLA SET Campo1 = @Valor WHERE ID = @ID

No es un mal enfoque, es muy claro. Sin embargo esta solución tiene dos inconvenientes:
1) Estamos pagando el costo de ejecutar un Query. Por mas que la consulta este indexada, tiene un costo.
2) No es 100% segura. En entornos muy demandantes, con alta concurrencia, puede darse el caso de que justo luego de ejecutar el IF EXISTS, otro proceso inserte en la tabla un registro con la misma PK y no tendríamos forma de darnos cuenta, generando un error de duplicate key.
Por lo tanto, esta opción que es la más común, tiene serios inconvenientes.

Segunda solución:

Una segunda opción podría ser esta:

UPDATE TABLA SET Campo1 = @Valor WHERE ID = @ID
IF @@ROWCOUNT = 0
    INSERT INTO TABLA (Campo1,ID) VALUES (@Valor,@ID)

En caso, se eliminaría el tener que ejecutar una query con el EXISTS. Aunque en caso de que no exista el registro, se ejecuta el UPDATE innecesariamente.
Si la mayoría de las operaciones van a ser del tipo INSERT, en realidad no se ganaría performance, pero por el contrario, si la mayoría de las operaciones seria del tipo UPDATE, podría llegar a ser mas performante.
De todas maneras esta solución sigue teniendo el problema de que otro proceso podría insertar un registro con la misma PK en la tabla y no tendríamos forma de darnos cuenta.

Tercera solución:

En el segundo caso ganamos un poco de performance (no siempre), pero seguimos con el mismo problema de concurrencia.
Pero ahora veamos este ejemplo de código:

BEGIN TRY
    INSERT INTO TABLA (Campo1,ID) VALUES (@Valor,@ID)
END TRY
BEGIN CATCH
    UPDATE TABLA SET Campo1 = @Valor WHERE ID = @ID
END CATCH

A nivel perfomance, es similar a las otras soluciones, pero si tenemos muchas más operaciones de inserción que de actualización, vamos a ganar velocidad.
Sin embargo, en este caso no tendríamos el inconveniente de concurrencia que sucede en los 2 casos anteriores!!. Lo cual lo hace ideal para situaciones de alta demanda.

Cuarta solución (Solo en SQL Server 2008):

SQL Server 2008, incorpora el comando MERGE (que ya teníamos en Oracle y otros motores), que sirve para resolver de una manera muy eficiente, exactamente este problema.

MERGE TABLA
USING (SELECT @ID AS ID) AS SRC ON SRC.ID = TABLA.ID
WHEN MATCHED THEN
    UPDATE SET Campo1 = @Valor
WHEN NOT MATCHED THEN
    INSERT (Campo1,ID) VALUES (@Valor,@ID)

Con este método, también solucionamos el problema de concurrencia, y además evitar tener que ejecutar consultas innecesarias. Por lo cual, podríamos decir que es la optima solución resolver este problema, aunque lamentablemente debemos esperar hasta mitad de año, cuando Microsoft libere SQL Server 2008.

Conclusión:

Vimos como un problema en apariencia tonto y trivial, puede causar serios problemas de performance y peor aun, crear errores de concurrencia y comportamientos no deseados.
Por las pruebas que hicimos en un entorno de TEST, la diferencia de performance que hicimos no son demasiadas. Pero en situaciones de alta concurrencia, las 2 primeras soluciones son definitivamente incorrectas.

Recomiendo ver estos links, que explican como funcionan los lockeos, en cada una de las distintas soluciones:

http://weblogs.sqlteam.com/mladenp/archive/2007/07/30/60273.aspx
http://weblogs.sqlteam.com/mladenp/archive/2007/08/03/60277.aspx

Y estas soluciones alternativas al mismo problema, tal vez sean un poco más complejas, pero en algunos escenarios pueden ser útiles:

http://www.samsaffron.com/blog/archive/2007/04/04/14.aspx
http://www.sqlteam.com/article/application-locks-or-mutexes-in-sql-server-2005

Marzo 16, 2008

Open DBDiff (Otro SQL Schema Compare mas, pero Open Source)

Archivado en: Open DbDiff, SQL Server — grimpi @ 12:35 pm

Muchas veces quienes trabajan y desarrollan con bases de datos, tendrán el problema de tener que ver como sincronizar los cambios en la base de datos de desarrollo con la de producción o testing. Es realmente un tema, ya que cualquier error ahí, nos hace fallar la subida al ambiente de producción (o de testing).Existen montón de herramientas que permiten sincronizar 2 base de datos y generar un script de migración de una base a otra, ahorrándonos el trabajo de nosotros tener que llevar un tracking de los cambios, lo cual lleva a una minimización de error. Una excelente herramienta para hacer esto es el Red Gate Compare, la cual es bastante potente y rápida. Lástima que sale 395 USD en su versión más económica. También Visual Studio for Database Professional incorpora una herramienta similar. Pero es necesario tener la versión Team System (la más cara) del VS (2005/2008).

Por lo tanto, he decido crear una herramienta 100% open source, desarrollada en C# usando .NET 3.5, para comparar 2 bases de datos y generar un script de diferencias.

La herramienta se llama Open DbDiff, actualmente está en estado Beta y solo funciona para SQL Server 2005, pero la hemos probado con montón de distintos casos de uso diferentes, en las más variadas situaciones y ha funcionado muy bien. Es rápida, especialmente en maquinas con más de un núcleo, ya que es multihilo. Su algoritmo de generación de script es bastante inteligente y sabe en qué situaciones debe regenerar una tabla o índice, o hacer simplemente un ALTER.

Compara los siguientes objetos:

  • Tablas (incluyendo opciones como vardecimal, text in row, etc..)
  • Columnas (incluyendo campos Formulas, opciones de xml, collate, etc..)
  • Constraints
  • Indices
  • Triggers
  • User Data Types
  • Vistas
  • Store Procedures
  • XML Schemas
  • Sinonimos
  • File Groups
  • Assemblys

De todas maneras, toda le falta pulir algunos detalles, está en estado Beta, pero es muy potente. Si está buscando herramientas de sincronización, se las recomiendo. El link para bajar el instalador es el siguiente:

https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=OpenDBiff&ReleaseId=11732

 

Marzo 8, 2008

Diferencias entre Clustered y Non Clustered Index en SQL Server

Archivado en: SQL Server — grimpi @ 9:13 pm

Una pregunta muy común para los iniciados en el mundo de SQL Server, es cuál es la diferencia entre un índice clustered y un índice non-clustered y en qué caso conviene usar un índice u otro. Bueno, empecemos a describir las características generales de un índice y luego de estos tipos de índices y las conclusiones van a ser evidentes.

Los índices son objetos de la bases de datos, cuya función es optimizar el acceso a datos. A medida que las tablas se van haciendo más grandes y se desea hacer consultar sobre estas tablas, los índices son indispensables.

Internamente un índice normal es una estructura de árbol, que cuenta con una página principal y luego esta con paginas hijas, que a su vez tiene más paginas hijas hasta llegar a la pagina final del índice (leaf level).

La clave del índice está repartida en las páginas del índice, de modo tal que la búsqueda se haga leyendo la menor cantidad posible de datos.

Estructura interna de un índice:

Después de esta brevísima introducción, donde está la diferencia entre un índice clustered y uno non-clustered? En la leaf level (la ultima pagina) del índice. En un índice non-clustered, la clave por la que buscamos tiene un puntero a la página de datos donde se encuentra el registro. Mientras que en índice clustered, la leaf level es la pagina de datos!. Con lo cual, el SQL Server, se ahorra hacer un salto para leer los datos del registro (Bookmark lookup). La diferencia es importante, ya que el uso de este tipo de índices al evitar tener que hacer lecturas adicionales para traer el registro, son más performantes.

Búsqueda por clustered index:

Búsqueda por non-clustered index:

SQL Server 2005 incorpora una nueva feature interesante en los índices non-clustered. Ahora es posible incluir dentro de la leaf page del índice, campos que en sí, no son parte de la clave. Esto nos permitirá en algunos casos, evitar el salto a la página de datos (Bookmark Lookup) que habíamos hablado anteriormente. Aunque hay que tener cuidado de seleccionar bien que campos se desean incluir al índice, porque de poner demasiados se expandería mucho el índice, haciendo ineficiente. Por ejemplo, si tenemos una tabla Personas cuyo campo DNI es un índice non-clustered y queremos hacer una consulta que solo traiga el Apellido y DNI, entonces si incluimos el campo Apellido , nos ahorraríamos tener que ir a la página de datos para buscar el valor. Es importante recalcar que el campo Apellido no sería parte de la tabla, sino un campo mas en pagina final del índice.

Ahora bien, entonces porque no siempre usar índices clustered? Bueno, en primer lugar, lamentablemente solo puede haber 1 solo índice clustered por tabla. La razón es muy sencilla y lógica: Los registros de la tabla físicamente son las paginas leaf-level del índice clustered. Los datos de la tabla esta ordenados según el índice. Y obviamente una tabla no puede simultáneamente estar físicamente ordenada de 2 maneras diferentes.
Por lo tanto, en tablas grandes y muy consultadas, tenemos que ser cuidadosos y analizar a que campos vamos a seleccionar para ser llaves del índice clustered. Tenemos 1 solo índice de este tipo por tabla, no hay que desperdiciarlo!!!
Este último punto es importante para saber en qué situaciones y para que campos se debe utilizar un clustered index o un non-clustered.

Guía general de uso de índices:

  • Campos autoincrementales (Identitys, newsequentialid, etc), deben convenientemente ser del tipo clustered index. La razón es reducir el page split (fragmentación) de la tabla.
  • Los clustered index son convenientes si se va seleccionar un rango de valores, ordenar (ORDER BY) o agrupar (GROUP BY).
  • La PK es un buen candidato para un clustered index. Pero no siempre. Por ejemplo, si tenemos una tabla de ventas, cuya PK es un identity en donde se efectúan muchas consultar por rangos de fecha, el campo Fecha seria un mejor candidato para el clustered que la PK.
  • Para búsquedas de valores específicos, conviene utilizar un non-clustered index.
  • Para índices compuestos, mejor utilizar non-clustered index (generalmente).

Marzo 3, 2008

Add-In Property Generator para Visual Studio

Archivado en: .NET, Add-In ProperyGenerator — grimpi @ 2:46 am

En mis épocas de programador en Java, usaba el Eclipse. Este excelente IDE, tenía una opción de refactoring que se usa para generar todos los getters y setters de una clase a partir de las variables privadas declaradas en la misma. Lo que en Java se llama getters/setters, en C# o VB sería el equivalente a las propiedades de una clase.

Siempre extrañé esa funcionalidad en Visual Studio. Cuando tenemos clases de 15 o más propiedades, tomarse el trabajo de generar 1 x 1, es aburrido.

Por lo tanto señores, he decidido hacerme mi propio Add-in de refactoring para agregarle esta interesante opción al Visual Studio. Este Add-in que desarrollé es compatible tanto con el Visual Studio 2005 como 2008. He visto un viejo Add-in, que se llama VsPropertyGenerator, que hace más o menos lo mismo, pero no tiene soporte y a mí no me funcionó y además, el que desarrolle yo, considero que es más fácil de usar.

Es libre, gratuito, pueden copiarlo, piratearlo y más que nada, disfrutarlo. El link para bajarlo es el siguiente:

http://www.opendbdiff.com/Addins.rar

Las opciones para instarlo se encuentran dentro de un archivo .txt. Cualquier sugerencia, problema o duda, dejen un comentario aquí, que prometo contestar.

Febrero 26, 2008

Diferencias entre Union y Concat en C# 3.0

Archivado en: .NET, Linq — grimpi @ 2:11 pm

Ayer trabajando con C# 3.0 y LINQ, me tope con una duda. Cuál es la diferencia entre el método Concat y Union de la lista?

En definitiva, ambos métodos parecen ser muy similares. Agregan el contenido de una lista en otra. Sin embargo, hay una pequeña e importante diferencia. El método Concat, agrega todos los ítems de una lista, mientras que el Union, agrega todos los ítems de la lista que no están en la otra lista.

Para hacerlo más claro, si tuviéramos que hacer la traducción a lenguaje SQL, el Concat es el equivalente al UNION ALL, mientras que Union es equivalente a UNION. Esto se puede ver claro, si usamos Linq to SQL.

Veamos este ejemplo:

List<String> list = new List<string>();
List<String> list2 = new List<string>();
list.Add(“Hola”);
list.Add(“Manuela”);
list.Add(“Jose”);
list2.Add(“Hola”);
list2.Add(“Esteban”);
list2.Add(“Ricardo”);

List<string> query = list.Concat(list2).ToList();
query.ForEach(item => System.Console.WriteLine(item));

La salida en este ejemplo serian 6 registros, o sea, la suma de list + list2.
Pero veamos este ejemplo:

List<string> query = list.Union(list2).ToList();
query.ForEach(item => System.Console.WriteLine(item));

En este caso, la salida serian solo 5 registros. Porque? Porque el item “Hola” se encuentra en las 2 listas. Por lo cual, el método Union no considera el duplicado.

Blog de WordPress.com.