Mostrando entradas con la etiqueta Sql Server. Mostrar todas las entradas
Mostrando entradas con la etiqueta Sql Server. Mostrar todas las entradas

lunes, 11 de agosto de 2014

Dapper, un micro ORM a tener en cuenta

Los ORM (Mapeo objeto – relacional) son una pieza fundamental en el desarrollo de nuestras aplicaciones.
Permite interactuar con la base de datos mediante objetos y listas de objetos, olvidándonos de los tediosos datasets.

Cuando hablamos de ORM nos viene a la cabeza Entity Framework y Nhibernate, pero existen también los llamados micro-orm que son bastante más ligeros y, siendo más simples que los ORM convencionales,  cumplen su función banstante bien, como pueden ser, entre otros, Massive, Simple.Data, Peta Poco y Dapper

Dapper es el ORM en el que se basa Stack Overflow y algunas otras webs.

https://code.google.com/p/dapper-dot-net/

Según los benchmarks en su web, llega a ser bastante más rápido que Entuty Framework y Nhibernate en realizar los Selects.

Dapper se puede usar a partir del Framework 3.5, aunque es recomendable Framework a partir de 4 para usar el potencial de las clases dynamic

Vamos a trastear un poco con él a ver cómo funciona.


Instalación
En primer lugar, decir que se trata de un método extensor de IDbConnection, por lo que podemos utilizar cualquier objeto que implemente IDbConnection( Sql Server, Oracle, MySql, ...)

Podemos descargarlo a través de NuGet, o directamente adjuntando la clase SqlMapper.cs a vuesto proyecto.


Consultas sin objetos mapeados
Para obtener una lista de objetos dinamic sin tener que usar ninguna clase:
            

string connectionString = "Data Source=(localdb)\\v11.0;Initial Catalog=NorthWind;Integrated Security=True";
IDbConnection conn= new SqlConnection(connectionString);
conn.Open();

var empleados = conn.Query("select * from employees where country = @pais", new { @pais = "UK" });
foreach (var empleado in empleados)
     Console.WriteLine("{0} {1} - {2}", empleado.FirstName, empleado.LastName, empleado.Country);
conn.Close();
Console.ReadKey();
Nótese que estamos usando Parameters en una clase anónima, por lo que evitamos inyección de SQL.

Consultas sin objetos mapeados en .Net 3.5
Si estuviéramos trabajando con Framework 3.5, bastará con añadir el símbolo de compilación condicional "CSHARP30" en las propiedades del proyecto - Compilar, y obtendremos los valores en IDictionary
var empleados = conn.Query("select * from employees where country = @pais", new { @pais = "UK" });
foreach (var empleado in empleados)
     Console.WriteLine("{0} {1} - {2}", empleado["FirstName"], empleado["LastName"], empleado["Country"]);

Consultas con objetos mapeados
Ahora añadimos una clase POCO de Empleados, y veamos que sigue funcionando, esta vez devolviendo un IEnumerable<T>
public class Employees
{
    public int EmployeeId;
    public string LastName;
    public string FirstName;
    public string Country;
}


var empleados = conn.Query<employees>("select * from employees where country = @pais", new { @pais = "UK" });
foreach (var empleado in empleados)
     Console.WriteLine("{0} {1} - {2}", empleado.FirstName, empleado.LastName, empleado.Country);
El mapeo que realiza Dapper es la coincidencia del nombre del campo y del nombre de la propiedad de la clase. Si hay alguna propiedad que difiera de los campos de la clase, simplemente no lo mapeará.

Actualizaciones, inserciones y borrados
Para realizar operaciones de escritura usaremos los siguientes ejemplos:

Observa que se pueden ejecutar varias líneas dentro de la sentencia.
conn.Execute(@"set identity_insert employees on;
                insert into employees (EmployeeId, FirstName, LastName, Country) values (@EmployeeId, @FirstName, @LastName, @Country);
                set identity_insert employees off;"
                , new { FirstName = "Oswaldo", EmployeeId = 21, LastName = "Rodriguez", Country = "Cuba" });


conn.Execute("update employees set firstname=@FirstName where employeeId=@EmployeeId", new { FirstName = "Leopoldo", EmployeeId = 21 });

También se pueden realizar operaciones masivas pasando una lista de objetos.
var empleados = conn.Query>empleado<("select * from employees where country = @pais", new { @pais = "UK" });
foreach (var empleado in empleados)
     empleado.FirstName = "-" + empleado.FirstName;
conn.Execute("update employees set firstname=@FirstName where employeeId=@EmployeeId", empleados.Select(x=>new {FirstName=x.FirstName, EmployeeId=x.EmployeeId}));

Agregados
var sql="Select * from products p left join categories c on p.CategoryID=c.CategoryID";
var products = conn.Query>Producto, Categoria, Producto<(sql, (prod, cat) => { prod.categoria = cat; return prod; }, splitOn:"CategoryID");
Conclusiones
Como podéis ver, puede ser una buena alternativa para determinados proyectos.
Espero que os sea de utilidad.

martes, 14 de febrero de 2012

Novedades SQL Server 2012 "Denali"

Como me imagino ya sabréis, el 7 de marzo de 2012 se lanzará oficialmente el Sql Server 2012.

La lista completa de las novedades que trae las podéis consultar en http://social.technet.microsoft.com/wiki/contents/articles/3783.aspx, pero me adelanto nombrando algunos puntos que he encontrado relevantes.

También os recomiendo los videos de la sesión de “12 horas de Sql Server 2012”, que los podéis encontrar en http://www.globbtv.com/microsite.aspx?id=12&cmd=0&cat=234 , donde algunas mejoras se explican de manera detallada.

Motor de base de datos


·         Clústeres a prueba de fallos. Es una configuración que permite a un clúster de servidores distribuidos remotamente seguir atendiendo  en caso de caída de un servidor. En caso de fallo de un nodo, otro nodo sigue sirviendo a las peticiones, y cuando el nodo caído se levanta, se sincroniza.  Esta característica la han bautizado como “AlwaysOn”
http://www.globbtv.com/12/microsite/1906/12-horas-sql-server-2012-alwayson
·         Sql Express LocalDB. Se crea un nuevo modelo de Sql Server que se situaría entre el Sql Server Express y el Sql Server Compact, y orientado principalmente a desarrolladores.

·         Índices columnares.

o   Resuelve rápidamente consultas con grandes volúmenes de datos.

o   Se almacena en memoria con algoritmo de compresión vertipaq.

o   Sólo carga las columnas necesarias que se incluyen en la consulta.

o   Pensado específicamente para datawarehouse, ya que las tablas que poseen este tipo de índice están en modo sólo lectura.

o   No son compatibles con columnas decimales con precisiones altas, ni con varchar(max), ni con text.

o   Aparece también el modo batch, que el SQL Server lo usa si hay índices columnares, hay cantidad de datos que lo justifique usar y se use algún join, filtro o agregación. Usa tecnología de procesamiento en paralelo, y procesa conjuntos de filas

·         Cláusula Over. Se le añade nuevas funcionalidades que permite realizar nuevas consultas
http://blog.consultec.es/index.php/2012/02/sql-server-2012-denali-novedades-en-t-sql-parte-ii/

·         Paginación de resultados:
SELECT BusinessEntityID, FirstName, LastName
 FROM Person.Person
 ORDER BY BusinessEntityID
 OFFSET 10 ROWS
 FETCH NEXT 10 ROWS ONLY;

·         Sequence: Nos permite definir en un sitio global un contador de valores en un orden e intervalo determinados y puede repetirse si se desea
http://blog.consultec.es/index.php/2012/02/sql-server-2012-denali-novedades-en-t-sql-parte-i/


·         Parse: Devuelve el resultado de una expresión, traducido al tipo de datos solicitado.
Sintaxis: PARSE ( string_value AS data_type [ USING culture ] )
·         Concat: Devuelve una cadena que es el resultado de concatenar dos o más valores de cadena.
Ejemplo: SELECT CONCAT ( 'Happy ', 'Birthday ', 11, '/', '25' ) AS Result;

·         Choose: Devuelve el elemento en el índice especificado de una lista de valores.
Ejemplo: SELECT CHOOSE ( 3, 'Manager', 'Director', 'Developer', 'Tester' ) AS Result;
Devuelve: Developer

·         IIF: Devuelve uno de dos valores, dependiendo de si la expresión booleana se evalúa como true o como false.  IIF es una manera abreviada para escribir una instrucción CASE.

·         Format: Devuelve un valor con formato con el formato y la referencia cultural opcional especificados.
Ejemplo:  SELECT FORMAT( GETDATE(), 'dd/MM/yyyy', 'en-US' ) AS Result'

Analysis Services



·         Integración de PowerPivot con Sharepoint

·         Sql Server Data Tools. Es el nombre del antiguo BIDS (Business Intelligence Developer Studio). Se integra con visual Studio 2010.

Integration Services
http://www.globbtv.com/12/microsite/1914/12-horas-sql-server-2012-novedades-de-integration-services-2012


·         Connection  Managers. Ahora puedes tener un entorno de desarrollo, otro de producción y otro de pruebas bajo un mismo paquete

·         Parámetros. Ahora los paquetes de Integration Services soportan parámetros, tanto a nivel de paquete como a nivel de proyecto.

·         Deshacer y rehacer


Data Quality Services
Se trata de una solución que permite repasar los datos de una base de datos, añadiendo reglas.
Por ejemplo, podemos crear una regla que diga que el apellido “Garcia” se renombre a “García” .


Este componente se integra como aplicación independiente y como componente de Integration Service, con lo que podemos “normalizar” los datos antes de transformarlos.
http://www.globbtv.com/12/microsite/1913/12-horas-sql-server-2012-data-quality-services

Reporting Services

·         Power View.  Un generador de informes para sharepoint mucho más fácil de usar que ReportBuilder, orientado a usuarios finales. Está basado en BISM Tabular. (modo tabular de Analysis Services)
http://todosobresql.blogspot.com/2012/01/power-view-en-sql-server-2012-primer.html


·         Data alerts. Avisos cuando un valor de un informe supere o no llegue a un rango



Como veis, hay bastantes cambios interesantes que os puede hacer la vida algo más sencilla (o más complicada J)

 Saludos.

viernes, 13 de enero de 2012

Pasando parámetros mutivalor de Reporting Services en procedimientos almacenados de MySQL

Configurar los parámetros con mútiples valores en SSRS no tiene mayor dificultad.
Se trata de elegir el parámetro que queremos pasar a multi valor, e indicarlo en un checkbox.

Lo que hace es pasar una lista de valores con los valores elegidos.
Si el acceso a datos lo hacemos a través de una consulta SQL, cambiaremos la consulta
“select * from usuarios where user_id=@PARAMETRO”
Por
“select * from usuarios where user_id in (@PARAMETRO)”


Hasta ahí, sencillo.
La complicación viene cuando lo que llamamos no es una consulta, sino un Procedimiento Almacenado. Y si el procedimiento Almacenado está en MySQL, las cosas se ponen más negras.


Parte 1: Modificaciones en el informe
Si tenemos un Procedimiento Almacenado con 3 parámetros (“A”,”B”,”C”), si el parámetro B viene con multiples valores, le pasará al procedimiento (“A”,”B1”,” B2”, “B3”, “C”), y dará error ya que el procedimiento espera 3 parámetros, no 5.

Lo que hay que hacer es al parámetro multivalor B es traducirlo a un único string separado por comas, es decir, pasar de “B1”,”B2”,”B3” a “B1,B2,B3”, con lo que los parámetros del procedimiento serían 3 (“A”,”B1,B2,B3”,C”)

Esto lo conseguimos configurando los parámetros de los conjuntos de datos,  y escribiendo una función personalizada




=JOIN(Parameters!Usuarios.Value,",")

Parte 2: Modificación en la base de datos


Sql Server nos habría facilitado la vida un montón ya que tenemos más recursos disponibles(funciones con retornos de tabla, funciones de cadena mucho más ricas, splits, etc...).

Con MySQL hay que dar bastante rodeo.


La única manera con la que he conseguido hacer funcionar esto ha sido a través de una tabla temporal, y con muchas pegas, ya que las tablas temporales no se pueden leer más de una vez. (Increible....)

Nos creamos un procedimiento almacenado en MySQL que nos meta en una tabla temporal los valores B1, B2, B3
CREATE DEFINER = 'root'@'%'
PROCEDURE _fn_StringCSVToTmpTable(texto VARCHAR(65535))
BEGIN
  declare pos int;
  declare elemento varchar(100);
  declare texto2 varchar(65535);

  DROP TEMPORARY TABLE IF EXISTS tmp_datos;
  CREATE TEMPORARY TABLE tmp_datos (campo1 INT NOT NULL) ENGINE = MEMORY;

  set pos=INSTR(texto,',');
  WHILE pos<>0 DO
    set elemento=substring(texto,1, pos-1);
    set texto2=substring(texto,pos+1);
    set texto=texto2; 
    insert into tmp_datos values (cast(trim(elemento) as unisgned));
    set pos=INSTR(texto,',');
  END WHILE;

  if texto<>"" then
    insert into tmp_datos values (cast(trim(elemento) as unisgned));
  end if;
END

Luego, este procedimiento almacenado es llamado desde el procedimiento principal.

CREATE DEFINER = 'root'@'%'
PROCEDURE PA2(IN p1 INT, IN texto varchar(200), IN p2 INT)
BEGIN
  call _fn_StringCSVToTmpTable(texto);
  select * from users where login in (select * from tmp_datos);
END

Cargamos la tabla temporal y hacemos la búsqueda.
Espero que os sea de utilidad.

martes, 9 de agosto de 2011

Funciones de categoría de SQL Server: rank, dense_rank, row_number, ntile

Voy a dar un repaso a unas funciones estadísticas que os pueden ser bastante útiles en vuestras queries.
Estas funciones están disponibles a partir del SQL Server 2005.

Nos basaremos en la base de datos AdventureWork y con los siguientes datos:

select FirstName, LastName, TerritoryGroup, SalesQuota FROM Sales.vSalesPerson where SalesQuota is not null




Función row_number()
Esta función nos mostrará un número correlativo según el orden que le especifiquemos.
El orden es obligatorio.
select row_number() over (order by salesquota desc) rowNumber, FirstName, LastName, TerritoryGroup, SalesQuota FROM Sales.vSalesPerson where salesquota is not null



Con esta función hemos conseguido un número de fila correlativo ordenado descendentemente por SalesQuota.

Función RANK()
Si lo que queremos es conocer la posición individual que ocupa (el ranking) en una lista , lo obtenemos de la siguiente forma:
select rank() over (order by salesquota desc) Rank, FirstName, LastName, TerritoryGroup, SalesQuota FROM Sales.vSalesPerson where salesquota is not null






Función DENSE_RANK()
Si lo que queremos es conocer la posición global que ocupa (el ranking) en una lista , lo obtenemos de la siguiente forma:
select dense_rank() over (order by salesquota desc) dense_rank, FirstName, LastName, TerritoryGroup, SalesQuota FROM Sales.vSalesPerson where salesquota is not null



Función NTILE()
Con esta función podemos dividir en grupos de N elementos un conjunto de filas.
select NTILE(4) over (order by salesquota desc) rowNumber, FirstName, LastName, TerritoryGroup, SalesQuota FROM Sales.vSalesPerson where salesquota is not null



En este ejemplo, el conjunto de filas lo hemos dividido en 4 grupos.
En el caso que el grupo no sea divisible, los primeros grupos tendrán más elementos que los últimos.
Es decir, como 14 filas no son divisibles entre 4,
el grupo 1 y 2 tiene 4 elementos, y el grupo 3 y 4 tiene 3 elementos.

Particionando los resultados
Si quisiéramos agrupar estos resultados deberíamos de usar el atributo PartitionBy.
select dense_rank() over (Partition by TerritoryGroup order by salesquota desc) rowNumber, FirstName, LastName, TerritoryGroup, SalesQuota FROM Sales.vSalesPerson where salesquota is not null



En este ejemplo, podemos ver el ranking de SalesQuota por territorio.
PartitionBy también se aplica al resto de funciones de categoría.



Otro ejemplo de Partition; esta vez mostraremos el rango que ocupa cada vendedor por territorio
select rank() over (Partition by TerritoryGroup order by salesYTD desc) rango , FirstName, LastName, TerritoryGroup, salesYTD FROM Sales.vSalesPerson




Por último, y a modo de resumen, muestro un resumen con todas las funciones de las que hemos hablado, para mostrar las diferencias.

select row_number() over (order by salesquota desc) rowNumber,  
rank() over (order by salesquota desc) rank,
dense_rank() over (order by salesquota desc) dense_rank,
NTILE(4) over (order by salesquota desc) ntile,
FirstName, LastName, TerritoryGroup, SalesQuota FROM Sales.vSalesPerson where salesquota is not null



Os dejo un enlace al MSDN donde podéis encontrar más información de estas funciones.
http://msdn.microsoft.com/es-es/library/ms189798.aspx


Saludos.










martes, 14 de junio de 2011

Bloqueos en producción

El otro día en la oficina empezamos a tener llamadas de gente que se le estaba quedando bloqueada la aplicación y que no les dejaba entrar.

Antes de darle botonazo al servidor, es conveniente saber la causa.

Buscando el proceso bloqueado
Tenemos que averiguar qué proceso es el causante de los bloqueos.

Para SQL versión 2000 o anterior:
comando "EXEC SP_LOCK" (para SQL anterior a versión 2005)
http://msdn.microsoft.com/en-us/library/ms187749.aspx
Según el MSDN, el comando SP_LOCK se discontinuará en versiones posteriores.

Columnas importantes:
  • SPID: Identificador de la sesión.
  • DBID: Identificador de la base de datos. Puedes usar DB_NAME(el número dbid) para saber el nombre de la base de datos.
  • STATUS:
    • WAIT: Procesos en espera por otros procesos
    • GRANT:Proceso bloqueado.

Para SQL Server versiones 2005 y posteriores
debemos usar "select * from sys.dm_tran_locks"
http://msdn.microsoft.com/en-us/library/ms190345.aspx

Columnas importantes:
  • resource_database_id: Identificador de la base de datos. Puedes usar DB_NAME(el número dbid) para saber el nombre de la base de datos.
  • request_session_id: Identificador de la sesión

  
Con estos comandos, podemos tener una visión de qué proceso es el que está causando el bloqueo.
Habrán procesos que están bloqueados por ptro proceso (Blocked by SPID xx), y también podremos ver que la mayor parte de los bloqueos los causa una sesión determinada

Nos anotamos el identificador de la sesión.
Localizando la sesión que bloquea

Comando "Exec SP_WHO2"
Este comando también nos sirve para ver qué usuarios están conectados actualmente al servidor SQL.
Localizamos la sesión que lanzó el proceso bloqueado, buscando en la columna SPID el identificador de la sesión que vimos anteriormente.

con SP_WHO, podemos ver:
  • SPID: Identificador de la sesión
  • LOGIN: Usuario de dominio que ejecuta la sesión
  • HOSTNAME: Equipo que ejecuta la sesión
  • DBNAME: Base de datos a la que está conectada la sesión
  • ProgramName: Programa que inicia la sesión
Una vez localizada la sesión que genera el bloqueo, debemos de cerrarla.
Lo primero que deberíamos de intentar es cerrar el proceso a través de la aplicación y sesión que está causando el problema.
Si no podemos conectarnos a esa máquina, el plan "b" sería matar el proceso con un "KILL XX", donde XX es el nº de sesión. 

Esto de matar los procesos alegremente tiene su peligro, ya que puedes dejar registros inconsistentes si la aplicación no dispone de procesos transaccionales.

 
Saludos.