El componente TTable facilita el acceso más simple y rápido a una tabla. Entre sus múltiples propiedades y métodos destacan:
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 |
---|
|
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.
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(); } |
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(); } |
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(); } |
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); } |
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; }
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; } }
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();