BASES DE DATOS - IN.CO. 1992 SOLUCIONES PRACTICO 1. Ejercicio 1. PARTE 1. 1) En una clasificación general, podemos mencionar 3 métodos de accesos por valor de un campo (acceso lógico): secuencial, la característica de este método es que para encontrar un registro que esta en el lugar n se deben leer previamente los n-1 registros anteriores. Un ejemplo de estructura de datos para lograr esta forma de acceso seria la lista . aleatorio, la característica de este método es que podemos acceder directamente al registro que queremos por medio de una función que dado un determinado campo me asocia el numero de registro correspondiente. Un ejemplo de estructura de datos para este método es hash . indizado (o por índices), la característica de este método es similar al anterior, es decir asociar a un campo de un registro su numero de registro, con la diferencia de que en lugar de una función se mantiene una estructura que permite acceder al registro deseado en pocos accesos. Un ejemplo de una estructura de datos para este método es el árbol binario ordenado (ABO) . 2) La complejidad en el tiempo medida en cantidad de comparaciones, de los ejemplos de las estructuras dadas es del orden (en caso promedio): lista -> n/2 hash -> n/B ABO -> log (2,n) (lease, log en base 2 de n) donde: n - es la cantidad de registros. B - es la cantidad de buckets en el caso de hash (suponiendo uniformidad). Para fijar lo que significa esta magnitud, supongamos que tenemos n=1.000.000 de registros, entonces (caso promedio): lista, para obtener el registro deseado necesitamos hacer 500.000 comparaciones. hash, 1.000.000/B comparaciones. Por ej. si B = 1000 (asumiendo que los punteros ocupan 4 bytes esto implica tener un directorio de buckets de 4K aprox.) necesitamos hacer 1.000 comparaciones. ABO, log (2, 1.000.000) ÷ 20 comparaciones. Cuanto ocuparía el archivo FABRICANTES con 1.000.000 de registros ? Suponiendo enteros de 4B, entonces cada registro de fabricante seria ÷ 40 B.El archivo de fabricantes ocuparía del orden de 40 MB !! (piense cual es la capacidad actual en RAM de una maquina). 3) Tomemos el método de acceso indizado. Trabajemos con la estructura ABO con la siguiente estructura de nodo: TYPE nodo_fab = RECORD #fab : integer; nrr : integer; /* supongamos que con integer alcanza*/ izq, der : ^ nodo_fab; END; abo_fab = ^ nodo_fab; Que obtuvimos con esta organización ? . nos estamos acercando a un uso mas razonable de la memoria primaria y de la secundaria. Mantener este árbol nos cuesta en espacio bastante menos que en el caso anterior (12 MB). realizar la búsqueda utilizando esta estructura es menos costosa que realizar una búsqueda secuencial en el archivo directamente. A grosso modo, podemos pensar que la búsqueda secuencial en el archivo nos cuesta en promedio n/2 accesos al archivo, mientras que utilizando esta estructura nos cuesta 1 acceso al archivo. Suponiendo que la estructura anterior es cargada en una fase inicial, la búsqueda del fabricante cuyo #fab es 45 seria: function buscar (x:integer; F:abo_fab; var r:reg_fab) : boolean; /* Retorna true si x esta en F además del registro en el parámetro r. Retorna false en otro caso. */ begin if F = nil then return (false) else if x = F^.#fab then begin seek (FABRICANTES, F^.nrr); read (FABRICANTES, r); return (true) end else if x < F^.#fab then return (buscar (x, F^.izq, r)) else return (buscar (x, F^.der, r)) end ; entonces desde el programa principal, otra función, etc: begin open (FABRICANTES, "read"); cargo_abo (FABRICANTES, raiz); /* var raiz:^no do _fab.cargo_abo es un procedimiento que carga la estructura. */ if buscar (45, raiz, r_45) then imprimo (r_45) else imprimo ("Fabricante con #fab 45 no existe") close (FABRICANTES) end ; 4) Dado que queremos trabajar todo en memoria secundaria tenemos que ver como entra en juego el tema de acceder a esa memoria: una alternativa es trabajar en forma secuencial. Para buscar un registro comenzamos a leer el archivo desde el principio registro por registro, comparando el campo por el que andamos buscando hasta encontrar el registro o hasta llegar al final del archivo. A grosso modo esto nos cuesta del orden de n/2 accesos al archivo. En nuestro ejemplo significa 500.000 accesos. Cada acceso puede llegar a demorar 20 ms por lo que necesitamos 7 días para realizar la búsqueda, en promedio. otra alternativa es definir la estructura de ABO, no en memoria primaria, sino en memoria secundaria. Nuestros registros tendrían la siguiente estructura: TYPE reg_fab = RECORD #fab : integer; fnombre : array [1..15] of char; ciudad : array [1..15] of char; nrr_izq, nrr_der : integer; END y nuestro algoritmo de búsqueda quedaría: function buscar (x:integer; var F:fab_arch; n: integer; var r:reg_fab) : boolean; var r_aux : reg_fab; begin if vacío? (n) then return (false) else begin seek (F,n); read (F, r_aux); if x = r_aux.#fab then begin assign_record (r, r_aux); return (true) end else if x < r_aux.#fab then return (buscar (x, F, r_aux.nrr_izq, r)) else return (buscar (x, F, r_aux.nrr_der, r)) end end ; Haciendo un análisis grueso del tiempo, esto nos cuesta del orden de log(2, n). En nuestro ejemplo, log (2, 1.000.000) ÷ 20 accesos o sea 4 seg. Observemos ahora con mas detalle lo que nos implica trabajar con la memoria secundaria. Como se explica en la letra, el acceso a la memoria secundaria es a través de la transferencia de bloques . Es decir que cuan do hacemos una lectura del archivo estamos eventualmente) transfiriendo un bloque para poder obtener un registro. Supongamos bloques de 512 B (tamaño mínimo), si solo tenemos un registro por bloque estamos desperdician do espacio y aumentando el numero de acceso . En nuestro caso, cada registro tiene aproximadamente 40 B. Basados en la observación anterior se han diseñando diferentes estructuras de datos para minimizar el numero de accesos a disco En particular, siguiendo con la idea del árbol parecería deseable mantener varios registros por bloque lo que traería como consecuencia una disminución en la altura del árbol y por consiguiente una disminución en el numero de accesos. Una posibilidad es seguir trabajando con la idea de ABO y hacer que varios nodos estén en un bloque quedando algo así: +---------------------------------------+ | o | | / \ | | / \ | | / \ | | / \ | | / \ | | / \ | | / \ | | o o | | / \ / \ | +---------/-+-\-----+-----/-+-\---------+ | / | \ | / | \ | | o | o | o | o | | / \ | / \ | / \ | / \ | | / \ | / \ | / \ | / \ | | o o|o o|o o|o o | +-----------+-------+-------+-----------+ Tendríamos por ejemplo 5 bloques con 3 nodos por cada uno de ellos. Si llevamos esta idea a nuestro caso, tendríamos 12 registros por bloque lo que en caso promedio y muy favorable tendríamos del orden de log (12, 1.000.000) ÷ 6 accesos es decir, 0.12 seg. Favorable en el sentido que, sin tener criterios de equilibrio esta estructura puede degenerarse convirtiéndose entonces en una lista. En este caso estaríamos en el orden de 1.000.000/12 ÷ 80.000 accesos. No podemos dejar de lado criterios de equilibrio. Tener algo perfectamente equilibrado también es algo difícil debido a lo costoso de mantener. Lo que surgieron fueron otros criterios menos fuerte y adecuados para trabajar con este tipo de situación. Un ejemplo de estructura de datos con criterio de balanceo muy utilizado es el árbol B . Existen diferentes variantes de esta estructura. No vamos a estudiar en profundidad ni la estructura arbol-B en general, ni sus variantes ni los algoritmos. Todo esto es una presentación rápida, incompleta y superficial. Las características de un arbol-B son las siguientes [Algorithms & Data Structures, N. Wirth]. Consideremos un arbol-B de orden m: Todo bloque contiene a lo sumo 2m campos (claves). Todo bloque excepto el bloque raiz, contiene por lo menos m campos. Todo bloque es o bien una hoja, o bien k+1 bloques descendientes, donde k es la cantidad de campos en ese bloque. Todos los bloques hoja aparecen al mismo nivel. A modo de ejemplo, consideremos unas de sus variantes: el arbol-B+. Veamos el aspecto de la estructura [Principles of Database and Knowledge-base systems, J.D. Ullman]: +--+---+-+ +-----------|25|144| | archivo | +--+---+-+ índice | | | | | +----------+ | | | \|/ \|/ \|/ +--+--+-+ +--+---+-+ +---+--+-+ +---------| 9| -| | +-|64|100| | |196| -| | | +--+--+-+ | +--+---+-+ +---+--+-+ | | | | | | | | | | | | | +---------------------+ | | | | | +----------+ | | | | | +------------------+ | | | | | +-----------+ | | | | | +---+ | | | | \|/ \|/ \|/ \|/ |\|/ \|/ +--+--+--+-+ +--+--+--+-+ +--+--+--+-+ +--+--+--+-+|+---+---+--+-+ +---+--+--+-+ | 1| 4| -| | | 9|16| -| | |25|36|49| | |64|81| -| |||144|169| -| | |196| -| -| | +--+--+--+-+ +--+--+--+-+ +--+--+--+-+ +--+--+--+-+|+---+---+--+-+ +---+--+--+-+ | archivo principal \|/ +---+---+--+-+ |100|121| -| | +---+---+--+-+ Por un lado se tienen los bloques del archivo principal, que forman los bloques hojas del arbol-B y a partir de ellos se mantiene una estructura de árbol donde los nodos tienen solo la clave de los primeros registros de los bloques del archivo principal. En este caso k = 2. En un árbol de este tipo el numero de accesos es en el peor caso, aproximadamente 1 + log (k, n/k'), donde k es la cantidad de registros en un bloque del archivo principal y k' es la cantidad en los bloques del archivo índice. En nuestro caso, 1 + log (6, 1.000.000/32) ÷ 6 accesos. PARTE 2. 1) i) open (EMPRESAS, "read"); read (EMPRESAS, e); while ªeof (EMPRESAS) do begin open (FABRICANTES, "read"); read (FABRICANTES, f); while ªeof (FABRICANTES) do begin if e.#emp = f.#emp then imprimir (f,e); read (FABRICANTES, f); end close (FABRICANTES); read (EMPRESAS, e); end close (EMPRESAS) Aprovechando la información de que campos son claves, open (FABRICANTES, "read"); read (FABRICANTES, f); while ªeof (FABRICANTES) do begin open (EMPRESAS, "read"); read (EMPRESAS, e); encontre := false; while ªeof (EMPRESAS) ^ ªencontre do begin if f.#emp = e.#emp then begin imprimir (f,e); encontre := true; end else read (EMPRESAS, e); end close (EMPRESAS); read (FABRICANTES, f); end close (FABRICANTES); Otra posibilidad, open (FABRICANTES, "read"); open (EMPRESAS, "read"); read (FABRICANTES, f); while ªeof (FABRICANTES) do begin read (EMPRESAS, e); encontre := false; while ªeof (EMPRESAS) ^ ªencontre do begin if f.#emp = e.#emp then begin imprimir (f, e); encontre := true; end else read (EMPRESAS, e); end seek (EMPRESAS, 0); read (FABRICANTES, f); end ii) EMPRESAS (#emp, nombre) ordenado por #emp FABRICANTES (#fab, fnombre, ciudad, #emp) ordenado por #emp open (EMPRESAS, "read"); open (FABRICANTES, "read"); read (EMPRESAS, e); read (FABRICANTES, f); while ªeof (EMPRESAS) ^ ªeof (FABRICANTES) do begin if e.#emp = f.#emp then begin imprimir (f, e); read (FABRICANTES, f); end else if e.#emp < f.#emp then read (EMPRESAS, e); else read (FABRICANTES, f); end close (EMPRESAS); close (FABRICANTES); iii) EMPRESAS (#emp, nombre) - Indice sobre #emp FABRICANTES (#fab, fnombre, ciudad, #emp) - Indice sobre #emp A grosso modo, la diferencia entre ambos índices esta en que: En EMPRESAS, el índice definido sobre el es tal que dado un valor de #emp dicho registro del índice hace referencia solo a un registro del archivo principal EMPRESAS. En FABRICANTES, en cambio, dado un valor de #emp dicho registro del índice hace referencia a un conjunto (cuya cardinalidad es variable) de registros del archivo principal FABRICANTES. Esto es debido a que en una empresa pueden trabajar varios fabricantes. Esto tiene consecuencias en las estructuras de datos para trabajar con esta clase de índices. Generalmente se les conoce índices secundarios. open (EMPRESAS, "read"); open (FABRICANTES, "read"); open_indice (FAB_INX); read (EMPRESAS, e); while ªeof (EMPRESAS) do begin read (FAB_INX, e.#emp, f); while ªerror do /* "error" es una variable que actualiza el read anterior. */ begin imprimir (f, e); read_next (FAB_INX, f); /* actualiza la variable "error" cuando cambia de empresa. */ end read (EMPRESAS, e); end close_indice (FAB_INX); close (FABRICANTES); close (EMPRESAS); Otra posibilidad seria recorrer FABRICANTES y utilizar el índice sobre EMPRESAS. 2) i) Esta consulta es una leve modificación de la consulta anterior por lo que podemos modificar la solución 1) ii) suponiendo que los 2 archivos están ordenados por #emp. open read encontre := false; while ªeof (EMPRESAS) ^ ªeof (FABRICANTES) ^ ªencontre do begin if e.#emp = f.#emp then if f.#fab = 15 then begin encontre := true; imprimir (f, e); end else read (FABRICANTES, f); else if ....... then else end close . Otra solución es suponer que EMPRESAS esta ordenado por #emp y FABRICANTES por #fab. open (FABRICANTES, "read"); open (EMPRESAS, "read"); read (FABRICANTES, f); encontre := false; me_pase := false; while ªeof (FABRICANTES) ^ ªencontre ^ ªme_pase do begin if f.#fab = 15 then begin encontre := true; /* busco la empresa en la que trabaja el #fab 15 */ read (EMPRESAS, e); encontre_emp := false; me_pase_emp := false; while ªeof(EMPRESAS) ^ ªencontre_emp ^ ªme_pase_emp do begin if e.#emp = f.#emp then begin encontre_emp := true; imprimir (f,e); end else if e.#emp < f.#emp then me_pase_emp := true; else read (EMPRESAS, e); end end else if f.#fab < 15 then me_pase :=true; else read (FABRICANTES, f); end close (EMPRESAS); close (FABRICANTES); ii) Definimos un índice sobre FABRICANTES por #fab para acceder al 15 y un índice sobre EMPRESAS por #emp para acceder a la empresa dado el #emp del fabricante con #fab 15. crea_indice (FAB_#fab_INX, FABRICANTES, #fab); crea_indice (EMP_#emp_INX, #emp); open (FABRICANTES, "read"); open (EMPRESAS, "read"); open_indice (FAB_#fab_INX); open_indice (EMP_#emp_INX); read (FAB_#fab_INX, 15, f); if ªerror then read (EMP_#emp_INX, f.#emp, e); if ªerror then imprimir (f, e); close_indice (EMP_#emp_INX); close_indice (FAB_#fab_INX); close (EMPRESAS); close (FABRICANTES); Observar: 1) cada vez que se cambia la estructura de datos para la misma consulta, el o los programas que la resuelven cambian. 2) leves cambios en las consultas implican programas nuevos parecidos a los que ya tenemos. Ejercicio 2. PROPIETARIOS (#prop , datos_pers , comisión , saldo) #prop identifica al propietario INQUILINOS (#inq , datos_pers , #prop , dir_finca , alquiler) #inq identifica al inquilino Con este diseño la forma de representar información de una finca cualquiera es que esa finca tenga un numero de inquilino asociado, o sea que este alquilada. Entonces para representar fincas no alquiladas tendremos que utilizar numero de inquilino ficticios. Además dado que el numero del inquilino solo puede tener asociado una finca, tendremos que usar un numero de inquilino ficticio. Esta forma de representar las fincas no alquiladas tiene consecuencias a la hora de operar con los datos ya que, por ejemplo, como aseguramos que los números de inqulino ficticios no se superpongan con los números de inquilinos verdaderos, etc. El problema ha sido fundamentalmente que la finca se ha representado asociada a al menos un inquilino. Para esto analicemos que quiere decir la restricción de que el #inq identifica al inquilino: No puede existir dos o mas registros en el archivo INQUILINOS que tengan igual numero de inquilino y distinto dirección de finca por ejemplo. De lo anterior, entonces, dado un numero de inquilino solo puede haber una finca alquilada por ese numero a un propietario único, con un solo precio de alquiler. Lo que sucede en el caso de que un inquilino alquila mas de una finca es que ese inquilino será representado por el sistema con tantos números de inquilinos como fincas este alquilando. Esto genera problemas principalmente en la actualización de por ejemplo algún dato personal de ese inquilino ya que habrá que actualizarlo en todos los registros asignados a el, con la gran posibilidad de producir inconsistencias. Esta modificación de la realidad tiene cambios mas grandes que en el caso anterior ya que como veremos, el "parche" no solo involucrara uno de los archivos. Por un lado el hecho de que un propietario acuerde distintas comisiones es un problema análogo al de la parte b). Su solución es representar a un propietario en el sistema a través de un conjunto de registros de propietarios, uno por cada comisión acordada y con números de propietarios diferentes. Esto genera los problemas mencionados en b). Pero, con lo anterior solo no alcanza ya que las comisiones se acuerdan para las fincas, es decir falta establecer como conozco la comisión para una finca. Esto lo pueda conocer a través del numero de propietario en el archivo INQUILINOS, existiendo la posibilidad de producir errores de tener números de propietarios en INQUILINOS que no se correspondan con ningún numero de propietario en PROPIETARIO. Ejercicio 3. Representación gráfica: ------------ s_com ------- s_rest ------------ | COMIDAS |<------------| SIRVE |---------> | RESTAURANT | ------------ ------- ------------ ^ ^ ^ | | | | | | | c_com | l_sirve | | | | | | | --------- ---------- | | COM_ING | | LE_GUSTA | | f_rest --------- ---------- | | | | | | | | c_ing | l_pers | | | | | | | \/ \/ | -------------- ---------- ----------- | INGREDIENTES | | PERSONAS | --------> | FRECUENTA | -------------- ---------- ----------- COMIDAS (nombre_comida) COM_ING (nombre_comida,ingrediente) INGREDIENTES (ingrediente) SIRVE (nombre_comida, nombre_rest, precio) LE_GUSTA (nombre_comida, nombre_rest, nombre_pers) PERSONAS (nombre_pers, dir_pers) RESTAURANT ( nombre_rest, dir_rest, tel, categoria) FRECUENCIA (nombre_pers, nombre_rest) Definicion usando el DDL dado en clase. record comidas nombre_comida char(20); record com_ing nombre_comida virtual source is comidas.nombre_comida of owner of c_com ingrediente virtual source is ingredientes.ingrediente of owner of c_ing; record ingredientes ingrediente char(15); record sirve nombre_comida virtual source is comidas.nombre_comida of owner of s_com nombre_rest virtual source is restaurant.nombre_rest of owner of s_rest; record le_gusta nombre_comida virtual source is sirve.nombre_comida of owner of l_sirve nombre_rest virtual source is sirve.nombre_rest of owner of l_sirve nombre_pers virtual source is personas.nombre_pers of owner of l_pers; record personas nombre_pers char(20); dir_pers char(40); record restaurant nombre_rest char(15); dir_rest char(40); tel char(8); categoria integer; record frecuenta nombre_pers virtual source is personas.nombre_pers of owner of f_pers nombre_rest virtual source is restaurant.nombre_rest of owner of f_rest; dbtg set s_com owner is comidas member is sirve; dbtg set c_com owner is comidas member is com_ing; dbtg set c_ing owner is ingredientes member is com_ing; dbtg set l_sirve owner is sirve member is le_gusta; dbtg set l_pers owner is personas member is le_gusta; dbtg set s_rest owner is restaurant member is sirve; dbtg set f_rest owner is restaurant member is frecuenta; dbtg set f_pers owner is personas member is frecuenta; Ejercicio 4. ---------- | CLIENTES | ---------- ^ | ord_clie | --------- | ORDENES | --------- ^ | o_ordenes | ---------- | ORD_ITEM | ---------- | | o_items \/ ------- | ITEMS | ------- ^ | p_items | ----------- | PROV_ITEM | ----------- | | p_proveedores \/ ------------- | PROVEEDORES | ------------- CLIENTES (nombre, direccion, saldo) calc_key: nombre ORDENES (#orden, fecha) calc_key: #orden ORD_ITEM (#item, #orden, cantidad) ITEMS (#item, nombre) calc_key: #item PROV_ITEM (precio) PROVEEDORES (#proveedor, nombre) calc_key: #proveedor a) Para resolver esta consulta nos podemos definir un dbtg set singular: dbtg set todos_clie owner is system member is clientes find first clientes record in current todos_clie set; while db.status = 0 do begin get clientes; if clientes.saldo = 0 then print (clientes.nombre); find next clientes record in current todos_clie set; end b) Dado que la calc_key es #proveedor nos definimos: dbtg set todos_prov owner is system member is proveedores encontre:= false; find first proveedores record in current todos_prov set while db.status = 0 ^ encontre = false do begin get proveedores; if proveedores.nombre = "R. Ruggido" then begin find first prov_item record in current p_proveedores set while db.status = 0 ^ encontre = false do begin find owner of current p_items set get items; if items.nombre = "Uranio" then begin find current of prov_item record; get prov_item; print prov_item.precio; encontre:= true; end else find next prov_item record in current p_proveedores set end if encontre = false then begin print ("R. Ruggido no vende Uranio") encontre:= true end end else find next proveedores record in current todos_prov set end c) encontre:= false; clientes.nombre := "F. Carpenter"; find clientes record using calc_key; /* suponemos que existe el cliente */ find first ordenes record in current ord_clie set; while db_status = 0 ^ encontre = false do begin find first ord_item record in current o_ordenes set; while db.status = 0 ^ encontre = false do begin find owner of current o_items set; get items; if items.nombre = "Plutonio" then begin find current of ord_item record get ord_item; print ord_item.cantidad encontre:= true; else find next ord_item record in current o_ordenes set; end if encontre = false then /* bajo esa orden no ordeno Plutonio*/ find next ordenes record in current ord_clie set end Ejercicio 5. ----------- | COMPANIAS | ----------- ^ ^ | | pertenece | | | ---------- trabaja | | ACCIONES | | ---------- | ^ | | posee | | ---------- | PERSONAS | ---------- COMPANIAS (nombre, direccion) calc_key: nombre ACCIONES (#accion, cantidad) calc_key: #accion PERSONAS (nombre,direccion) calc_key: nombre a) error:= false; read nro_accion; while nro_accion <> "salida" ^ error = false do begin acciones.#accion := nro_accion; find acciones record using calc_key if dbstatus <> 0 then begin print ("No existe tal nro de accion o algun otro problema"); error:= true; end else begin find owner of current posee set if dbstatus <> 0 then begin print ("No hay persona que la tenga o algun otro problema"); error:= true; end else begin get personas; print personas.nombre, personas.direccion read nro_accion; end end end b) companias.nombre:= "AMIP.SA" find companias record using calc_key if dbstatus <> 0 then print ("No existe la compania o algun otro error"); else begin error:= false; find first acciones record in current pertenece set while dbstatus = 0 ^ error = false do begin find owner of current posee set if dbstatus <> 0 then begin print ("No hay persona para una accion o algun otro problema"); error:= true end else begin get personas; print personas.nombre, personas.direccion; find next acciones record in current pertenece set end end end c) dbtg set todas_pers owner is system member is personas error:= false; find first personas record in current todas_pers set; while dbstatus = 0 ^ error = false do begin find owner of current trabaja set; if dbstatus <> 0 then begin print ("Persona que no trabaja o algun otro error"); error:= true; end else begin get companias; aux:= companias.nombre; /* Tenemos en aux la compania en que trabaja la persona */ encontre:= false; error:= false; while dbstatus = 0 ^ error = false do begin find owner of current pertenece set if dbstatus <> 0 then begin print ("Accion erronea o algun otro error"); error:= true; end else begin get companias; if aux = compania.nombre then begin find current of personas record; get personas; print personas.nombre; encontre:= true; end else find next acciones record in current posee set; end end find next personas record in current todas_pers set; end end _s d) error:= false; cant:= 0; find first personas record in current todas_pers set while dbstatus = 0 ^ error = false do begin find owner of current trabaja set if dbstatus <> 0 then begin print ("Persona que no trabaja o algun otro error"); error:= true; end else begin get companias; if companias.nombre = "AMIP.SA" then begin error:= false; find first acciones record in current posee set; while dbstatus = 0 ^ error = false do begin find owner of current pertenece set; if dbstatus <> 0 then begin print ("Accion erronea o algun otro error"); error:= true; end else begin get companias; if companias.nombre = "AMIP.SA" then begin find current of acciones record get acciones; cant:= cant + acciones.cantidad; end find next acciones record in current posee set; end end end find next personas record in current todas_pers set end end print (cant); Observaciones: En este ejercicio, al escribir las consultas, se tuvo en cuenta el manejo de errores. La finalidad de esto fue para observar la complejidad (en cuanto a lo engorroso) de tratar con los errores. Este problema parece ser muy comun cuando se trata de vincular lenguajes de programacion de proposito general y comandos del modelo de datos, en este caso redes. A continuacion se analiza, por un lado otra solucion a la parte c) ( sin tener en cuenta todos los errores) y por otro se da otra posible solucion para la parte d) discutiendo porque esa solucion no seria correcta. c) En lugar de definir un dbtg set singular sobre personas lo hacemos sobre acciones. La idea es dada una accion hallar por un lado la compania a la cual pertenece mediante el dbtg set PERTENECE. Por otro lado ubicamos la persona que posee (por medio de POSEE) y a la vez ubicamos la compania en que dicha persona trabaja (por medio de TRABAJA) y si las companias coinciden entonces la persona pertenece a la solucion. Tal como se planteara el algoritmo, tendra como inconveniente que si una persona tiene mas de una accion de la empresa en donde trabaja figurara mas de una vez. Dado que finalidad del ejercicio es basicamente lo relacionado con las operaciones del modelo de redes no consideraremos este otro problema. dbtg set todas_accion owner is system member is accion find first acciones record in current todas_accion set; while dbstatus = 0 do begin find owner of current pertenece set; get companias; aux:= companias.nombre; find owner of current posee set; find owner of current trabaja set; get companias; if companias.nombre = aux then begin find current of personas record; print (personas.nombre, personas.direccion); end find next acciones record in current todas_accion set; end d) La idea es: para cada persona que trabaja en AMIP.SA verificamos si algunas de sus acciones son de AMIP.SA; en tal caso acumulamos cant:= 0; companias.nombre:= "AMIP.SA"; find companias record using calc_key; find first personas record in current trabaja set; while dbstatus = 0 do begin find first acciones record in current posee set; while dbstatus = 0 do begin find owner of current pertenece set; get companias; B -----> if companias.nombre = "AMIP.SA" then begin find current of acciones record; get acciones, cantidad cant:= cant + cantidad; end find next acciones record in current posee set; end A ---> find next personas record in current trabaja set; end print(cant); El problema con este algoritmo es que si el "if" B da falso (es decir la compania no es AMIP.SA) entonces se modificaron los currency pointers de COMPANIAS y del dbtg set TRABAJA. Cuando se realice el "find" A se podria estar con otra persona que trabaja en otra compania. Entonces esta solucion seria incorrecta. Ejercicio 6. --------------- | INMOBILIARIAS | --------------- | | -------------- | OFICINAS | -------------- / \ / \ / \ ----------- ----------- | EMPLEADOS | | INMUEBLES | ----------- ----------- | | ---------- | CLIENTES | ---------- a) get leftmost empleados where oficinas.ciudad = "Montevideo" and empleados.ventas > 100.000; print empleados.nombre; b) get leftmost inmuebles where precio > 50.000; while dbstatus = 0 do begin print (inmuebles.direccion); get next inmuebles where precio > 50.000; end c) get leftmost empleados where empleados.nombre = "M. Marti"; get next within parent cliente; while dbstatus = 0 do begin print (clientes.nombre, clientes.direccion); get next within parent clientes; end Otra forma de hacerlo es: get leftmost clientes where empleados.nombre = "M. Marti"; while dbstatus = 0 do begin print (clientes.nombre, clientes.direccion); get next clientes where empleados.nombre = "M. Marti"; end