Archivo de la categoría 'SQL Server 2008'


Índices filtrados en SQL Server 2008

Lunes, 21 Sep, 2009 @ 03:39 | Por Dario Krapp | SQL Server, SQL Server 2008

La intención de este artículo es la de comentar una de las nuevas capacidades de SQL Server 2008 que es la posibilidad de crear índices filtrados, pero me parece que es una buena oportunidad para mencionar que son los índices, cual es su objetivo, que tipos de índices existen y dejar para final del artículo este asunto de los índices filtrados.

Comencemos por la idea más básica que es la de preguntarse qué es un índice y para qué sirve, un índice es un mecanismo que permite acceder a un conjunto de datos en forma más eficiente que si no se utilizase dicho mecanismo, considerando a la velocidad de acceso a los datos como el factor de eficiencia que los índices optimizan. En el primer tipo de índice que vamos a comentar la estrategia de optimización consiste en ordenar físicamente los datos de forma que puedan encontrarse más rápidamente, esto significa que en este tipo de índice existirá una o varias columnas que definirán de que manera estará la tabla físicamente ordenada.
Esta idea no es nueva, para quienes hayan programado alguna vez en cualquier lenguaje sabrán que si debemos buscar un valor en un vector ordenado, podremos utilizar algunas técnicas como por ejemplo la búsqueda binaria que permitirán encontrar los datos buscados en orden logarítmico a diferencia del inmejorable orden lineal cuando los valores dentro del vector están desordenados. Para quien nunca haya programado podrá recordar un diccionario, en un diccionario un usuario busca una definición (datos) a partir de una clave (palabra a buscar) y el hecho de que los datos estén ordenados por la clave (o sea las definiciones por las palabras) permitirá que el usuario no tenga que recorrer todas las palabras del diccionario hasta encontrar la palabra deseada. De forma similar dentro de la estructura de tablas del SQL Server el hecho que los datos se encuentren ordenados físicamente por la clave permitirá un acceso más rápido a los mismos. No estará quien se pregunte qué sucederá cuando se inserte un nuevo registro con la performance, y no hay dudas que será menos eficiente que si los datos estuviesen desordenados, pero no hay que olvidar que lo que se desea es eficiencia en las operaciones de búsquedas, que son las que se realizan con mayor frecuencia.

La forma más sencilla de ver la diferencia que puede provocar un índice de este tipo es crear una tabla simple en nuestro motor de base de datos SQL Server y ver el plan de ejecución en ambos casos (con y sin el índice), comencemos creando la tabla y agregando algunos valores:

CREATE TABLE [dbo].[Datos1](
	[ID] [int] NOT NULL,
	[Numero] [int]NOT NULL,
	[Descripcion] [nvarchar](50) NOT NULL,
)
INSERT INTO Datos1 ([ID],[Numero],[Descripcion]) VALUES (1,1,'D1')
INSERT INTO Datos1 ([ID],[Numero],[Descripcion]) VALUES (2,2,'D2')
INSERT INTO Datos1 ([ID],[Numero],[Descripcion]) VALUES (3,3,'D3')
INSERT INTO Datos1 ([ID],[Numero],[Descripcion]) VALUES (4,4,'D4')

Luego iniciaremos una búsqueda y veremos el plan de ejecución. El plan de ejecución mostrará de qué manera el query optimizer intentará acceder a los datos durante una consulta, (El query optimizer es el encargado de diseñar la estrategia del acceso a los datos).
Existen varias maneras de ver el plan de ejecución, utilizaremos en estos ejemplos la forma grafica.
Luego de haber ejecutado el script previo deberemos escribir lo siguiente en un query analizer:

SELECT [ID], [Numero], [Descripcion] FROM Datos1 WHERE ID=1

Y luego presionar CTRL+L. Se obtendrá un resultado similar a lo siguiente:

Los planes de ejecución en formato gráfico deben leerse de izquierda a derecha y de arriba hacia abajo, y aunque pueden ser extremadamente largos y complejos de leer, en nuestro caso podemos ver el mismo está compuesto por solamente dos iconos y una flecha que los une a ambos. Cada icono representará una operación y la flecha simbolizará el movimiento de datos entre las dos operaciones, indicándonos que la operación “Table Scan” ha tomado los datos que la operación SELECT procesará, en realidad la operación SELECT no ha hecho nada en este caso. Este diagrama nos indica que está haciendo internamente el motor de base de datos.

Una operación “Table Scan” nos está indicando que el motor ha necesitado recorrer secuencialmente la tabla Datos1 para poder encontrar los registros que cumplan con la condición pedida.
La operación “Table Scan” es equivalente a tener un diccionario desordenado donde es necesario recorrerlo secuencialmente hasta encontrar la palabra que deseamos buscar, pero además la palabra puede existir más de una vez, así que siempre deberemos recorrerlo hasta la última palabra para asegurarnos que hemos encontrado todas las definiciones. Cuando no hay índices creados la performance de las búsquedas quedan gravemente comprometidas.
En contraposición crearemos un índice y veremos que cambios se producen en el plan de ejecución, ejecutaremos la siguiente línea de código:

 CREATE CLUSTERED INDEX IX_1  ON [dbo].[Datos1] (ID)

Donde hemos indicado la creación de un índice por la columna “ID”,(la palabra CLUSTERED indicará que la tabla se ordernenará físicamente por el índice solicitado, luego veremos que existe otro tipo de índices que no impone tal condición.)
Si volvemos a ejecutar la consulta anterior, el plan de ejecución tomará el siguiente formato:

Indicando que en este caso la búsqueda de datos está utilizando el índice IX_1, de manera que el motor ya no debe recorrer toda la tabla para encontrar los registros pedidos.
Podemos ahora preguntarnos que pasaría si además es necesario realizar búsquedas por otro campo, supongamos por el campo “Numero”, en este caso no podremos reordenar la tabla físicamente por “Numero”, ya que al hacer esto perderíamos el orden físico que ya habíamos establecido por el campo “ID”, es claro que el orden físico puede establecerse solo para una clave (ya sea compuesta por un solo o varios campos). Para estos casos existen otro tipo de índices conocidos como índices non-clustered, ya que no modifican el orden físico de los registros en la tabla original, estos índices guardarán en otra estructura una copia de los valores involucrados en la clave y un puntero al registro original de la tabla. Para probar lo antes comentado ejecutaremos el siguiente comando:

CREATE INDEX IX_2  ON [dbo].[Datos1] (Numero)

Y luego veremos el plan de la siguiente búsqueda:

Donde puede verse que el query optimizer ha decidido utilizar el nuevo índice IX_2.

Habrá seguramente quien se haya percatado que en este último query solo estamos incluyendo a la columna “Numero” y se pregunte el por qué de esta decisión?, y más aun, habrá quien pareciéndole extraño realizará la misma búsqueda pero esta vez con todos los campos (al menos eso espero). Si es así, quien realice esta prueba descubrirá algo pertubador, y es que el query optimizer habrá decidió utilizar el índice IX_1, y no IX_2, pero por que? podrán preguntarse y la respuesta es la siguiente:
Como comentamos previamente los índices non-clustered guardan una copia de las claves y un puntero al registro original, de esta manera cuando hemos buscado solamente por “Numero” el índice IX_2 es capaz de devolver la información solicitada ya que posee el valor de la columna “Numero”, pero cuando hemos pedido otros datos como “ID” y “Descripcion” que no existen en IX_2 el query optimizer ha decidido que es menos costoso recorrer la tabla por IX_1 para devolver los datos que IX_2 no posee. Cuando un índice non-clustered cubre todos los datos solicitados en la consulta se dice que es un covered-index, el caso contrario no será un covered-index y el query optimizer deberá buscar alguna estrategia para obtener los datos faltantes, obviamente los clustered index son siempre covered index, ya que poseen el registro completo.
El query optimizer puede utilizar otras estrategias para obtener los datos faltantes como veremos a continuación. Si ejecutamos el siguiente código:

DELETE FROM Datos1
DECLARE @C int =1
WHILE @C < 10000
BEGIN
	INSERT INTO Datos1 ([ID],[Numero],[Descripcion])
	VALUES (@C,@C + 1,'D1' + cast(@C as nvarchar(10)))
	SET @C+=1
END

Donde solamente hemos agregado más datos y volvemos a ejecutar la consulta anterior veremos lo siguiente:

Ahora el query optimizer ha utilizado nuestro índice IX_2 pero para recuperar los datos faltantes a requerido efectuar una operación de Key Lookup extra utilizando el índice IX_1, para finalmente unir los datos en la operación Nested Loops. Si creamos un nuevo índice que cubra todos los datos pedidos de la siguiente forma:

CREATE INDEX IX_3  ON [dbo].[Datos1] (Numero,ID,Descripcion)

No debería sorprendernos el siguiente resultado:

Otra opción para incluir las columnas restantes es utilizar la sentencia INCLUDE de la siguiente forma:

CREATE INDEX IX_3 ON [dbo].[Datos1] (Numero)  INCLUDE (Descripcion, ID)

En el segundo caso, las columnas son agregadas al índice pero no forman parte del mismo.

En ambos tipos de índices, clustered o non-clustered existe la posibilidad de definirlos como únicos (unique), un índice único no admite repetición de valores, y permite una mayor optimización en las búsquedas. Las claves primarias de las tablas están compuestas por índices “unique” que pueden ser o no clustered.

En Sql Server 2008 existe además la posibilidad de crear índices filtrados, o sea índices que se aplican solo a un grupo de datos. Para probarlo podemos eliminar los índices IX_2 e IX_3 y crear un nuevo índice IX_4 filtrado, las siguientes líneas de código efectuan estas operaciones:

DROP INDEX IX_2 ON [dbo].[Datos1]
DROP INDEX IX_3 ON [dbo].[Datos1]

CREATE INDEX IX_4 ON [dbo].[Datos1] (Numero,ID,Descripcion) WHERE Numero < 100

De esta forma el índice IX_4 será aplicable para algunas condiciones solamente, por ejemplo si ejecutamos el siguiente query:

El query optimizer ha decidido emplear IX_4 mientras que en el caso de:

Ha optado por IX_1.

Por último me queda por comentar que existen además de los índices que hemos mencionado (que son los que se utilizan en la mayoría de los casos) los índices full text, los índices XML y los índices espaciales, los cuales espero podamos ver en algún próximo articulo.

Espero como es la costumbre que este articulo haya sido de utilidad y nos vemos en el próximo.

VN:F [1.7.3_972]
Rating: 8.3/10 (7 votos cast)

Arreglos en SQL Server

Viernes, 03 Oct, 2008 @ 17:18 | Por Dario Krapp | .NET, SQL Server 2008

El uso de arreglos (o arrays) es tan necesario en ciertas ocasiones que ha hecho que su aparición sea tan inmediata como los primeros lenguajes declarativos, y hasta la actualidad, los lenguajes más modernos no los han descartado.

mov [BX],AX; Assember
array = (int *)malloc (N*sizeof(int)); C
type Int_Buffer is array (1..10) of Integer; Adda
int *iarray;iarray = new int [10]; C++
DIM vars$(52) Basic
Dim c(3 to 82) as Integer Microsoft Visual Basic
int[] numbers = new int[10]; C#

Entonces ¿podrían ser los arreglos tan necesarios en el T-SQL de Sql Server? La respuesta es que depende para quien, como suelen ser las respuestas a casi todas las preguntas formulables. Quizás esta necesidad no se manifieste tanto dentro del propio motor de base de datos, ya que siempre es posible acceder a los datos necesarios de alguna u otra forma, pero dentro del mundo de los desarrolladores la necesidad es innegable, como lo veremos con un ejemplo posteriormente. Claro que todo este preámbulo no tendría sentido si el T-SQL de Sql Server tuviese arreglos, pero de hecho no los posee (dejemos de lado por ahora la llegada de SQL Server 2008). ¿Entonces como se puede resolver este problema?, plantearemos entonces el siguiente caso que es, como acostumbramos, un caso del mundo real.

Hace un tiempo desarrollamos una aplicación que empleaba SQL Server 2005 como gestor para el soporte de datos. No tardó en aparecer la siguiente situación, entre nuestras tablas se daba, por las reglas de negocios el caso de que cuando se daba de alta un registro en una tabla del tipo “cabecera” debían asociársele unos cuantos registros del tipo “detalle” conjuntamente. Supongamos el caso de las tablas que se muestran a continuación que representan la estructura del problema abstrayendo complicaciones adicionales.


En nuestro caso estábamos empleando C# sobre el Framework 3.5 y ADO.NET para que nuestra capa de datos accediera al motor de base de datos SQL Server 2005, ante el problema analizamos las siguientes opciones;

  1. Emplear procedimientos almacenados (haciendo tantas llamadas como fueran necesarias).
  2. Emplear consultas dinámicas.
  3. Emplear un procedimiento almacenado que resolviera todo de alguna forma.

Recordemos que nuestro problema a resolver era que cuando se creaba un registro cabecera, en conjunto se creaban siempre varios registros detalle.

La primera aproximación fue la más inocente, poseer un procedimiento almacenado que inserta una cabecera y otro que inserta un detalle, llamémoslos ins_cabecera e ins_detalle para darle nombres intuitivos, entonces si había que crear una cabecera con, por ejemplo, 10 detalles. La metodología era llamar a los procedimientos almacenados desde nuestra capa de datos en .NET de la siguiente forma

ins_cabecera “datos cabecera”
ins_detalle “datos detalle 1″
ins_detalle “datos detalle 2″
ins_detalle “datos detalle 3″
ins_detalle “datos detalle 4″
ins_detalle “datos detalle 5″
ins_detalle “datos detalle 6″
ins_detalle “datos detalle 7″
ins_detalle “datos detalle 8″
ins_detalle “datos detalle 9″
ins_detalle “datos detalle 10″

Claro está que funciona perfectamente, pero hay algo que no se ve muy bien y es cada vez que se llama a una inserción en la capa de datos de .NET se produce una larga cantidad de llamadas contra el motor de base de datos, a pesar de ser procedimientos almacenados, claro está que hay una gran pérdida de tiempo. Inmediatamente se pensó que había que evitar hacer tantas llamadas.

Una segunda opción, (que creo que la descartamos incluso desde antes que a alguien se le ocurriera mencionarla), es la del uso de consultas dinámicas. Básicamente es la idea de escribir una larga sentencia T-SQL y pasársela al servidor de una sola vez para que el mismo la ejecute, este tipo de solución haría una sola llamada, claro está, pero hay muchas desventajas al usar consultas dinámicas en contrapartida a procedimientos almacenados.

En SQL Server (y estoy convencido que en cualquier otro motor de base de datos también), el uso de procedimientos almacenados ofrece ventajas en contraste a las consultas dinámicas, un factor clave es por ejemplo la compilación del plan de trabajo (la estrategia que empleará el motor de base de datos para acceder a los datos) que es reutilizada en procedimientos almacenados y no tiene que recalcularse cada vez que se lanza la consulta, un factor que suma puntos a la hora de evaluar la performance.

Además el uso de procedimientos almacenados asegura la inmunidad contra ataques por injection, como ya sabemos es posible crear consultas dinámicas parametrizadas, pero queda en manos de l del programador utilizarlos, quien puede omitir esta práctica y armar las sentencias por simple concatenación, quedando el sitio vulnerable a ataques por injection, (aunque parezca una locura, claramente este problema es más común de lo que uno se imagina), en cambio, en las llamadas a procedimientos almacenados no existe la posibilidad de dejar esa puerta de entrada. Finalmente otra ventaja es que los procedimientos almacenados pueden invocarse unos a otros, lo cual puede permitir atomizar y reutilizar código, otro punto a favor (Exceptuando lamentablemente nuestro caso en SQL Server 2005 donde la opción no está disponible) los procedimientos almacenados pueden depurarse con mucha facilidad, estos son a mi parecer motivos suficientes para desterrar a las consultas dinámicas, aunque hay situaciones en que no es posible utilizar procedimientos almacenados, por ejemplo existen aplicaciones que crean, modifican y destruyen estructuras de datos dentro del motor de base de datos empleando instrucciones DDL, en éstos casos el acceso a datos posiblemente deberá llevarse a cabo mediante consultas dinámicas. Esperemos que los programados sean cautos en esos casos delicados.

Entonces nos quedó nuestra última carta emplear una sola llamada a un procedimiento almacenado que resolviera todo el problema. Siendo algo así como la solución mágica, la misma consideraría pasarle a nuestro procedimiento almacenado, llamémoslo ins_cabdet, los datos de la cabecera y una estructura con todos los detalles (ya que no sabemos cuántos puede haber) para que los inserte juntos ins_cabdet de una sola vez.

Nuestra función desde la capa de datos en .NET tomaría la siguiente forma:

ins_cabdet “datos cabecera”, Estructura(“datos detalle 1″, “datos detalle 2″ … “datos detalle 10″)

Pero aquí mismo es donde aparece el problema, un arreglo nos vendría más que bien en este caso, pero ya dijimos que SQL Server no soporta arreglos (para quien recién halla cambiado de canal y se haya perdido el principio, recordemos que estamos omitiendo SQL Server 2008). Claramente ahora deberíamos pensar que los arreglos si eran tan necesarios el T-SQL de Sql Server. De todas formas no todo estuvo perdido, el problema pudimos resolverlo desde otro lugar algo lejano. Una de las características que posee SQL Server 2005 es el uso de XML, SQL Server 2005 posee un tipo de datos XML y es capaz de parsear XML’s empleando incluso XPath, entonces la idea fue clara. si no podemos pasarle arreglos al SQL Server intentemos pasarle un parámetro XML con los datos necesarios para poder realizar las inserciones, el XML es flexible y fácil de usar por lo que este tipo de solución es adaptable a muchos casos similares. El precio es claramente la creación del XML desde .NET y el parseo desde SQL Server, pero hoy día ya casi nada es gratis.

Debimos diseñar primero un formato de XML, creamos algo similar a lo siguiente:

<r>
<h nm="nombre_cab" />
 <dc>
  <d nm="nombre_de1"/>
  <d nm="nombre_de2"/>
 </dc>
</r>

Con nuestro fragmento XML armamos un procedimiento almacenado, al que llamamos Ins_CabDet el cual haría lo siguiente:

  • Extracción de cabecera del xml
  • Inserción de cabecera
  • Para cada detalle del xml
    • Extracción del detalle del xml
    • Inserción del detalle

En el mundo real la función tomó la siguiente forma:

CREATE PROCEDURE Ins_CabDet
 @doc xml
AS
BEGIN
 BEGIN TRY
  DECLARE @docHandle int
  EXEC sp_xml_preparedocument @docHandle OUTPUT, @doc
  SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
  BEGIN TRAN
  INSERT INTO Cabecera (Nombre)
  SELECT nm 'Nombre'
  FROM
  OPENXML (@docHandle,'r/h',1)
   WITH (
    nm nvarchar(20)
   )
  DECLARE @IDCabecera int
  SET @IDCabecera = @@IDENTITY
  INSERT INTO Detalle (IDCabecera, Nombre, OtroDato1, OtroDato2)
  SELECT @IDCabecera 'IDCabecera', nm 'Nombre', od1 'OtroDato1', od2 'OtroDato2'
  FROM
  OPENXML (@docHandle,'r/dc/d',1)
   WITH (
    nm nvarchar(20),
    od1 int,
    od2 datetime
   )
  COMMIT
  RETURN 0
 END TRY
 BEGIN CATCH
  IF @@TRANCOUNT > 0
   ROLLBACK
  RETURN -1
 END CATCH
END

Básicamente hace lo esperado, parsea el XML y lo inserta en las tablas adecuadamente.

Finalmente quedaba un último punto, efectuar la llamada desde la capa de datos en .NET, escribimos el código necesario (en nuestro caso estábamos empleando VS2008) para armar el XML y llamar al procedimiento almacenado, creamos una función similar a la siguiente:

public void Ins_CabDet(string Cab_Nombre, List Detalles) {
 using (SqlCommand objSCmd = new SqlCommand("Ins_CabDet", new SqlConnection("Data  Source=XXX...."))) {
  objSCmd.CommandType = CommandType.StoredProcedure;
  objSCmd.Connection.Open();
  MemoryStream objMS = new MemoryStream();
  using (XmlWriter objXMLW = XmlWriter.Create(objMS))
  {
   objXMLW.WriteStartElement("r");
   objXMLW.WriteStartElement("h");
   objXMLW.WriteAttributeString("nm", Cab_Nombre);
   objXMLW.WriteEndElement();
   objXMLW.WriteStartElement("dc");
   foreach (Detalle objDet in Detalles) {
    objXMLW.WriteStartElement("d");
    objXMLW.WriteAttributeString("nm", objDet.Nombre);
    objXMLW.WriteAttributeString("od1", objDet.OtroDato1.ToString());
    objXMLW.WriteAttributeString("od2", objDet.OtroDato2.ToString("yyyyMMdd",     CultureInfo.InvariantCulture));
    objXMLW.WriteEndElement();
   }
   objXMLW.WriteEndElement();
   objXMLW.WriteEndElement();
   objMS.Position = 0;
   objSCmd.Parameters.Add("@doc", SqlDbType.Xml).Value = new SqlXml(objMS);
  }
  objSCmd.ExecuteNonQuery();
 }
}

Detalle es la estructura:


public struct Detalle {
 public string Nombre { set; get; }
 public int OtroDato1 { set; get; }
 public DateTime OtroDato2 { set; get; }
}

Con esta simple solución pudimos resolver el problema. Un punto a considerar es que en realidad no estamos pasando arreglos, de alguna forma estamos adaptando algo que fue pensado para otra cosa, como una especie de artificio para conseguir nuestro fin, por lo que este tipo de solución no es una solución universal y debería evaluarse en qué casos simplifica y en que otros complica el problema a resolver. Otra opción que no mencionamos es crear un tipo de datos y un procedimiento CLR, aprovechando las ventajas de poder ejecutar código manejado dentro del motor SQL Server 2005, esta es una opción válida que dependiendo del caso también podría implementarse.

Arreglos en SQL 2008

La versión 2008 de Sql Server ha resuelto el problema, finalmente hay arreglos disponibles, aunque los arreglos en T-SQL poseen una visión más cercana al manejo de tablas que al manejo de arreglos de los lenguajes de programación a los que los desarrolladores estamos acostumbrados, esto no provoca ningún problema. La idea aplicada al caso anterior es la siguiente;

suponiendo que seguimos teniendo las mismas tablas Cabecera y Detalle, lo primero que vamos a hacer es crear un tipo de datos de usuario, en SQL Server 2008 no hay que escribir código CLR, la forma de hacerlo es simplemente escribir un script, para nuestro caso el siguiente:


create type TipoDetalle as table
(
 Nombre nvarchar(20),
 OtroDato1 int,
 OtroDato2 DateTime
)

Algo bastante similar a nuestra estructura en C#, una vez creado el tipo de datos ya podemos pasar a crear el procedimiento almacenado


CREATE PROCEDURE Ins_CabDet
(
 @Nombre_Cab nvarchar(20),
 @Detalles TipoDetalle readonly
)
AS
BEGIN
 BEGIN TRY
  SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
  BEGIN TRAN
  INSERT INTO Cabecera (Nombre) VALUES (@Nombre_Cab)
  DECLARE @IDCabecera int
  SET @IDCabecera = @@IDENTITY
  INSERT INTO
  Detalle (IDCabecera,Nombre, OtroDato1, OtroDato2)
  SELECT @IDCabecera ,Nombre, OtroDato1, OtroDato2
  FROM
   @Detalles;
  COMMIT
  RETURN 0
 END TRY
 BEGIN CATCH
  IF @@TRANCOUNT > 0
   ROLLBACK
  RETURN -1
 END CATCH
END

Claramente puede verse en T-SQL un arreglo es visto como una especie tabla, en cada fila de la tabla hay un elemento (del tipo Detalle en nuestro caso, para eso primero debimos crearlo) y
cada columna representa una propiedad del elemento (Nombre, OtroDato1, etc.), como mencionamos previamente, es un poco diferente a lo usual, pero no puede negarse que es una visión bastante razonable para un motor de base de datos.

Finalmente queda ver como efectuar la llamada desde .NET, en C# la llamada tomaría la siguiente forma:

public void Ins_CabDet(string Cab_Nombre, List Detalles) {
 DataTable dt = new DataTable();
 dt.Columns.Add("Nombre", typeof(string));
 dt.Columns.Add("OtroDato1", typeof(int));
 dt.Columns.Add("OtroDato2", typeof(DateTime));
 foreach (Detalle objDet in Detalles) {
  DataRow dr = dt.NewRow();
  dr[0] = objDet.Nombre;
  dr[1] = objDet.OtroDato1;
  dr[2] = objDet.OtroDato2;
  dt.Rows.Add(dr);
 }
 using (SqlCommand objSCmd = new SqlCommand("Ins_CabDet", new SqlConnection("Data  Source=XXX...."))) {
  objSCmd.CommandType = CommandType.StoredProcedure;
  objSCmd.Connection.Open();
  objSCmd.Parameters.Add("@Nombre_Cab", SqlDbType.NVarChar, 20).Value = Cab_Nombre;
  objSCmd.Parameters.AddWithValue("@Detalles", dt);
  objSCmd.ExecuteNonQuery();
 }
}

Un último detalle es que desde en el código C# puede verse como el arreglo es preparado como una tabla, mapeando la estructura que el motor espera recibir.

SQL Server 2008 trae muchas otras novedades como los campos Date (sin Time) y Time (sin Date), tipos de datos espaciales, la sentencia MERGE y otras cosas que seguramente veremos en algún otro artículo si es que las hemos utilizado para resolver algún problema.

VN:F [1.7.3_972]
Rating: 8.9/10 (12 votos cast)

Error al modificar la estructura de las tablas de una base de SQL Server 2008

Jueves, 07 Ago, 2008 @ 18:23 | Por Gustavo Cantero (The Wolf) | SQL Server 2008

Probablemente si recién está comenzando a utilizar el SQL Server 2008 se vea frustrado al intentar modificar la estructura de una tabla desde el diseñador que trae el Managemente Studio y este le devuelva un error como el siguiente: “Saving changes is not permitted. The changes you have made require the following tables to be dropped and re-created. You have either made changes to a table that can’t be re-created or enabled the option Prevent saving changes that require the table to be re-created.”.

Esto es debido a un nueva opción agregada al diseñador que, por defecto, evita que los usuarios puedan hacer cambios que requieran la eliminación y recreación de las tablas.  Para modificar esta opción y poder utilizar el diseñador como en las versiones anteriores, hay que destildar la opción “Prevent saving changes that require table re-creation“, ubicada en el menú “Tools”, opción “Options…”, dentro del item “Designers” y “Table and Database Designers” del árbol de la izquierda.

Espero que este “mini-tip” les sea de utilidad.

VN:F [1.7.3_972]
Rating: 0.0/10 (0 votos cast)

SQL Server 2008 y Visual Studio 2008

Jueves, 07 Ago, 2008 @ 16:39 | Por Gustavo Cantero (The Wolf) | SQL Server 2008, Visual Studio

Cómo muchos sabrán, ayer salió la versión RTM (ya no un Release Candidate) del Microsoft® SQL Server 2008, el cual bajamos e instalamos en nuestro servidor. El primer inconveniente que encontramos fue que no se pudo hacer un upgrade desde el SQL Server 2005 Standard al SQL Server 2008 Web Edition, lo cual no fue mucho problema, simplemente desinstalamos nuestro SQL actual, instalamos el 2008 y “attachamos” las bases anteriores en el nuevo motor.

Hasta acá funcionó todo bien, pero no todo es tan sencillo, ya que con el Management Studio del SQL Server 2005 no se puede conectar al del SQL Server 2008 (obviamente), entonces intentamos instalar el Management Studio nuevo en las máquinas de desarrollo, las cuales ya tenian instalado el Visual Studio 2008 Professional.
Luego de varios pasos de validaciones, instalaciones de los archivos del instalador (si, aunque suene redundante) e instalaciones de parches (incluido el .NET 3.5 Service Pack 1), llegamos a un último chequeo en el que el instalador nos informa que hay un error en una “regla”. Este error es, ni más ni menos, que el siguiente: Rule “Previous releases of Microsoft Visual Studio 2008″ failed.” A previous release of Microsoft Visual Studio 2008 is installed on this computer. Upgrade Microsoft Visual Studio 2008 to the SP1 before installing SQL Server 2008, o traducido, Error en la regla ‘Versiones anteriores de Microsoft Visual Studio 2008′.” En el equipo hay instalada una versión anterior de Microsoft Visual Studio 2008. Actualice Microsoft Visual Studio 2008 al SP1 antes de instalar SQL Server 2008. Lo primero que intentamos hacer es instalar el “Visual Studio 2008 Service Pack 1″ pero, para nuestra sorpresa, sólo hay un Beta de este paquete, el cual bajamos e instalamos de todas formas, pero todo fue inutil, el instalador del SQL Server 2008 seguia devolviéndonos el mismo mensaje.  Luego de buscar en el sitio de soporte de Microsoft® encontramos una página (http://support.microsoft.com/kb/956139/en-us) la cual nos dice que antes de instalar el SQL Server 2008 hagamos alguno de los siguientes puntos:

  • Instalar una versión comercial del Service Pack 1 para Visual Studio 2008 – el cual aún no existe.
  • Desinstalar todos los componentes del Visual Studio 2008 anteriores al Visual Studio 2008 - o sea, todos.
  • No instalar ningún componente del SQL Server 2008 que requiera el Visual Studio 2008 – el problema acá es que no dice cuales son los componentes que lo requieren pero, para nuestro pesar, descubrimos que el Managemen Studio es uno que si.

Conclusión

Si ya necesitan utilizar el SQL Server 2008 por alguna de sus nuevas características como las de seguridad o sus nuevos tipos de datos GEOMETRY y GEOGRAPHY, lo mejor es instalarlo en un servidor y conectarse desde desde las máquinas de desarrollo con el Visual Studio o con un Terminal Server al servidor, aunque la mejor opción es esperar que liberen la versión final del Service Pack 1 de Visual Studio 2008, la cual estaria disponible el 11 de Agosto.

Comentarios del 11 de agosto del 2008

A partir de hoy ya se puede bajar el Service Pack 1 final para Visual Studio 2008 desde la siguiente dirección: http://www.microsoft.com/downloads/details.aspx?FamilyId=FBEE1648-7106-44A7-9649-6D9F6D58056E&displaylang=en

VN:F [1.7.3_972]
Rating: 0.0/10 (0 votos cast)