Optimización de Bases de Datos: Normalización Relacional y Consultas MongoDB Avanzadas

Enviado por Chuletator online y clasificado en Informática y Telecomunicaciones

Escrito el en español con un tamaño de 9,08 KB

Modelado de Datos y Normalización en Bases de Datos Relacionales

Este documento aborda conceptos fundamentales de modelado de datos relacionales y su normalización, así como la manipulación de datos en MongoDB.

Esquemas de Bases de Datos Relacionales y Formas Normales

A continuación, se presentan esquemas de tablas relacionales y se identifican sus propiedades de normalización, específicamente en relación con la Segunda y Tercera Forma Normal (2FN y 3FN).

Tabla: Politico

Esquema: Politico(DNI, cargo1, cargo2, cargo3, NomPartido, votosObtenidos, siglasPartido, Matricula)

  • Clave Primaria (PK): DNI
  • Observación de Normalización:
    • Existe una dependencia funcional entre NomPartido y siglasPartido (y viceversa). Cada cambio en uno obliga a actualizar el otro en todas las filas.
    • Violación de la Tercera Forma Normal (3FN): Si DNI determina NomPartido y NomPartido determina siglasPartido, entonces siglasPartido depende transitivamente de la clave primaria DNI a través de NomPartido (un atributo no clave). La tabla se encuentra en 2FN, pero no en 3FN.

Tabla: CocheOficial

Esquema: CocheOficial(Matricula, marca, modelo, color, DNI)

  • Clave Primaria (PK): Matricula
  • Observación de Normalización:
    • Existe una dependencia transitiva: Matricula determina modelo, y modelo determina marca. Esto significa que marca depende de Matricula a través de modelo (un atributo no clave).
    • Anomalías: En las actualizaciones, es crucial verificar que la marca sea correcta para el modelo asociado.
    • Violación de la Tercera Forma Normal (3FN): La tabla se encuentra en 2FN, pero no en 3FN debido a la dependencia transitiva.
    • Nota: Los atributos DNI o Matricula son esenciales para relacionar esta tabla con otras.

Tabla: Partido

Esquema: Partido(NomPartido, siglasPartido, NomComunidadAuto, siglasComunidadAuto, EscañosObtenidos)

  • Clave Primaria (PK): (NomPartido, NomComunidadAuto) o (siglasPartido, siglasComunidadAuto)
  • Observación de Normalización:
    • NomPartido determina siglasPartido: siglasPartido depende parcialmente de NomPartido, que es parte de la clave primaria compuesta.
    • NomComunidadAuto determina siglasComunidadAuto: siglasComunidadAuto depende parcialmente de NomComunidadAuto, que también es parte de la clave primaria compuesta.
    • Violación de la Segunda Forma Normal (2FN): La tabla se encuentra en 1FN, pero no en 2FN debido a estas dependencias parciales de atributos no clave sobre partes de la clave primaria.

Operaciones con Colecciones en MongoDB

Se asume la existencia de una colección llamada Ocio con documentos que describen actividades de ocio. Cada documento tiene la siguiente estructura:

  • TipoOcio: Categoría de la actividad (ej. "viajes", "música", "esquí", "cine").
  • Empresa: Entidad que ofrece la actividad (ej. "Warner", "Sony").
  • Elemento: Nombre específico de la actividad (ej. "viajeCanarias", "conciertoU3", "JuegosTronos").
  • Nivel: Popularidad o calificación de la actividad (número de estrellas entre 0 y 10).
  • Coste: Precio de la actividad (ej. precio del "viajeCanarias").
  • Propiedades: Array de pares (nombre, valor) con características particulares del elemento (ej. [{ "nombre": "dificultad", "valor": "media" }]).

A continuación, se detallan las instrucciones de MongoDB solicitadas para diversas operaciones.

a) Agregación para Actividades Populares por Tipo y Empresa

Utilizando la operación aggregate, se busca obtener para cada TipoOcio y Empresa: el tipo de ocio, la empresa, la media de los costes de sus actividades más populares (Nivel >= 7), y el número total de actividades que cumplen dicha condición. Los resultados se filtrarán para incluir solo aquellos TipoOcio que tengan más de 5 actividades populares. El resultado se clasificará de forma descendente por TipoOcio y Empresa.

db.Ocio.aggregate([
    { $match: { "Nivel": { $gte: 7 } } },
    { $group: {
        _id: { ElTipo: "$TipoOcio", LaEmpresa: "$Empresa" },
        mediaCoste: { $avg: "$Coste" },
        totdocs: { $sum: 1 }
    }},
    { $match: { "totdocs": { $gte: 5 } } },
    { $sort: { "_id.ElTipo": -1, "_id.LaEmpresa": -1 } }
]);

Nota sobre el ordenamiento: Se ha especificado el ordenamiento por los campos anidados dentro de _id para mayor claridad y precisión en la clasificación descendente por TipoOcio y luego por Empresa.

b) Actualización o Inserción de Documento Único (Upsert)

Se requiere actualizar el primer documento que cumpla con las siguientes condiciones: TipoOcio: "montañismo", Empresa: "CabraMonteSA", Elemento: "CaminoSchmid", y que en sus Propiedades contenga el par (dificultad: media). Si no existe un documento que cumpla estas condiciones, se insertará uno nuevo con estos datos. Los cambios a realizar (o datos a insertar) son: Nivel = 9, Coste = 5, y un nuevo atributo MeGusta: "sí".

Además, se solicita que ningún otro proceso pueda escribir en la colección hasta que esta operación finalice. Aunque $isolated está obsoleto y las operaciones de escritura en MongoDB son atómicas a nivel de documento, se incluye para cumplir con la especificación original.

db.Ocio.updateOne(
    {
        TipoOcio: "montañismo",
        Empresa: "CabraMonteSA",
        Elemento: "CaminoSchmid",
        Propiedades: { $elemMatch: { nombre: "dificultad", valor: "media" } }
    },
    {
        $set: { Nivel: 9, Coste: 5, MeGusta: "sí" }
    },
    {
        upsert: true
        // $isolated: 1 // Nota: $isolated está obsoleto y no se recomienda para nuevas implementaciones.
                       // Las operaciones de escritura en MongoDB son atómicas a nivel de documento por defecto.
    }
);

Correcciones: Se ha utilizado updateOne en lugar de update (que está obsoleto) y se ha corregido la sintaxis de $elemMatch asumiendo que Propiedades es un array de objetos con claves nombre y valor. El parámetro $isolated se ha comentado con una nota sobre su obsolescencia.

c) Actualización Condicional de Costes con forEach

Utilizando una instrucción find combinada con la función forEach, se actualizarán las actividades cuyo TipoOcio sea "montañismo", "rafting", "piragua" o "piano", y que además tengan un Nivel bajo (menor que 5). La actualización consiste en bajar el coste proporcionalmente y añadir un atributo Oferta, según las siguientes reglas:

  • Si el Coste es menor de 1000:
    • Fórmula: (10 - (5 - nivel) / 10) * coste
    • Atributo: Oferta = "regular"
  • Si el Coste es mayor o igual a 1000:
    • Fórmula: (8 - (5 - nivel) / 10) * coste
    • Atributo: Oferta = "buena"
db.Ocio.find({
    TipoOcio: { $in: ["montañismo", "rafting", "piragua", "piano"] },
    Nivel: { $lt: 5 }
}).forEach(function (myDoc) {
    let newCoste;
    let ofertaText;

    if (myDoc.Coste < 1000) {
        newCoste = ((10 - (5 - myDoc.Nivel)) / 10) * myDoc.Coste;
        ofertaText = "regular";
    } else {
        newCoste = ((8 - (5 - myDoc.Nivel)) / 10) * myDoc.Coste;
        ofertaText = "buena";
    }

    // Actualizar el documento en la base de datos
    db.Ocio.updateOne(
        { _id: myDoc._id },
        { $set: { Coste: newCoste, Oferta: ofertaText } }
    );
});

Correcciones: Se ha corregido la sintaxis de la consulta find. Se ha reemplazado db.Ocio.save(myDoc) por db.Ocio.updateOne({ _id: myDoc._id }, { $set: { ... } }), que es la forma moderna y recomendada para actualizar documentos específicos. Se han eliminado las líneas de depuración y el texto irrelevante.

Entradas relacionadas: