Curso de C++ Builder


Acceso a bases de datos

TTable



El componente TTable facilita el acceso más simple y rápido a una tabla. Entre sus múltiples propiedades y métodos destacan:

Relaciones maestro/detalle

Para construir una relación maestro/detalle en C++ Builder basta con indicar el DataSource de la tabla maestro en la propiedad MasterSource de la tabla detalle y establecer la condición de la reunión (join) mediante la propiedad MasterFields. Además, hay que especificar IndexName o IndexFieldNames para establecer un orden en la tabla detalle (y ¡para que C++Builder lo haga eficientemente!).

Ejercicio

  • Crear un módulo de datos (DataModule) que contenga dos tablas (clientes y pedidos, por ejemplo) y sus correspondientes DataSources.

  • Establecer la propiedad MasterSource en la tabla detalle para que apunte al DataSource de la tabla maestra. Fijar las propiedades necesarias para especificar correctamente la relación maestro/detalle (vg: reunión CustNoÞCustNo usando como índice CustNo).

  • Crear un formulario que contenga dos componentes TDBGrid enlazados a las tablas del módulo de datos (a sus DataSources para ser más precisos). Para ello es necesario realizar el #include correspondiente.
  • Búsquedas

    Una necesidad bastante común en cualquier aplicación de bases de datos es la de seleccionar un conjunto de registros que cumplan una condición.

    Filtros

    Para realizar búsquedas se pueden utilizar tablas con filtros. Un filtro se puede construir usando la propiedad Filter (con FilterOptions) o el evento OnFilterRecord. La propiedad Filtered indica si el filtro está activo. Un conjunto de datos filtrado puede recorrerse usando los métodos FindFirst(), FindNext(), FindPrior() y FindLast().

    Medite antes de decidirse a utilizar OnFilterRecord. Este tipo de filtro se aplica en el cliente, lo cual implica que la aplicación debe bajarse a través de la red incluso los registros que no satisfacen el filtro.

    void __fastcall TFormX::Filtrar(TObject *Sender)
    {
      Set<TFieldType, ftUnknown, ftDataSet> TiposConComillas;
      AnsiString Operador, Valor;
      TiposConComillas < ftString < ftDate < ftTime < ftDateTime;
    
      Operador = Sender == miIgual ? "=" : Sender == miMayorIgual ? ">=" :
                 Sender == miMenorIgual ? "<=" : "<>";
    
      // Extraer el nombre del campo
    
      AnsiString Campo = DBGrid->SelectedField->FieldName;
    
      // Extraer y dar formato al valor seleccionado
    
      if (TiposConComillas.Contains(DBGrid->SelectedField->DataType))
         Valor = QuotedStr(DBGrid->SelectedField->AsString);
      else {
         Valor = DBGrid->SelectedField->AsString;
         for (int i = 1; i <= Valor.Length(); i++)
             if (Valor[i] == DecimalSeparator) Valor[i] = '.'; 
      }
    
      // Combinar la nueva condición con las anteriores
    
      if (Table->Filter == "")
        Table->Filter = Format("[%s] %s %s", ARRAYOFCONST((Campo, Operador, Valor)));
      else
        Table->Filter = Format("%s AND [%s] %s %s",
                               ARRAYOFCONST((Table1->Filter, Campo, Operador, Valor)));
    
      // Activar directamente el filtro
      miActivarFiltro->Checked = True;
      Table->Filtered = True;
      Table->Refresh();
    }
    
    void __fastcall TFormX::miActivarFiltroClick(TObject *Sender)
    {
      miActivarFiltro->Checked = ! miActivarFiltro->Checked;
      // Activar o desactivar en dependencia del estado de la opción del menú.
      Table->Filtered = miActivarFiltro->Checked;
      Table->Refresh();
    }
    
    void __fastcall TFormX::miEliminarFiltroClick(TObject *Sender)
    {
      miActivarFiltro->Checked = False;
      Table->Filtered = False;
      Table->Filter = "";
      Table->Refresh();
    }
    
    NB: Los filtros también son aplicables a componentes de tipo TQuery

    Índices

    Se pueden hacer búsquedas usando el índice activo de una tabla (establecido por IndexName o IndexFieldNames) usando los métodos FindKey y FindNearest.

    Alternativamente se pueden utilizan las combinaciones SetKey+GotoKey (en vez de FindKey) y SetKey+GotoNearest (sustituyendo a FindNearest)

    void __fastcall TFormDatos.ButtonClick(TObject *Sender)
    {
      Table->SetKey();
    
      // FormSearch => diálogo del tipo OK/Cancel con TDBEdits
    
      if (FormSearch->ShowModal() == mrOk)
         Table->GotoNearest();
      else
         Table->Cancel();
    }
    

    Rangos

    También se puede limitar el conjunto de registros de un conjunto de datos utilizando rangos mediante los métodos SetRange y CancelRange. Un rango es una restricción de las filas visibles de una tabla en la que se muestran sólo aquéllas tuplas en las cuales los valores de ciertas columnas se encuentran entre dos valores dados. Debe existir un índice sobre esas columnas.

    void __fasctcall TFormAgenda::TabControlChange(TObject *Sender)
    {
      AnsiString Letra;
    
      Letra = TabControl->Tabs->Strings[TabControl1->TabIndex];
    
      if (TabControl->TabIndex == 0)
         // Si es la pestaña con el asterisco mostramos todas las filas.
         Table->CancelRange();
      else
         // Activamos el rango correspondiente
         Table->SetRange(ARRAYOFCONST((Letra)),ARRAYOFCONST((Letra + "zzz")));
    
      // Actualizamos los controles asociados
      Table->Refresh();
    }
    

    Locate & Lookup

    Los métodos Locate y Lookup amplían las posibilidades de los métodos de búsqueda con índices. El primero intenta encontrar un registro con los valores deseados (usando índices si los hay); mientras que el segundo se utiliza para buscar el valor de una columna utilizando como clave una diferente (por ejemplo, para buscar el nombre de un empleado conociendo su código personal o, al revés, para encontrar el código de un empleado del que conocemos su nombre).

    AnsiString TDataModulePersonal::ObtenerNombre(int Codigo)
    {
      return VarToStr(tbClientes->Lookup("Código", Codigo, "Nombre"));
    }
    
    
    int TDataModulePersonal::ObtenerCodigo(const AnsiString Apellido, const AnsiString Nombre)
    {
      Variant V = tbEmpleados->Lookup( "Apellido;Nombre",
                                       VarArrayOf(ARRAYOFCONST((Apellido, Nombre))), "Código");
    
      if (VarIsNull(V))
         DatabaseError("Empleado no encontrado", 0);
    
      return V;
    }
    
    
    AnsiString TDataModulePersonal::NombreDeEmpleado(int Codigo)
    {
      Variant V = tbEmpleados->Lookup("Codigo", Codigo, "Nombre;Apellido");
    
      if (VarIsNull(V))
         DatabaseError("Empleado no encontrado", 0);
    
      return V.GetElement(0) + " " + V.GetElement(1);
    }
    

    Actualizaciones

    Actualización programada

    TableDetail->Edit();
    
    try {
        // Asignaciones a campos...
    
        TableDetailFecha->Value = Date(); 
          // == TableDetail->FieldByName("Fecha")->AsDateTime = Date();
          // == TableDetail->FieldValues["Fecha"] = Date();
    
    	TableDetailConcepto->Clear();
    
        TableDetail->Fields->Fields[x]->Assign(TableMaster->Fields->Fields[x]);
       
        // Post
    
        TableDetail->Post();
    
    } catch (Exception&) {
    
        TableDetail->Cancel();
        throw;
    }
    

    Inserción de datos

    AnsiString RandomString (int Longitud)
    {
      char*      Vocales = "AEIOU";
      char       LastChar = 'A';
      AnsiString Rslt;
    
      Rslt.SetLength(Longitud);
    
      for (int i = 1; i <= Longitud; i++) {
          LastChar = strchr("AEIOUNS", LastChar) ? random(26) + 'A' : Vocales[random(5)];
          Rslt[i] = LastChar;
      }
    
      return Rslt;
    }
    
    
    void CrearDatosSinteticos (TTable *Tabla, int registros)
    {
      int intentos = 3;
      int longitud = Tabla->FieldByName("Cadena")->Size;
    
      randomize();
    
      while (registros > 0)
            try {
    
                1) Tabla->Append();
                   Tabla->FieldValues["Cadena"] = RandomString(longitud);
                   Tabla->FieldValues["Entero"] = random(MAXINT);
    
                2) Tabla->AppendRecord( ARRAYOFCONST (
                                         RandomString(longitud),
                                         random(MAXINT) ) ));
    
                Tabla->Post();
    			
                // NOTA: Sería más eficiente si se agrupasen
                // distintas inserciones en una única transacción
    
                intentos = 3;
                registros--;
    
            } catch(Exception&) {
    
                intentos--;
    
                if (intentos == 0)
                   throw;
            }
    }
    

    Refresco de los datos

    El contenido actual de la fila activa se puede obtener llamando de forma consecutiva a Edit y Cancel (lo que es más eficiente que llamar a Refresh sobre el conjunto completo de datos)

    Table->Edit();
    Table->Cancel();
    


    Índice de la sección