Un caso de aplicacion de bases de datos via web

Un caso de aplicacion de bases de datos via web

Jorge Llarens

Este texto es un intento de mostrar otra forma de basar una aplicacion, conocida como server-side. La mas llamativa ventaja de esta forma de trabajar es que la mayor carga de requerimientos esta en el server, donde residen los datos y donde realmente se ejecuta el sistema. En las terminales de acceso, solo se necesita lo que se conoce como thin client, que incluye como minimo una coneccion tcp/ip y un navegador. Por tanto, si de migrar se trata, lo unico que es necesario agregar (en caso de que falte) son estas dos caracteristicas, no importando el sistema operativo en que esten basadas las terminales (tratandose de windows o linux, estas caracteristicas ya se encuentran cubiertas, si hablamos de DOS, no es dificil encontrar soporte para tcp y navegadores basados en texto o graficos).


El caso

La aplicacion fue inicialmente una planilla de calculo, donde, en una sola hoja constaban los datos individuales y las materias asignadas a los profesores de una institucion educativa.

NroNombreAsignaturaHsCursoDivSitTur
1OJEDA , LauraInglés33ro5taInterinT
  Inglés33ro7maSuplenteT
  Total 6    T
2Fernández LugoLengua Extranjera31ro8vaInterinT
  Total 3    T
3ALFONZO, GuillInglés33ro1raTitularT
  Inglés34toC4InterinT
  Inglés34toC1InterinT
  Inglés35toC1InterinT
  Inglés35toC3InterinT
  Inglés34toC3InterinT
  Total 18    T

Una primera instancia, antes de migrar la informacion es diseñar la base de datos donde se destinara la informacion de la planilla. Este paso es independiente de la forma en que se implemente finalmente la aplicación. Asimismo pueden utilizarse herramientas de definicion de bases de datos o hacerlo en forma manual, con un simple editor de textos.


Un poco de teoria

Para migrar la informacion de la planilla de calculo a una base de datos, deben diseñarse los equivalentes de la informacion en base a los elementos basicos de una base de datos: tablas y relaciones. Existe una metodologia sistematica para hacer esto conocida como normalizacion de bases de datos, basada en una especificacion (bastante vieja, de finales de los 60, pero EFICIENTE) conocida como formas normales. Si ya han oido hablar de las formas normales, probablemente sabran que una de sus ventajas es reducir la redundancia de datos, lo cual NO quiere decir que reduzca el TAMAÑO de la base de datos. En realidad, tal vez una de las ventajas mas importantes de la normalizacion sea evitar las "anomalias de almacenamiento" (y esa es la genesis del termino "normalizar")


Un poco de teoria y practica

Las formas normales de una base de datos son 3 y se aplican progresivamente:

1a forma normal (1FN desde ahora): Una relacion esta en 1FN si todos los campos en cada registro tienen un solo valor. Esto basicamente significa que la informacion debe aparecer como una tabla plana, con cada casillero ocupado.


Miremos la planilla original

En primer lugar olvidemonos del dato TOTAL, que es un dato calculado a partir del campo horas.

NroNombreAsignaturaHsCursoDivSitTur
1OJEDA , LauraInglés33ro5taInterinT
  Inglés33ro7maSuplenteT
2Fernández LugoLengua Extranjera31ro8vaInterinT
3ALFONZO, GuillInglés33ro1raTitularT
  Inglés34toC4InterinT
  Inglés34toC1InterinT
  Inglés35toC1InterinT
  Inglés35toC3InterinT
  Inglés34toC3InterinT

Esto funciona perfectamente para una persona en una planilla de calculo, pero hay "huecos" en cada columna, lo que no cumple con la 1FN, la solucion es rellenar los huecos con los valores de las filas superiores.

NroNombreAsignaturaHsCursoDivSitTur
1OJEDA , LauraInglés33ro5taInterinT
2OJEDA , LauraInglés33ro7maSuplenteT
2Fernández LugoLengua Extranjera31ro8vaInterinT
3ALFONZO, GuillInglés33ro1raTitularT
3ALFONZO, GuillInglés34toC4InterinT
3ALFONZO, GuillInglés34toC1InterinT
3ALFONZO, GuillInglés35toC1InterinT
3ALFONZO, GuillInglés35toC3InterinT
3ALFONZO, GuillInglés34toC3InterinT

(que facil)

Cual es el problema con esta forma de guardar las cosas?: Lo que anteriormente mencionamos como anomalias de almacenamiento:

  • Si se decidiese cambiar el nombre de la materia "Ingles" a "Lengua extranjera: ingles" deberian cambiarse todas las instancias donde aparezca, situacion que es propensa a errores (anomalia de modificacion).

  • De haber necesidad de agregar la materia "Educacion cívica" antes de tener datos de profesor o cursos, no se podria agregar sin dejar de cumplir la 1FN (anomalia de insercion).

  • Si se decidiera eliminar al profesor Fermández Lugo, desaparecerian la materia lengua extranjera y los datos del curso que queda vacante (anomalia de eliminacion).

Para solucionar estas anomalias debemos pasar la relacion a 2a forma normal (de ahora en mas 2FN) e introducir algunos conceptos mas:

  • Clave primaria: Es algun valor, dentro del registro que identifica de forma unica al mismo. Notese el campo nro es único para cada nombre de profesor, se puede decir que es una clave primaria para los datos individuales del profesor,(atencion, NO es clave primaria para el registro, solo para el profesor).

  • Dependencia funcional: Se dice que los datos de un registro dependen de una clave, si esta sirve para identificar los datos del mismo.

  • Dependencia funcional completa: Si TODOS los datos del registro dependen funcionalmente de la clave.

2a forma normal: Una relacion esta en 2FN si es 1FN y cada atributo no-clave es total y funcionalmente dependiente de su clave primaria.

Si miramos en la planilla de profesores, veremos algunas claves que podrian servir: nro es clave que sirve para identificar al profesor, curso Div y Turno identifican al curso, pero NINGUNA identifica a cada fila de datos en si. La solucion es AGREGAR una clave para cada registro. En este caso, lo mas sencillo es una secuencia consecutiva:

asigs
clavenronombreasignaturahscursodivsittur
11OJEDA , LauraInglés33ro5taInterinT
22OJEDA , LauraInglés33ro7maSuplenteT
32Fernández LugoLengua Extranjera31ro8vaInterinT
43ALFONZO, GuillInglés33ro1raTitularT
53ALFONZO, GuillInglés34toC4InterinT
63ALFONZO, GuillInglés34toC1InterinT
73ALFONZO, GuillInglés35toC1InterinT
83ALFONZO, GuillInglés35toC3InterinT
93ALFONZO, GuillInglés34toC3InterinT

Con esto tenenos una clave única para todo el registro pero no se solucionan las anomalias de almacenamiento, que se deben a que no todos los valores dependen de la clave primaria que hemos creado. La regla general a seguir es tomar las otras claves y los valores que dependen de ellas y separarlas en otras tablas.

Este es el caso de nro/nombre, asignatura y situacion, para las cuales crearemos una clave, nro para la nueva tabla profes, codasi para asignaturas y codsit para situacion,

asigs
clavenrocodasihorascursodivsituacionturno
11133ro5ta1T
21133ro7ma2T
32231ro8va1T
43133ro1ra3T
53134toC41T
63134toC11T
73135toC11T
83135toC31T
93134toC31T

Profes
Nronombre
1OJEDA , Laura
2Fernández Lugo
3ALFONZO, Guill.

asignaturas
codasiasignatura
1Inglés
2Lengua Extranjera

situacion
codsitsituacion
1Interino
2Suplente
3Titular

La 2FN tambien puede tener anomalias, si los datos no-clave dependen transitivamente de otros valores no claves, lo cual no es el caso en esta base de datos, es tema para ejemplos mas complejos. Por lo tanto nuestra base de datos ya esta en 3ra forma normal.

3a forma normal (3FN de ahora en mas): una relacion es 3FN si es 2FN y ningun dato no-clave es funcionalmente dependiende de otro dato no-clave

Existe una regla practica que sirve en la mayoria de los casos: alla donde se vean columnas con datos de textos que se repiten con frecuencia, tomar este dato, la clave que lo identifica (en caso de que no tenga clave se crea ese dato) y trasladarlos a una tabla separada, manteniendo solo la clave en la tabla de donde provino. Esto fue lo que se hizo con los dato nro/nombre,asignatura y situacion.


Manos a la obra

Bueno, ya tenemos un diseño, es hora de meter todo eso en una base de datos MySQL.


Donde residiran nuestros documentos

Antes de empezar a trabajar sobre las cosas, un comentario acerca de usos y costumbres. Yo estoy poco habituado al uso de diferentes usuarios y a la administracion de seguridad, asi que por mucho tiempo simplemente entraba con el unico usuario que sabia que se creaba inmediatamente despues de la instalacion: ROOT. MAL HECHO! El usuario ROOT tiene, por asi decirlo, permiso para matar, puede hacer LO QUE SEA con el sistema, bueno o MALO, asi que si no se creo su propio usuario durante la instalacion, lo crearemos aqui mismo:

adduser programador (y enter)
passwd programador (y enter, luego debemos tipear la clave)
   

Esto hecho en modo texto, o en una consola X. Para empezar a trabajar con el usuario programador, nos desconectamos del usuario root, tecleando:

logout (y enter)
   

(Esto exclusivamente en modo texto, en modo grafico, terminar la sesion depende de la distribucion y entorno grafico que se este usando).

Esto nos devuelve al 'Login' del principio, donde ingresamos el usuario programador y la clave que acabamos de crear.

Y a partir de ahora, existe un directorio personal donde residiran nuestros documentos: /home/programador


Cual base de datos?

Bueno, aqui llegamos a la primera decision importante. Seguramente muchos conocen la disyuntiva que se presenta en estos dias: MySQL vs PostgreSQL, las bases de datos libres mas conocidas. Por lo poco que he leido y probado, las conclusiones son estas: PostgreSQL es la mas indicada para grandes bases de datos de mision critica, y MySQL es la mas rapida y sencilla de implementar. Y como MySQL es mas sencilla, la utilizare para este caso.

Las intrucciones para crear la base de datos y las tablas se transcribiran a un archivo ASCII, llamese profes.sql:

CREATE TABLE asimed
     (
     clave                INT NOT NULL AUTO_INCREMENT,
     nroorden             INT not null ,
     nroasig              INT not null ,
     horas                TINYINT UNSIGNED not null ,
     curso                CHAR(10),
     div                  CHAR(10),
     codsit               INT not null ,
     turno                CHAR(10),
     decreto              CHAR(30),
     alta                 DATETIME,
     plaza                INT not null ,
     KEY (codsit),
     KEY (nroasig),
     KEY (nroorden),
     PRIMARY KEY (clave)
     );
  
CREATE TABLE asipol
     (
     clave                INT NOT NULL AUTO_INCREMENT,
     nroorden             INT not null ,
     nroasig              INT not null ,
     horas                TINYINT UNSIGNED not null ,
     curso                CHAR(10),
     div                  CHAR(10),
     codsit               INT not null ,
     turno                CHAR(10),
     alta                 DATETIME,
     plaza                INT not null ,
     KEY (codsit),
     KEY (nroasig),
     KEY (nroorden),
     KEY (clave),
     PRIMARY KEY (clave)
     );
 
CREATE TABLE asignaturas
     (
     nroasig              INT NOT NULL AUTO_INCREMENT,
     asignatura           CHAR(60),
     KEY (asignatura),
     PRIMARY KEY (nroasig)
     );
 
CREATE TABLE profesores
     (
     nroorden             INT NOT NULL AUTO_INCREMENT,
     nomprof              CHAR(50),
     dni                  INT not null ,
     codtit               INT not null ,
     lem                  SMALLINT not null ,
     KEY (codtit),
     PRIMARY KEY (nroorden)
     );
 
CREATE TABLE situacion
     (
     codsit               INT NOT NULL AUTO_INCREMENT,
     situacion            CHAR(10),
     PRIMARY KEY (codsit)
     );
CREATE TABLE titulos
     (
     nrotit               INT NOT NULL AUTO_INCREMENT,
     titulo               CHAR(50),
     PRIMARY KEY (nrotit)
     );
   

Para crear las tablas en base a profes.sql se necesita la aplicacion de terminal "mysql" como sigue:

mysql < profes.sql
   

Lo ejecutara y terminara sin mas. Para verificar si existen la base de datos y las tablas creadas se ejecuta:

mysql profes
   

el prompt quedara asi:

mysql>
   

Tecleando: show tables; (y enter), deberia aparecer un listado de este tipo:

+------------------+
| Tables_in_profes |
+------------------+
|asignaturas       |
|asimed            |
|profesores        |
|situacion         |
|titulos           |
+------------------+
   

y despues teclear: quit (y enter) para volver a la linea de comandos.


La migracion de la informacion original

Aqui tenemos un paso que es clasico y rutinario en cualquier ambiente de trabajo: pasar informacion de un formato a otro, Y NO NECESARIAMENTE UNA VEZ, podria ser necesario que este paso requiera de algun tipo de rutina automatica, para incorporar nuevos datos a medida que se presentan. En este caso en particular, asumiremos que una vez migrados los datos, se usaran pura y exclusivamente desde mysql, asi que solo nos dedicaremos a convertir la planilla de calculo una sola vez. En primer lugar, se pasara a un formato de importacion reconocible por practicamente cualquier sistema: campos delimitados por tabulaciones y registros delimitados por corte de linea. Simplemente se trata de grabar desde excel, la planilla en formato "texto (delimitado por tabulaciones)", con el nombre (por ejemplo) poli_ascii.txt.

Esta claro que con este archivo no podemos hacer una importacion directa, ya que la informacion original no esta normalizada. Se requiere un algoritmo que lea la informacion registro por registro (o linea por linea) y vaya acomodando la informacion en las tablas que corresponda. El script i2.php es el encargado de la migracion. Se ejecuta tecleando:

php -f i2.php
   

Para verificar si la migracion no tuvo problemas entremos otra vez a la consola de sql:

mysql profes (y enter)
   

dentro de la consola:

select * from titulos; (y enter)
   

y tiene que aparecer una serie de registros


Ya tenemos la info. Y AHORA QUE?

Si bien a mi me encanta trastear mandando sentencias sql a una base de datos, no es el caso de quienes necesitan hacer uso diario de esa informacion, asi que necesitamos algo que medie entre los usuarios y la informacion. Y para eso esta la aplicacion.

Cuales son las necesidades basicas del usuario de una base de datos? En este caso en particular, el seguimiento manual de las horas que un profesor tiene asignadas es un trabajo arduo. Cada vez que se necesita conocer la informacion pertinente a un profesor, se revisan legajos, fichas etc, y ni hablar de pedir informacion estadistica. Esto tal vez no parezca tan serio, hasta que se tiene en cuenta que de la informacion que consta en este legajo, depende el monto del sueldo de un docente, y la existencia de discrepancias implica problemas burocraticos.

La aplicacion debe tener capacidad de mostrar la informacion en un formato familiar para los usuarios de la informacion (ej: planilla de asignaciones, legajo personal). Para este caso, un editor sencillo para las tablas Asignaturas y Titulos, uno especial para la tabla profesores, que incluye las horas que tiene asignadas, y un listador completo de las horas asignadas por profesor.

Los archivos .php que generan las paginas de nuestro sistema normalmente residiran en un subdirectorio de /var/www/html, pero como usuario normal, uno no tiene derechos para crear o modificar directorios o ficheros fuera de los propios (en este caso /home/programador). Una opcion es hacer la tarea de copiar esos archivos como root, MALACOSTUMBRARSE a hacer todo como root, copiar los archivos via FTP, O utilizar una posibilidad que nos da el apache de publicar informacion proveniente de NUESTRO propio directorio de documentos. Creando un directorio public_html bajo /home/programador y asignando permisos de lectura y acceso para cualquier usuario, sera posible navegar por esas paginas invocando por ejemplo la direccion: http://localhost/˜programador. Existen en realidad mas formas de publicar documentos desde directorios personales, pero esta me parece la mas sencilla, amen de que esta configurada por defecto en apache.

La secuencia concreta en nuestro caso seria:

cd /home/programador
mkdir public_html
chmod o+rx /home/programador/public/html/
chmod o+r /home/programador/public/html/*
   

Algunos aspectos de la publicacion/sistema

Interface

Un punto que me parece importante destacar, es que, aun siendo la aplicacion un SISTEMA de informacion, dado que manipula registros y obtiene informacion, esta basado en la web, es un sistema de PUBLICACION de documentos. Este concepto, en mi caso en particular, ha sido el mas dificil de asimilar: hay que pensar no en terminos de interface o de acciones a llevar a cabo, sino en terminos de la FORMA de presentar la informacion. Es decir, no tenemos aqui posibilidad de usar sentencias que interactuen con el usuario y generen una inmediata respuesta. En realidad cada vez que un usuario ingresa un dato, es a traves de un boton SUBMIT de un <form> por ejemplo, y esa pagina se descarta, los datos del formulario se pasan a la pagina destino y a partir de estos datos se construye otra pagina, en la cual, por ejemplo, se modifica un registro. Si nos limitamos al uso exclusivo de PHP (nada de javascript o equivalentes) todas las interacciones se llevan a cabo a traves de ingreso de datos en un formulario, o a traves de hipervinculos que trasladan variables a otra pagina. Este mecanismo no pertenece a PHP, es en realidad HTML puro, solo que las variables HTML son leidas por PHP de forma transparente.

Ejemplos

El archivo menu.inc contiene el fragmento de codigo HTML que muestra el menu que aparece al principio de todas las paginas del sistema:

<Ahref="tabla2.php
?consulta=select+asignatura+from+asignaturas+order+by+asignatura
&edcampos=select+*+from+asignaturas
&tabla=asignaturas
&boton=edit.inc">
Asignaturas</A>
   

Este fragmento es un hipervinculo que llama al archivo tabla2.php, el cual necesita de algunos datos para ejecutarse: 'consulta', que especifica la sentencia SQL que mostrara los datos, 'edcampos' con la sentencia SQL con los campos que se editaran efectivamente, 'tabla', el nombre de la tabla que se edita y 'boton', con el nombre del archivo con el fragmento de codigo que llama a la pagina que edita un registro en particular.

tabla2.php toma estos datos y los usa para construir una estructura HTML <table> con el resultado de la sentencia contenida en la variable 'consulta' dentro de tabla2.php hay formularios con botones para moverse a traves de las pagina de la tabla, el mecanismo de paso de informacion es el mismo, solo que ahora cada variable esta contenida en un tag INPUT de formulario HTML.

Todo el mecanismo de muchas de las rutinas es un poco confuso, porque es el resultado de mis primeras experiencias en PHP y bases de datos, de hecho, ya hay una version mas simple que usa el concepto de sesion, por el cual muchos datos que se podrian considerar globales a una pagina que se recicla con informacion similar, como es el caso de tabla2.php, lista para ser comentada.


Obtencion de informacion

Como en este entorno no existe una manipulacion directa de la informacion, sino que es a traves de sentencias SQL, el manejo puede resultar tambien un poco extrano (por ejemplo para aquellos acostumbrados a dbase/foxpro).

Un bucle para obtener unos cuantos registros es tipicamente una llamada a una funcion SQL con la sentencia de la consulta:

$recordset=mysql_query('select codasi,asignatura from asignaturas');
   

...que se guarda en una variable ($recordset) que es un PUNTERO a donde esta almacenada la informacion recuperada. Para tener acceso a cada uno de los registros se van extrayendo uno a uno en un array ($registro) a traves de esa variable puntero:

while ($registro=mysql_fetch_object($recordset)) {
        echo $registro['codasi'].' '.$registro['asignatura'].'\n';
    }
   

Este array $registro es de tipo asociativo, es decir, se llama a cada elemento por un subindice alfanumerico, en este caso, la funcion mysql_fetch_object, devuelve un array con el contenido de un registro con cada campo con un subindice por sus nombres de campo, p.ej. $registro['codasi'] contendra el valor del campo codasi para ese registro recuperado Si en lugar de mysql_fetch_object se hubiera utilizado la funcion mysql_fetch_array, el array tendria subindices numericos, p.ej. $registro[0] contendra el valor del campo codasi para ese registro recuperado


Orientación a objetos

Una de las ventajas del uso de objetos es que ayudan a descomponer problemas complejos en partes manejables y reutilizables. Para la generacion de un formulario complejo tipo maestro-detalle he recurrido al uso de clases para armar cada uno de los controles del formulario. Los unicos necesarios son un control que permita ingresar texto (set_text) y otro que permita elejir valores de una tabla (set_combo). Los controles se agrupan en dos tipos de contenedores: edit_ctrl que edita un registro (para la parte de cabecera o maestro ) y tedit_ctrl que edita varios registros (para la parte de detalle) Con frm_aut se crea un objeto formulario para la cabecera y otro para el detalle, y en cada uno se van agregando los controles de los campos que se quieran editar. Para el primer objeto se invoca edit_ctrl para editar los datos unitarios del formularios y para el segundo objeto se invoca tedit_ctrl para editar los registros del detalle.


Conclusión

Bue no se me ocurre que mas agregar asi que termino esto pidiendo que lean este material, sobre todos los script (estan bastante comentados y creo que me salieron mas o menos claros, je) aunque mas no sea para ver si les resulta util, y para sugerirme alguna que otra forma de mejorarlo o avisar de algun error que se me haya escapado.

/home/murray/public_html/art/e_asi.php