Agrupando sumas mediante particiones en SQL

Cuando el cliente te pide una funcionalidad relacionada con la generación de informes, sabes que vas a tener que enfrentarte a un concepto que, a menudo, provoca verdaderos dolores de cabeza cuando se trata de realizar consultas SQL: los valores agrupados.

No entraremos en detalle en el concepto de agrupación, ya que cualquier manual de SQL le dedica secciones enteras a este concepto. Trataremos un caso particular de agrupación: la agrupación de sumas.

Read the rest of this post »

Ignorar mayúsculas y tildes en ORACLE

¿A quién no le ha pasado esto?: Cuando la aplicación está casi terminada y la entrega está próxima, el tester se da cuenta de un pequeño (y básico) detalle que no habíamos tenido en cuenta: los formularios de búsqueda de nuestra aplicación deben ignorar mayúsculas y minúsculas y, por si fuera poco, también deben ignorar tildes.

La primera parte tiene una solución relativamente sencilla: ORACLE proporciona las funciones upper() y lower(), que devuelven una cadena de texto convertida a mayúsculas y a minúsculas respectivamente. Así, para crear un filtro en una sentencia SELECT que ignore mayúsculas y minúsculas (es decir, que sea CASE-INSENSITIVE), bastaría con lo siguiente (según queramos búsqueda exacta o aproximada):


select * from USUARIOS where upper(APELLIDOS) = upper('López')
select * from USUARIOS where upper(APELLIDOS) like '%' || upper('López') || '%'

Ahora nos queda la segunda parte: podemos ignorar las letras con tilde mediante la funcion translate(). Dicha funcion recibe tres parametros: una cadena de entrada sobre la que se quiere operar, un patrón con los caracteres que se quieren sustituir y otro patrón con los caracteres que sustituirán a los incluidos en el parámetro anterior. Así, la siguiente llamada a la función:


translate('Cadena 112234', '1234', 'abcd');

Devolverá el resultado ‘Cadena aabbcd’, al realizar la sustitución 1->a, 2->b, 3->c, 4->d. Si aplicamos la misma teoría a nuestras búsquedas, para no tener en cuenta ningún tipo de diéresis ni tilde, sustituiríamos las consultas anteriores por las siguientes:


select * from USUARIOS
where translate(upper(APELLIDOS), 'ÁÉÍÓÚÄËÏÖÜÀÈÌÒÙÂÊÎÔÛ', 'AEIOUAEIOUAEIOUAEIOU') = translate(upper('López'), 'ÁÉÍÓÚÄËÏÖÜÀÈÌÒÙÂÊÎÔÛ', 'AEIOUAEIOUAEIOUAEIOU')
select * from USUARIOS
where translate(upper(APELLIDOS), 'ÁÉÍÓÚÄËÏÖÜÀÈÌÒÙÂÊÎÔÛ', 'AEIOUAEIOUAEIOUAEIOU') like translate(upper('%López%'), 'ÁÉÍÓÚÄËÏÖÜÀÈÌÒÙÂÊÎÔÛ', 'AEIOUAEIOUAEIOUAEIOU')

Hecho esto, no tendremos que preocuparnos más ni de mayúsculas ni de tildes :D

Comprobar la existencia de una tabla en SQL Server

Cuando tratamos con un procedimiento almacenado en el que utilizamos tablas temporales, es posible que un fallo en la consulta arruine el flujo normal del programa, haciendo que la eliminación de la propia tabla temporal no se realice.

Este caso en concreto puede provocar un error en la próxima ejecución del procedimiento, ya que la creación de la tabla temporal provocará un fallo si esta ya existe.
Para intentar subsanar esta posibilidad, podemos comprobar, antes de crear la tabla temporal, si esta ya existe. Para ello haremos lo siguiente:

IF OBJECT_ID('tempdb..#UsuarioTemp') IS NOT NULL
BEGIN
DROP TABLE #UsuarioTemp;
END

Esto comprobará la existencia de la tabla temporal, y si existe, la eliminará. Posteriormente podremos incluir el código de creación de la tabla.

El el caso de que queramos comprobar la existencia de una tabla real (no temporal) de la base de datos, utilizaremos el siguiente código T-SQL:

IF OBJECT_ID('USUARIO') IS NOT NULL
BEGIN
SELECT 'SELECT * FROM USUARIO';
END
ELSE
BEGIN
CREATE TABLE USUARIO(
[IdUsuario] [int],
[Nombre] [varchar](100),
[Apellido1][varchar](100),
[Apellido2][varchar](100),
[Login][varchar](100),
[Password][varchar](100),
)
END

Con lo cual se realizará una consulta sobre la tabla si ésta existe, o la creará en caso contrario. Queda a voluntad del programador las posibilidades de la existencia o no de las tablas.

Tratamiento de cadenas en SQL Server

En cualquier lenguaje de programación es tarea obligada poseer una manipulación de cadenas fluida.

Transact SQL proporciona una variedad bastante amplia de funciones para llevar a cabo tal tarea. Algunas de las operaciones que podemos realizar son concatenación, obtención de subcadenas, pasar a mayúsculas o minúsculas, eliminar espacios en blanco… incluso detectar la similitud entre expresiones.

A continuación veremos algunas de ellos y aplicaremos algunos ejemplos:

Read the rest of this post »

Eliminar la hora a una fecha en SQL Server 2005

Hay veces que pese a recibir un parámetro en formato fecha que almacene dia y hora, nos interese únicamente almacenar (o recuperar) la parte correspondiente a la fecha.
Aquellos que hayan realizado alguna vez comparaciones de fechas situadas en un intervalo probablemente sepan de qué hablo: ¿Comparar entre un día a las 00:00:00 y el siguiente a las 00:00:00? ¿Entre el mismo día entre las 00:00:00 y las 23:59:59?

Una forma simple de ahorrarnos dolores de cabeza sería almacenar la fecha sin hora. Para ello haremos uso de los distintos formatos de fecha que nos permite promocionar SQL Server y escribiríamos algo así:

 UPDATE NombreTabla SET CampoFecha = convert(datetime, convert(varchar(10), CampoFecha, 103))  

Con lo cual, almacenaremos nuestra fecha en formato DD:MM:AAAA 00:00:00.

Realizando una transacción en SQL Server

Por norma general, me gusta codificar la lógica de acceso a datos en programa, en lugar de en procedimientos almacenados en base de datos. Esta aproximación no es muy óptima (como se suele decir, al César lo que es del César y a Dios lo que es de Dios), ya que el entorno transaccional estará mejor gestionado por el SGBD que por el código .NET.
Si queremos trasladar la responsabilidad de gestionar la transacción al SGBD, deberemos saber en primer lugar que se declara del siguiente modo:

BEGIN TRAN
 -- Ejecución de sentencias
COMMIT TRAN

Como imaginaremos, la primera sentencia “arranca” la transacción, mientras que la última la compromete. Pero ¿y si ocurre un error en alguna de las sentencias? ¿Cómo lo detectamos?
En el siguiente ejemplo, utilizaremos variables enteras para trazar los fallos y realizar un ROLLBACK en el caso de que la transacción falle.

Se declarará una variable de tipo INT por cada sentencia a ejecutar, y se inician a 0. Se comprueba, después de cada sentencia ejecutada, la variable @@Error, y se almacena en las variables anteriormente creadas. Si es distinta de 0, hay un error, por lo que se añade información al mensaje de error.
Al final de la transacción, si todas las variables son 0, se hace el COMMIT. Si una de ellas es distinta de cero, significará que existe un error y se hace el Rollback, mostrando el mensaje de error.


DECLARE @CadenaError varchar(1000);
 DECLARE @CodigosError varchar(1000);
 DECLARE @ErrorSentencia1 int;
 DECLARE @ErrorSentencia2 int;
 DECLARE @ErrorSentencia3 int;

 SET @ErrorSentencia1 = 0;
 SET @ErrorSentencia2 = 0;
 SET @ErrorSentencia3 = 0;

 SET @CadenaError = 'Error al realizar actualización: ';
 SET @CodigosError = 'Códigos de error: ';

 -- Inicio de la transacción
 BEGIN TRAN

 -- Primera actualización
 UPDATE Usuario SET Nombre = 'Elena' WHERE Nombre = "Helena";

 -- Almacenamos el posible código de error
 SET @ErrorSentencia1 = @@Error;
 IF (@ErrorSentencia1 <> 0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 1; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia1 AS VARCHAR(10)) + '; ';
 END

 -- Segunda actualización
 UPDATE Perfil SET Nombre = 'Administrador' WHERE Nombre = "Admin";

 -- Almacenamos el posible código de error
 SET @ErrorSentencia2 = @@Error;
 IF (@ErrorSentencia2 <> 0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 2; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia2 AS VARCHAR(10)) + '; ';
 END

 -- Tercera actualización
 UPDATE UsuarioPerfil SET CodigoUsuario = 1 WHERE CodigoPerfil < 3;

 -- Almacenamos el posible código de error
 SET @ErrorSentencia3 = @@Error;
 IF (@ErrorSentencia3 <> 0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 3; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia3 AS VARCHAR(10)) + '; ';
 END

 -- En ausencia de errores, comprometemos la transacción
 IF (@ErrorSentencia1 = 0) AND (@ErrorSentencia2 = 0) AND (@ErrorSentencia3 = 0)

BEGIN
     COMMIT TRAN;
     PRINT('Transacción realizada con éxito');
 END
 ELSE
 BEGIN
     ROLLBACK TRAN;
     PRINT('Transacción abortada');
     PRINT(@CadenaError);
     PRINT(@CodigosError);
 END

Con todo esto, obtendremos suficiente información (esperemos) como para detectar donde está el fallo.

Abstracción de datos (II): Construyendo un DAO simple en C#

Como introducción a la aplicación práctica de este patrón que ya vimos previamente, daremos las indicaciones necesarias para construir un DAO simple para una entidad en concreto.

El ejemplo que mostraremos ahora se tratará de un elemento macizo y poco funcional, que posteriormente iremos retocando y refinando para alcanzar un nivel de abstracción lo suficientemente elevado como para exportarlo a un componente plenamente reutilizable.

Read the rest of this post »