BD CLASE 5 2024-II

 


En este tutorial utilizaremos LEFT JOINs para mostrar los datos de las dependencias, tengan o no, un responsable de área y resolveremos los problemas que se generan cuando pasamos una variable vacía por la url

 

¿Qué es un LEFT JOIN y cómo se diferencia de otros tipos de JOINs?

 

Filtrando Registros con la Clausula WHERE:

Creando condiciones: Utilizando la cláusula WHERE para filtrar los resultados de una consulta según criterios específicos.

Combinando condiciones: Apilando múltiples condiciones con operadores lógicos (AND, OR, NOT).


Considerando tus requisitos, este tutorial abordará en profundidad el LEFT JOIN en SQL, su distinción con otros tipos de JOIN, y cómo utilizar la cláusula WHERE para filtrar y combinar condiciones. Además, se incluirá una sección práctica sobre cómo manejar variables vacías pasadas por URL en el contexto de una aplicación web PHP/MySQL, un problema común en el desarrollo frontend-backend.


Tutorial: LEFT JOIN y Filtrado con WHERE en SQL, y Manejo de Variables Vacías en PHP

En este tutorial, exploraremos una de las operaciones fundamentales en bases de datos relacionales: la combinación de tablas (JOIN). Nos centraremos en el LEFT JOIN, una herramienta poderosa para recuperar datos de múltiples tablas, incluso cuando no hay coincidencias en una de ellas. Además, abordaremos cómo refinar los resultados con la cláusula WHERE y cómo gestionar un desafío común en el desarrollo web: el manejo de variables vacías recibidas a través de la URL.

Requisitos Previos:

  • Conocimientos básicos de SQL (SELECT, FROM).
  • Conocimientos básicos de PHP y HTML (para la parte de la aplicación web).
  • Un entorno XAMPP con Apache y MySQL funcionando.
  • La base de datos db_vehiculos_mantenimiento o una similar con al menos dos tablas relacionadas (ej. Dependencias y Empleados).

1. Preparación de la Base de Datos (Ejemplo Práctico)

Para ilustrar el LEFT JOIN, utilizaremos dos tablas de ejemplo: Dependencias y Empleados. Una dependencia puede tener un responsable (un empleado), pero no todas las dependencias tienen por qué tenerlo asignado en todo momento.

Diseño de Tablas Sugerido:

  • Dependencias:

    • id_dependencia (INT, PK, AUTO_INCREMENT)
    • nombre_dependencia (VARCHAR)
    • ubicacion (VARCHAR)
    • id_responsable (INT, FK a Empleados.id_empleado, nullable) - Esto es clave para el LEFT JOIN.
  • Empleados:

    • id_empleado (INT, PK, AUTO_INCREMENT)
    • nombre_completo (VARCHAR)
    • cargo (VARCHAR)

Sentencias SQL para crear y poblar las tablas:

SQL
-- Selecciona tu base de datos si no lo has hecho
-- USE db_vehiculos_mantenimiento;

-- Tabla Empleados (necesaria para el responsable)
CREATE TABLE Empleados (
    id_empleado INT AUTO_INCREMENT PRIMARY KEY,
    nombre_completo VARCHAR(100) NOT NULL,
    cargo VARCHAR(50)
);

-- Insertar datos de ejemplo en Empleados
INSERT INTO Empleados (nombre_completo, cargo) VALUES
('Ana García', 'Gerente de TI'),
('Carlos Ruiz', 'Jefe de Ventas'),
('Laura Morales', 'Asistente Administrativo'); -- Este empleado no será responsable de una dependencia al inicio

-- Tabla Dependencias
CREATE TABLE Dependencias (
    id_dependencia INT AUTO_INCREMENT PRIMARY KEY,
    nombre_dependencia VARCHAR(100) NOT NULL,
    ubicacion VARCHAR(100),
    id_responsable INT,
    FOREIGN KEY (id_responsable) REFERENCES Empleados(id_empleado)
);

-- Insertar datos de ejemplo en Dependencias
INSERT INTO Dependencias (nombre_dependencia, ubicacion, id_responsable) VALUES
('Departamento de Contabilidad', 'Edificio Principal', NULL), -- Sin responsable
('Departamento de Marketing', 'Anexo B', 2), -- Carlos Ruiz es responsable
('Soporte Técnico', 'Piso 3', 1), -- Ana García es responsable
('Recursos Humanos', 'Edificio Principal', NULL); -- Sin responsable

2. ¿Qué es un LEFT JOIN y cómo se diferencia de otros tipos de JOINs?

Un JOIN en SQL se utiliza para combinar filas de dos o más tablas basándose en una columna relacionada entre ellas. Existen varios tipos de JOINs, cada uno con un comportamiento diferente:

  • INNER JOIN (o simplemente JOIN):

    • Devuelve solo las filas cuando hay coincidencias en ambas tablas. Si una fila en una tabla no tiene una coincidencia en la otra, no se incluirá en el resultado.
    • Uso común: Cuando necesitas información que solo existe si las dos entidades están relacionadas (ej. solo quieres ver mantenimientos que tienen un vehículo y un empleado asignados).
  • LEFT JOIN (o LEFT OUTER JOIN):

    • Devuelve todas las filas de la tabla de la "izquierda" (la primera tabla mencionada después de FROM).
    • También devuelve las filas coincidentes de la tabla de la "derecha".
    • Si no hay coincidencia en la tabla de la "derecha", los campos de esa tabla aparecerán con valores NULL (nulos).
    • Uso común: Cuando quieres ver todos los registros de una tabla, y si tienen información relacionada en otra tabla, mostrarla; de lo contrario, mostrar NULL. Por ejemplo, mostrar todas las dependencias, tengan o no un responsable.
  • RIGHT JOIN (o RIGHT OUTER JOIN):

    • Es el inverso del LEFT JOIN. Devuelve todas las filas de la tabla de la "derecha", y las coincidentes de la izquierda. Si no hay coincidencia en la tabla de la "izquierda", los campos de esa tabla aparecerán con NULL.
    • Uso común: Menos frecuente que el LEFT JOIN porque su funcionalidad suele lograrse simplemente invirtiendo el orden de las tablas en un LEFT JOIN.
  • FULL OUTER JOIN:

    • Devuelve todas las filas cuando hay una coincidencia en cualquiera de las tablas. Combina los resultados del LEFT JOIN y RIGHT JOIN. Si no hay coincidencia, los campos aparecerán con NULL.
    • Nota: MySQL no soporta FULL OUTER JOIN directamente. Se emula con LEFT JOIN UNION RIGHT JOIN.

Demostración Práctica del LEFT JOIN

Vamos a utilizar nuestras tablas Dependencias y Empleados.

Objetivo: Mostrar todas las dependencias, y si tienen un responsable asignado, mostrar su nombre. Si no tienen responsable, el campo del responsable debe aparecer NULL.

SQL
SELECT
    d.nombre_dependencia,
    d.ubicacion,
    e.nombre_completo AS nombre_responsable,
    e.cargo AS cargo_responsable
FROM
    Dependencias d
LEFT JOIN
    Empleados e ON d.id_responsable = e.id_empleado;

Resultado esperado:

nombre_dependenciaubicacionnombre_responsablecargo_responsable
Departamento de ContabilidadEdificio PrincipalNULLNULL
Departamento de MarketingAnexo BCarlos RuizJefe de Ventas
Soporte TécnicoPiso 3Ana GarcíaGerente de TI
Recursos HumanosEdificio PrincipalNULLNULL

Análisis del LEFT JOIN: Observa que "Departamento de Contabilidad" y "Recursos Humanos" aparecen en el resultado, a pesar de no tener una id_responsable (NULL) que coincida con ningún id_empleado. Los campos de Empleados (nombre_responsable, cargo_responsable) para estas filas son NULL. Esto es el comportamiento característico del LEFT JOIN.


3. Filtrando Registros con la Cláusula WHERE

La cláusula WHERE se utiliza para especificar una o más condiciones que deben cumplirse para que un registro sea incluido en el conjunto de resultados de una consulta.

Creando Condiciones Básicas

Una condición se compone de una columna, un operador de comparación y un valor.

Operadores de comparación comunes:

  • = (Igual a)
  • != o <> (Diferente de)
  • > (Mayor que)
  • < (Menor que)
  • >= (Mayor o igual que)
  • <= (Menor o igual que)
  • BETWEEN valor1 AND valor2 (Entre dos valores, inclusivo)
  • LIKE 'patron' (Para búsqueda de patrones, usa % como comodín para cero o más caracteres, _ para un solo carácter)
  • IN (valor1, valor2, ...) (Valor está en una lista de valores)
  • IS NULL (Valor es nulo)
  • IS NOT NULL (Valor no es nulo)

Ejemplos de WHERE con LEFT JOIN:

a) Mostrar solo las dependencias que tienen un responsable asignado:

SQL
SELECT
    d.nombre_dependencia,
    d.ubicacion,
    e.nombre_completo AS nombre_responsable,
    e.cargo AS cargo_responsable
FROM
    Dependencias d
LEFT JOIN
    Empleados e ON d.id_responsable = e.id_empleado
WHERE
    e.id_empleado IS NOT NULL; -- O directamente d.id_responsable IS NOT NULL

Este WHERE filtra los resultados del LEFT JOIN para incluir solo las filas donde la tabla de la derecha (Empleados) sí encontró una coincidencia (es decir, id_empleado no es nulo).

b) Mostrar dependencias en una ubicación específica:

SQL
SELECT
    d.nombre_dependencia,
    d.ubicacion,
    e.nombre_completo AS nombre_responsable
FROM
    Dependencias d
LEFT JOIN
    Empleados e ON d.id_responsable = e.id_empleado
WHERE
    d.ubicacion = 'Edificio Principal';

Combinando Condiciones: AND, OR, NOT

Puedes apilar múltiples condiciones usando operadores lógicos:

  • AND: Ambas condiciones deben ser verdaderas.
  • OR: Al menos una de las condiciones debe ser verdadera.
  • NOT: Niega la condición.

Ejemplos:

a) Dependencias en "Edificio Principal" Y con responsable asignado:

SQL
SELECT
    d.nombre_dependencia,
    d.ubicacion,
    e.nombre_completo AS nombre_responsable
FROM
    Dependencias d
LEFT JOIN
    Empleados e ON d.id_responsable = e.id_empleado
WHERE
    d.ubicacion = 'Edificio Principal' AND e.id_empleado IS NOT NULL;

b) Dependencias con responsable 'Ana García' O sin responsable asignado:

SQL
SELECT
    d.nombre_dependencia,
    d.ubicacion,
    e.nombre_completo AS nombre_responsable
FROM
    Dependencias d
LEFT JOIN
    Empleados e ON d.id_responsable = e.id_empleado
WHERE
    e.nombre_completo = 'Ana García' OR e.id_empleado IS NULL;

c) Todas las dependencias EXCEPTO las del 'Piso 3':

SQL
SELECT
    d.nombre_dependencia,
    d.ubicacion,
    e.nombre_completo AS nombre_responsable
FROM
    Dependencias d
LEFT JOIN
    Empleados e ON d.id_responsable = e.id_empleado
WHERE
    NOT d.ubicacion = 'Piso 3'; -- Equivalente a d.ubicacion != 'Piso 3'

Uso de paréntesis para agrupar condiciones: Es crucial usar paréntesis para controlar el orden de evaluación de las operaciones lógicas, especialmente con AND y OR.

  • WHERE (condicion1 AND condicion2) OR condicion3
  • WHERE condicion1 AND (condicion2 OR condicion3)

4. Resolución de Problemas: Pasar una Variable Vacía por la URL en PHP

Un escenario común es tener un filtro en tu aplicación web que toma un valor de la URL (usando el método GET) para usarlo en una consulta SQL. El problema surge cuando ese valor está vacío (ej. el usuario no introduce nada en un campo de búsqueda o el parámetro no está presente en la URL).

Escenario: Queremos mostrar las dependencias, opcionalmente filtradas por un responsable específico que se pasa por la URL (?responsable=ID_EMPLEADO). Si responsable está vacío, queremos mostrar todas las dependencias.

Archivos Necesarios:

  • includes/conexion.php (reutilizado del tutorial anterior).
  • listado_dependencias.php (nuestro script principal).

listado_dependencias.php:

PHP
<?php
session_start();
include 'includes/conexion.php'; // Asegúrate de que esta ruta sea correcta para tu proyecto
include 'includes/funciones.php'; // Si tienes funciones útiles como sanear_dato

// 1. Obtener la variable de la URL
$id_responsable_filtro = $_GET['responsable'] ?? ''; // Usa el operador de fusión de null (PHP 7+)
// Si usas PHP < 7, usa: $id_responsable_filtro = isset($_GET['responsable']) ? $_GET['responsable'] : '';

// 2. Sanear y validar la variable
// Es CRUCIAL sanear cualquier entrada de usuario para prevenir inyecciones SQL
if (!empty($id_responsable_filtro)) {
    // Asegurarse de que sea un número entero si esperamos un ID
    $id_responsable_filtro = (int) sanear_dato($conn, $id_responsable_filtro);
} else {
    // Si está vacío, le asignamos NULL o un valor que indique "no filtrar por responsable"
    $id_responsable_filtro = null; // O un valor que no coincida con ningún ID, como -1
}

// 3. Construir la consulta SQL dinámicamente
$sql = "SELECT
            d.id_dependencia,
            d.nombre_dependencia,
            d.ubicacion,
            e.nombre_completo AS nombre_responsable,
            e.cargo AS cargo_responsable
        FROM
            Dependencias d
        LEFT JOIN
            Empleados e ON d.id_responsable = e.id_empleado";

$params = []; // Para los parámetros de la sentencia preparada
$types = "";  // Para los tipos de los parámetros

if ($id_responsable_filtro !== null) {
    // Si hay un filtro, agregamos la cláusula WHERE
    $sql .= " WHERE d.id_responsable = ?";
    $params[] = $id_responsable_filtro;
    $types .= "i"; // 'i' para entero
}

// 4. Preparar y ejecutar la consulta (¡fundamental para seguridad!)
$stmt = $conn->prepare($sql);

if ($stmt === false) {
    die("Error al preparar la consulta: " . $conn->error);
}

if ($id_responsable_filtro !== null) {
    $stmt->bind_param($types, ...$params);
}

$stmt->execute();
$result = $stmt->get_result();

?>
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Listado de Dependencias</title>
    <link rel="stylesheet" href="css/style.css"> </head>
<body>
    <div class="container">
        <h1>Listado de Dependencias</h1>

        <p>Filtrar por Responsable (introduce ID de empleado):</p>
        <form action="listado_dependencias.php" method="GET">
            <input type="text" name="responsable" placeholder="Ej. 1, 2, etc." value="<?php echo htmlspecialchars($_GET['responsable'] ?? ''); ?>">
            <input type="submit" value="Filtrar">
            <a href="listado_dependencias.php">Mostrar Todos</a>
        </form>

        <?php if ($result->num_rows > 0): ?>
            <table>
                <thead>
                    <tr>
                        <th>ID Dependencia</th>
                        <th>Nombre Dependencia</th>
                        <th>Ubicación</th>
                        <th>Nombre Responsable</th>
                        <th>Cargo Responsable</th>
                    </tr>
                </thead>
                <tbody>
                    <?php while ($row = $result->fetch_assoc()): ?>
                        <tr>
                            <td><?php echo htmlspecialchars($row['id_dependencia']); ?></td>
                            <td><?php echo htmlspecialchars($row['nombre_dependencia']); ?></td>
                            <td><?php echo htmlspecialchars($row['ubicacion']); ?></td>
                            <td><?php echo htmlspecialchars($row['nombre_responsable'] ?? 'Sin Asignar'); ?></td>
                            <td><?php echo htmlspecialchars($row['cargo_responsable'] ?? 'N/A'); ?></td>
                        </tr>
                    <?php endwhile; ?>
                </tbody>
            </table>
        <?php else: ?>
            <p>No se encontraron dependencias con los criterios seleccionados.</p>
        <?php endif; ?>

        <?php
        $stmt->close();
        $conn->close();
        ?>
    </div>
</body>
</html>

Explicación del Manejo de la Variable Vacía:

  1. Captura de la Variable:

    • $id_responsable_filtro = $_GET['responsable'] ?? '';
    • Aquí usamos el operador de fusión de null (??) (disponible desde PHP 7) que asigna '' (cadena vacía) a $id_responsable_filtro si $_GET['responsable'] no está definido o es NULL. Esto es más conciso que isset().
    • Si el usuario ingresa un valor pero lo deja vacío en el formulario de búsqueda, también llegará como cadena vacía.
  2. Sanear y Validar (if (!empty($id_responsable_filtro))):

    • empty(): Verifica si la variable está vacía (cadena vacía, NULL, 0, false, array() vacío).
    • Si $id_responsable_filtro no está vacío, significa que el usuario ha proporcionado un valor para el filtro. En ese caso, se sanea el dato y se convierte a entero para asegurar que es un ID válido.
    • Si $id_responsable_filtro está vacío, lo convertimos a null. Este null será nuestra señal para que la consulta no incluya la cláusula WHERE de filtrado por responsable.
  3. Construcción Dinámica de la Consulta SQL:

    • La sentencia SELECT ... LEFT JOIN ... se define inicialmente sin la cláusula WHERE.
    • Solo si $id_responsable_filtro no es null (es decir, el usuario sí especificó un filtro), se concatena WHERE d.id_responsable = ? a la consulta SQL. Esto asegura que la cláusula WHERE solo se añada si es necesaria.
  4. Sentencias Preparadas ($stmt->bind_param(...)):

    • Es fundamental usar sentencias preparadas para todas las variables que provienen de la entrada del usuario. Esto previene ataques de inyección SQL.
    • $stmt->bind_param($types, ...$params); solo se ejecuta si $id_responsable_filtro no es null, ya que solo en ese caso $params y $types tendrán valores.

Cómo probarlo:

  • Guarda los archivos PHP en C:\xampp\htdocs\tu_proyecto\.
  • Abre tu navegador y ve a http://localhost/tu_proyecto/listado_dependencias.php. Deberías ver todas las dependencias.
  • Prueba a filtrar: http://localhost/tu_proyecto/listado_dependencias.php?responsable=1 (mostrará solo dependencias con responsable ID 1).
  • Prueba con un ID que no existe: http://localhost/tu_proyecto/listado_dependencias.php?responsable=999. No mostrará resultados.
  • Prueba con un valor vacío o ningún parámetro: http://localhost/tu_proyecto/listado_dependencias.php?responsable= o simplemente http://localhost/tu_proyecto/listado_dependencias.php. Debería mostrar todas las dependencias, incluyendo las que tienen NULL como responsable.

Este tutorial te ha proporcionado una comprensión profunda del LEFT JOIN para combinar tablas, cómo utilizar WHERE para filtrar datos de manera precisa, y una solución práctica y segura para manejar variables vacías en consultas SQL en aplicaciones web PHP.


Referencias

American Psychological Association. (2020). Publication Manual of the American Psychological Association (7th ed.). American Psychological Association.

MySQL. (s.f.). MySQL Reference Manual. Recuperado de https://dev.mysql.com/doc/refman/8.0/en/

PHP. (s.f.). PHP Manual. Recuperado de https://www.php.net/manual/es/


Comentarios