martes, 16 de julio de 2013

Select * From Where Between

La selección de datos que están comprendidos en un rango de fechas es un tema muy mencionado en los Foros de Msdn tanto en C# como en Vb.Net.
A si que recordando lo complicado que pudiera ser esto para los que aun se están iniciando en el mundo de la programación, preparé un ejemplo de como utilizar el BETWEEN del Transact-SQL para obtener registros cuyo campo Fecha estén dentro de un rango definido, agregándole como Filtro adicional una tercer condicionante, es decir; filtraremos por fechas y por id.
Esta es la consulta que usaremos en el ejemplo, observen la manera en que se estructura la instrucción sql usando parámetros, recuerden que el uso de parámetros en las consultas Sql son esenciales para prevenir primero errores de sintaxis, segundo para prevenir ataques por inyección de código Sql o Sql Injection.
   1: //

   2: //Para seleccionar registros cuyo campo Fecha esta dentro del rango de Fechas

   3: //utilizamos la clausula Between en una consulta Parametrizada

   4: //

   5: const string sqlAction = @"SELECT Vt.FolioVenta, Vt.NumArticulo, Vt.Precio, Vt.FechaVenta, Vt.NumEmpleado, Em.NomEmpleado 

   6:                            FROM Ventas Vt 

   7:                            INNER JOIN Empleados Em ON Vt.NumEmpleado = Em.NumEmpleado 

   8:                            WHERE FechaVenta 

   9:                            BETWEEN @fechainicial AND @fechafinal"
;
Los prefijos para los parámetros para aquellos que aun no están familiarizados en su uso dependerá del motor de Bd que estemos usando, por ejemplo para este caso que se usa una Bd SQlCe o SqlCompact se usa el prefijo “@” al igual que en SQlServer, pero para motores como Access y MySql se usa “?”.

Descripción del ejemplo:

El ejemplo simula la selección de las ventas realizadas en un rango de fechas agregando como filtro adicional el numero de vendedor.

Imaginemos que necesitamos la ventas generales del 01/01/2013 al 01/07/2013, después queremos saber cuanto vendió el empleado no. 1 en ese rango de fechas, pues bien este ejemplo les servirá para adentrarnos en los filtros condicionales usando el Between de Sql.

SelectBetween

Creamos la función encargada de llenar un DataTable con los datos recuperados por la consulta parametrizada, que recibirá dos parámetros de entrada que son: fechaInicial y fechaFinal.
  1: /// <summary>
  2: /// Devuelve un conjunto de registros existentes dentro de un rango de Fechas
  3: /// </summary>
  4: /// <autor>Jose Luis Garcia Bautista</autor>
  5: /// <param name="fechaInicial">Fecha inicial del rango buscado</param>
  6: /// <param name="fechaFinal">Fecha final del rango buscado</param>
  7: /// <returns>DataTable con los registros encontrados</returns>
  8: /// <remarks></remarks>
  9: private DataTable GetSaleByDate(DateTime fechaInicial, DateTime fechaFinal)
 10: {
 11:     //
 12:     //Defininimos el nombre de nuesta cadena de conexion alojada en el App.config
 13:     //
 14:     using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()))
 15:     {
 16:         //
 17:         //Para seleccionar registros cuyo campo Fecha esta dentro del rango de Fechas
 18:         //utilizamos la clausula Between en una consulta Parametrizada
 19:         //
 20:         const string sqlAction = @"SELECT Vt.FolioVenta, Vt.NumArticulo, Vt.Precio, Vt.FechaVenta,     Vt.NumEmpleado, Em.NomEmpleado 
 21:                                    FROM Ventas Vt 
 22:                                    INNER JOIN Empleados Em ON Vt.NumEmpleado = Em.NumEmpleado 
 23:                                    WHERE FechaVenta 
 24:                                    BETWEEN @fechainicial AND @fechafinal
";
 25:         //
 26:         //Creamos el objeto Command, en este caso uso SQlCecommand porque el ejemplo lleva una Bd SqlCe
 27:         //
 28:         using (SqlCeCommand cmd = new SqlCeCommand(sqlAction, cnx))
 29:         {
 30:             //
 31:             //Creamos los parametros y asignamos valores
 32:             cmd.Parameters.AddWithValue("@fechainicial", fechaInicial);
 33: 
 34:             cmd.Parameters.AddWithValue("@fechafinal", fechaFinal);
 35: 
 36:             //Definimos el datatable que llenaremos con los datos recuperador
 37:             //
 38:             DataTable dt = new DataTable();
 39:             //
 40:             //creamos el objeto DataAdapter, este se encarga de abrir y cerrar la conexion
 41:             //
 42:             SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
 43:             //
 44:             //llenamos el objeto DataTable
 45:             //
 46:             da.Fill(dt);
 47:             //
 48:             //Devolvemos el datatable con los datos recuperados
 49:             //
 50:             return dt;
 51:             //
 52:             //Se cierra el Using destruyendo los objetos creados dentro 
 53:             //
 54:         }
 55:         //
 56:         //liberamos tambien la conexion a la bd
 57:         //
 58:     }
 59: 
 60: }
 
Sobre cargamos la función anterior con un parámetro adicional que corresponderá al Numero de Empleado, cambiando con esta sobrecarga de dos parámetros a tres, entonces nuestra función GetSaleByDate queda de la siguiente manera, ¡ojo! es una sobrecarga no una modificación de nuestra primera función, una sobre carga en palabras menos técnicas es una función o método con el mismo nombre pero diferente firma;
  1:         /// <summary>
  2:         /// Devuelve un conjunto de registros existentes dentro de un rango de Fechas pertenecientes al Empleado solicitado
  3:         /// </summary>
  4:         /// <autor>Jose Luis Garcia Bautista</autor>
  5:         /// <param name="fechaInicial">Fecha inicial del rango buscado</param>
  6:         /// <param name="fechaFinal">Fecha final del rango buscado</param>
  7:         /// <param name="numEmpleado">Numero del empleado consultado</param>
  8:         /// <returns>DataTable con los registros encontrados</returns>
  9:         /// <remarks></remarks>
 10:         private DataTable GetSaleByDate(DateTime fechaInicial, DateTime fechaFinal, int numEmpleado)
 11:         {
 12:             //
 13:             //Defininimos el nombre de nuesta cadena de conexion alojada en el App.config
 14:             //
 15:             using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()))
 16:             {
 17:                 //
 18:                 //Para seleccionar registros cuyo campo Fecha esta dentro del rango de Fechas
 19:                 //utilizamos la clausula Between en una consulta Parametrizada mas el Id del Empleado consultado
 20:                 //
 21:                 const string sqlAction = "SELECT Vt.FolioVenta, Vt.NumArticulo, Vt.Precio, Vt.FechaVenta, Vt.NumEmpleado, Em.NomEmpleado FROM Ventas Vt " +
 22:                                           "INNER JOIN Empleados Em ON Vt.NumEmpleado = Em.NumEmpleado " +
 23:                                           "WHERE FechaVenta BETWEEN @fechainicial AND @fechafinal " +
 24:                                           "AND Vt.NumEmpleado = @numempleado";
 25:                 //
 26:                 //Creamos el objeto Command, en este caso uso SQlCecommand porque el ejemplo lleva una Bd SqlCe
 27:                 //
 28:                 using (SqlCeCommand cmd = new SqlCeCommand(sqlAction, cnx))
 29:                 {
 30:                     //
 31:                     //Creamos los parametros y asignamos valores
 32:                     cmd.Parameters.AddWithValue("@fechainicial", fechaInicial);
 33:                     cmd.Parameters.AddWithValue("@fechafinal", fechaFinal);
 34:                     cmd.Parameters.AddWithValue("@numempleado", numEmpleado);
 35:                     //Definimos el datatable que llenaremos con los datos recuperador
 36:                     //
 37:                     DataTable dt = new DataTable();
 38:                     //
 39:                     //creamos el objeto DataAdapter, este se encarga de abrir y cerrar la conexion
 40:                     //
 41:                     SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
 42:                     //
 43:                     //llenamos el objeto DataTable
 44:                     //
 45:                     da.Fill(dt);
 46:                     //
 47:                     //Devolvemos el datatable con los datos recuperados
 48:                     //
 49:                     return dt;
 50:                 }
 51:                 //
 52:                 //liberamos tambien la conexion a la bd
 53:                 //
 54:             }
 55: 
 56:         }

Como en el ejemplo necesitamos tener la lista de todos los Empleados cargados en un control ComboBox, creamos una función que retornara un DataTable con todos los empleados existentes en la Bd misma que se usara con DataSource del ComboBox:
1: /// <summary>
  2:         /// Funcion que retorna un objeto DataTable con el contenido de la tabla Empleados
  3:         /// </summary>
  4:         /// <autor>Jose Luis Garcia Bautista</autor>
  5:         /// <returns>DataTable con los registros encontrados</returns>
  6:         /// <remarks></remarks>
  7:         private DataTable GetAllEmployee()
  8:         {
  9: 
 10:             using (SqlCeConnection cnx = new SqlCeConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()))
 11:             {
 12:                 const string sqlAction = "SELECT * FROM Empleados";
 13: 
 14:                 using (SqlCeCommand cmd = new SqlCeCommand(sqlAction, cnx))
 15:                 {
 16: 
 17:                     DataTable dt = new DataTable();
 18: 
 19:                     SqlCeDataAdapter da = new SqlCeDataAdapter(cmd);
 20:                     da.Fill(dt);
 21: 
 22:                     return dt;
 23:                 }
 24:             }
 25: 
 26:         }

Para cargar el ComboBox con la lista de Empleados, usamos el evento Load del Form, observen como se establecen los valores a mostrar y el valor miembro de este a las propiedades DisplayMember y ValueMember respectivamente.
  1:         private void Form1_Load(object sender, EventArgs e)
  2:         {
  3:             try
  4:             {
  5:                 //
  6:                 //Llenamos el combobox con la lista de los empleados, para eso usamos la funcion GetAllEmployee
  7:                 //
  8:                 ComboBox1.DataSource = GetAllEmployee();
  9:                 //
 10:                 //Indicamos que valor es el que se va a mostrar al usuario
 11:                 ComboBox1.DisplayMember = "NomEmpleado";
 12:                 //
 13:                 //Indicamos el valor que esta ligado al valor desplegado
 14:                 ComboBox1.ValueMember = "NumEmpleado";
 15:             }
 16:             catch (Exception ex)
 17:             {
 18:                 MessageBox.Show(ex.Message);
 19:             }
 20:         }
Nuestro formulario debe de cargarse con los datos de los Empleados.

LoadComboBox

Ahora usemos la función GetSaleByDate() ello en el Evento Click de un botón mandamos a llamar a la función enviando los parámetros fechaInicial y fechaFinal los valores parar estos parámetros los tomaremos de los controles DateTimePicker:

fechaInicial = DateTimePicker1
fechaFinal = DateTimePicker2

Recordemos que tenemos esta función sobrecargada y solo estaremos usando una sobrecarga (la que acepta dos parámetros de entrada), también observen como nosotros no necesitamos hacer nada mas que enviar los parámetros que tenemos y automáticamente en tiempo de ejecución se resuelve que Función se ejecutara, es pocas palabras, no tenemos que preocuparnos por indicarle al CLR que función ejecutara ya que este lo resuelve en relación a la cantidad de parámetros enviados.
  1:         private void Button1_Click(object sender, EventArgs e)
  2:         {
  3:             try
  4:             {
  5:                 //El evento Click del boton unicamente cargara las ventas realizadas en el rango de fechas seleccionado, sin filtrar por empleado
  6:                 //
  7:                 //Par esto usamos al funcion sobrecargada GetSaleByDate
  8:                 //
  9:                 //Observa como unicamente se envia el valor de la fecha sin la hora usando la Propiedad Date del control DateTimePicker
 10:                 DataGridView1.DataSource = GetSaleByDate(DateTimePicker1.Value.Date, DateTimePicker2.Value.Date);
 11:                 //
 12:                 //Atrapamos cualquier Excepcion que pudiera con el uso de Try...Catch...End Try
 13:                 //
 14:             }
 15:             catch (Exception ex)
 16:             {
 17:                 //
 18:                 //Mostramos el mensaje de error controlado por el bloque Catch
 19:                 MessageBox.Show(ex.Message);
 20:             }
 21:         }

1

Nota: En esta imagen se muestra el nombre del vendedor LIN porque es el primero que se carga al ComboBox pero aun no se utiliza para aplicar el filtro

Ahora para seleccionar solo las ventas de un vendedor en ese mismo rango de fechas configuramos el Evento SelectionChangeCommitted del control ComboBox para lanzar la búsqueda por Rango de Fechas y Numero de empleado solo cuando se cambie el valor seleccionado en el control Combobox; recordemos que el SelectionChangeCommitted únicamente se activa cuando el usuario cambia de item manualmente, este evento no se activa por código como así lo hace el SelectedIndexChanged para mas información:

SelectionChangeCommitted
SelectedIndexChanged

Entonces usamos el SelectionChangeCommitted para llamar a la función sobrecargada GetSaleByDate() enviándole tres parámetros, fechaInicial, fechaFinal y numEmpleado


  1:         private void ComboBox1_SelectionChangeCommitted(object sender, EventArgs e)
  2:         {
  3:             try
  4:             {
  5:                 DataGridView1.DataSource = GetSaleByDate(DateTimePicker1.Value.Date, DateTimePicker2.Value.Date, Convert.ToInt32(ComboBox1.SelectedValue));
  6: 
  7:             }
  8:             catch (Exception ex)
  9:             {
 10:                 MessageBox.Show(ex.Message);
 11:             }
 12:         }

2

Obsérvese que tanto en el Evento Click del control Button1 y el SeleccionChangeCommmited del control ComboBox1, únicamente enviamos el valor de la Fecha de los controles DTP, en consultas condicionales en donde únicamente se requiere de un filtro por fecha (“SELECT * FROM Tabla WHERE CampoFecha = @fecha”) este punto es muy importante y les funcionara solo si no tienen el valor de la hora junto con la fecha en su campo de la Bd por que…

No es lo mismo esto 2013-07-07 00:00:00.000 que esto 2013-07-07 18:30:25.156

En el primer caso (que es el caso de nuestro ejemplo) con solo tomar el Date del DateTimePicker bastara:

   1: GetSaleByDate(DateTimePicker1.Value.Date, DateTimePicker2.Value.Date);

pero no será tan sencillo para el segundo caso ya que para que se encuentren resultados se deberá de enviar el valor a buscar tal cual como esta en la Bd, es decir; Fecha, horas, minutos, segundos, milisegundos; como ya se habrán imaginado seleccionar un tiempo tan preciso desde un control manualmente es casi imposible, entonces para solucionar este problema tenemos dos opciones.

1) Convertir el valor de FechaHora a un formato de Fecha Corta  en la consulta sql
o

2) Enviar el valor de Fecha hora, minutos, segundos desde la aplicación tal cual como lo tienen almacenados en su campo.

Como recomendación si no necesitan el valor de la hora en el campo no lo envíen!, esto es muy sencillo ya que únicamente deberán de tomar el valor de la fecha del control DTP usando el Date como ya se mostro arriba DateTimePicker1.Value.Date.

Si tienen alguna duda tienen la sección de comentarios para hacérmela llegar.

Saludos desde Monterrey, Nuevo León México!


Aquí les dejo el proyecto de ejemplo desarrollado en C# y la versión en Vb.Net.

Ejemplo en C# :
Ejemplo en Vb.Net

Nota: El proyecto fue desarrollado en Vs2010 usando una Bd SqlCompact V3.5 y Framework 4.0

1 comentario:

  1. Hola Jose Luis, muy util, mil gracias. He intentado acceder al ejemplo en VB.NET pero no esta disponible, me lo puedes facilitar? me seria de gran ayuda. Gracias

    ResponderEliminar

Deja un comentario si el articulo fue de utilidad.