Apunte Práctico — SQL

Unidad V · Bases de Datos · UTN · Parcial 25-jun-2026

🔍

SELECT — Estructura base

El orden de las cláusulas es fijo

SELECT   col1, col2, ...          -- qué columnas mostrar
FROM     tabla                    -- de qué tabla
WHERE    condición                 -- filtro de filas (antes de agrupar)
GROUP BY col1                     -- agrupar resultados
HAVING   condición_de_grupo       -- filtro sobre grupos
ORDER BY col1 ASC|DESC            -- ordenar resultado
LIMIT    n;                       -- limitar cantidad de filas
💡
Orden de ejecución real: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
El SELECT se ejecuta después del WHERE y del GROUP BY — por eso no podés usar alias del SELECT en el WHERE.
-- DISTINCT: elimina filas duplicadas
SELECT DISTINCT color FROM Parts;

-- Alias de columna y tabla
SELECT p.sid AS proveedor_id, p.sname AS nombre
FROM   Proveedores p;

WHERE — Filtros y operadores

Filtra filas antes de cualquier agrupación

Comparación

  • = igual
  • <> o != distinto
  • < > <= >=

Lógicos

  • AND — ambas condiciones
  • OR — al menos una
  • NOT — negación

Rango y lista

  • BETWEEN a AND b
  • IN (v1, v2, ...)
  • NOT IN (...)

Texto y nulos

  • LIKE 'Bo%' — empieza con Bo
  • LIKE '%ing' — termina en ing
  • IS NULL / IS NOT NULL
SELECT * FROM Proveedores
WHERE  address = '221 Packer Street'
   OR  sid IN (1, 3, 5);

SELECT * FROM Empleados
WHERE  salary BETWEEN 80000 AND 120000;

SELECT * FROM Avion
WHERE  aname LIKE 'Boeing%';   -- cualquier avión Boeing

🔗

JOINs — Combinar tablas

El más usado en parciales: INNER JOIN

TipoQué devuelve
INNER JOINSolo filas que coinciden en ambas tablas
LEFT JOINTodas las de la izquierda + las que coinciden de la derecha (NULL si no hay)
RIGHT JOINTodas las de la derecha + coincidencias de la izquierda
CROSS JOINProducto cartesiano — todas las combinaciones
-- INNER JOIN (el más común)
SELECT p.sname, pt.pname, c.cost
FROM   Proveedores p
JOIN   Catalog     c  ON p.sid  = c.sid
JOIN   Parts       pt ON c.pid  = pt.pid
WHERE  pt.color = 'roja';

-- Self JOIN: comparar filas de la misma tabla
SELECT c1.sid AS sid1, c2.sid AS sid2
FROM   Catalog c1
JOIN   Catalog c2 ON c1.pid = c2.pid
WHERE  c1.cost > c2.cost;  -- ej 9: proveedor que cobra más que otro
⚠️
Truco parcial: JOIN solo = INNER JOIN. Siempre especificá el ON con la clave foránea correcta, no mezcles claves distintas.

Funciones de Agregación

Operan sobre grupos de filas

FunciónQué haceIgnora NULL
COUNT(*)Cuenta todas las filas del grupoNo
COUNT(col)Cuenta filas donde col no es NULL
SUM(col)Suma los valores
AVG(col)Promedio
MAX(col)Valor máximo
MIN(col)Valor mínimo
SELECT COUNT(*) AS total_empleados FROM Empleados;

SELECT SUM(salary) AS masa_salarial FROM Empleados;  -- ej j

SELECT MAX(salary) FROM Empleados;  -- mayor salario

📊

GROUP BY / HAVING

Agrupar filas y filtrar grupos

🔑
Regla: toda columna en el SELECT que no sea una función de agregación, debe estar en el GROUP BY.
WHERE filtra filas antes de agrupar · HAVING filtra grupos después de agrupar.
-- Cantidad de aviones por piloto (ej h, i)
SELECT   eid, COUNT(aid) AS cant_aviones
FROM     Certificados
GROUP BY eid
HAVING   COUNT(aid) = 3;          -- exactamente 3 aviones (ej i)

-- Presupuesto total por gerente (ej d, e)
SELECT   managerid, SUM(budget) AS total
FROM     Dept
GROUP BY managerid
HAVING   SUM(budget) > 5000000;   -- ej d

-- Partes con al menos 2 proveedores (ej 10)
SELECT   pid
FROM     Catalog
GROUP BY pid
HAVING   COUNT(DISTINCT sid) >= 2;

-- Promedio de edad por nivel (ej f)
SELECT   level, AVG(age) AS prom_edad
FROM     Student
GROUP BY level
HAVING   level <> 'JR';            -- ej g: excepto JR

🔄

Subconsultas

Consultas dentro de consultas — el corazón del parcial

IN / NOT IN

-- eids de pilotos Boeing (ej a)
SELECT eid FROM Certificados
WHERE  aid IN (
  SELECT aid FROM Avion WHERE aname LIKE 'Boeing%'
);

-- pilotos con rango > 3000 que NO son Boeing (ej e)
SELECT ename FROM Empleados
WHERE  eid IN (
    SELECT eid FROM Certificados JOIN Avion USING(aid)
    WHERE  cruisingrange > 3000
)
AND    eid NOT IN (
    SELECT eid FROM Certificados JOIN Avion USING(aid)
    WHERE  aname LIKE 'Boeing%'
);

EXISTS / NOT EXISTS

Más eficiente que IN cuando la subconsulta es correlacionada. Devuelve verdadero si la subconsulta retorna al menos una fila.

-- Estudiantes no inscriptos en ninguna clase (ej j)
SELECT sname FROM Student s
WHERE NOT EXISTS (
  SELECT 1 FROM Inscripto i
  WHERE  i.snum = s.snum    -- correlación: referencia a la fila externa
);

Subconsulta escalar (devuelve un solo valor)

-- partes más caras de Yosemite Sham (ej 11)
SELECT pid FROM Catalog c
JOIN   Proveedores p ON c.sid = p.sid
WHERE  p.sname = 'Yosemite Sham'
  AND  c.cost = (
    SELECT MAX(c2.cost) FROM Catalog c2
    JOIN  Proveedores p2 ON c2.sid = p2.sid
    WHERE p2.sname = 'Yosemite Sham'
  );

ALL / ANY

-- aviones que pueden cubrir distancia Bonn→Madras (ej c)
SELECT aid FROM Avion
WHERE  cruisingrange >= (
  SELECT distance FROM Vuelos
  WHERE  from_city = 'Bonn' AND to_city = 'Madras'
);

UNION · INTERSECT · EXCEPT

Operaciones de conjuntos — mismas columnas y tipos

OperadorEquivale a ARElimina duplicados
UNION∪ Unión
UNION ALL∪ sin dedupNo
INTERSECT∩ Intersección
EXCEPT− Diferencia
-- sids que proveen parte roja O parte verde (ej 2)
SELECT sid FROM Catalog JOIN Parts USING(pid) WHERE color='roja'
UNION
SELECT sid FROM Catalog JOIN Parts USING(pid) WHERE color='verde';

-- sids que proveen roja Y verde (ej 4)
SELECT sid FROM Catalog JOIN Parts USING(pid) WHERE color='roja'
INTERSECT
SELECT sid FROM Catalog JOIN Parts USING(pid) WHERE color='verde';
⚠️
Las columnas de ambas partes deben ser compatibles en cantidad y tipo. Usá alias si los nombres difieren.

÷

División en SQL — "TODOS"

No existe como operador — se implementa con NOT EXISTS doble

🔑
Cada vez que el enunciado diga "TODOS" o "CADA" — pensá en división.
En SQL se implementa con doble NOT EXISTS: "no existe ningún X para el que no existe Y".

Patrón: ¿Quién provee CADA parte? (ej 5)

-- Traducción lógica: "no existe ninguna parte que este proveedor NO provea"
SELECT s.sid
FROM   Proveedores s
WHERE NOT EXISTS (          -- no existe ninguna parte...
  SELECT 1 FROM Parts pt
  WHERE NOT EXISTS (        -- ...que NO esté en el catálogo de este proveedor
    SELECT 1 FROM Catalog c
    WHERE  c.sid = s.sid
      AND  c.pid = pt.pid
  )
);

Patrón: ¿Quién provee CADA parte roja? (ej 6)

SELECT s.sid
FROM   Proveedores s
WHERE NOT EXISTS (
  SELECT 1 FROM Parts pt
  WHERE  pt.color = 'roja'    -- solo las rojas
    AND NOT EXISTS (
      SELECT 1 FROM Catalog c
      WHERE  c.sid = s.sid AND c.pid = pt.pid
    )
);

Alternativa con COUNT (más legible)

-- Proveedores que tienen en catálogo TODAS las partes rojas
SELECT sid
FROM   Catalog c
JOIN   Parts   p USING(pid)
WHERE  p.color = 'roja'
GROUP BY sid
HAVING COUNT(DISTINCT pid) = (
  SELECT COUNT(*) FROM Parts WHERE color = 'roja'
);

🏆

Máximo y 2do mayor salario

Patrones que siempre caen en parcial

El mayor salario (ej f)

-- Opción 1: con MAX
SELECT eid FROM Empleados
WHERE  salary = (SELECT MAX(salary) FROM Empleados);

-- Opción 2: no existe nadie que gane más
SELECT eid FROM Empleados e
WHERE NOT EXISTS (
  SELECT 1 FROM Empleados e2
  WHERE  e2.salary > e.salary
);

El 2do mayor salario (ej g)

-- Opción 1: MAX excluyendo el máximo
SELECT eid FROM Empleados
WHERE  salary = (
  SELECT MAX(salary) FROM Empleados
  WHERE  salary < (SELECT MAX(salary) FROM Empleados)
);

-- Opción 2: existe exactamente 1 que gana más
SELECT eid FROM Empleados e
WHERE (
  SELECT COUNT(DISTINCT salary) FROM Empleados
  WHERE salary > e.salary
) = 1;

El mayor COUNT (ej h — más aviones)

SELECT eid
FROM   Certificados
GROUP BY eid
HAVING COUNT(aid) = (
  SELECT MAX(cnt) FROM (
    SELECT COUNT(aid) AS cnt
    FROM   Certificados
    GROUP BY eid
  ) AS sub
);

🎯

Patrones frecuentes en parciales

"Empleados que trabajan en Hardware Y en Software" (ej 4a)

SELECT e.ename, e.age
FROM   Emp e
WHERE  e.eid IN (
  SELECT w.eid FROM Works w JOIN Dept d USING(did) WHERE d.dname='Hardware'
)
AND    e.eid IN (
  SELECT w.eid FROM Works w JOIN Dept d USING(did) WHERE d.dname='Software'
);

"Solo departamentos con X" — gerentes que NO tienen ninguno fuera (ej 4b)

-- Gerentes que administran SOLO deptos con budget > 1M
SELECT managerid FROM Dept
GROUP BY managerid
HAVING MIN(budget) > 1000000;   -- si el mínimo > 1M, todos > 1M

"Estudiantes con mayor número de clases" (ej 3i)

SELECT s.sname
FROM   Student s JOIN Inscripto i USING(snum)
GROUP BY s.snum, s.sname
HAVING COUNT(*) = (
  SELECT MAX(cnt) FROM (
    SELECT COUNT(*) AS cnt FROM Inscripto GROUP BY snum
  ) sub
);

"Dos clases a la misma hora" — self join (ej 3c)

SELECT DISTINCT s.sname
FROM   Student   s
JOIN   Inscripto i1 ON s.snum = i1.snum
JOIN   Inscripto i2 ON s.snum = i2.snum AND i1.cname <> i2.cname
JOIN   Class     c1 ON i1.cname = c1.name
JOIN   Class     c2 ON i2.cname = c2.name
WHERE  c1.meets_at = c2.meets_at;

✏️

DML — Modificar datos

-- INSERT: agregar filas
INSERT INTO Parts (pid, pname, color) VALUES (109, 'Tornillo', 'azul');

-- INSERT múltiple
INSERT INTO Parts (pid, pname, color) VALUES
  (110, 'Perno',   'roja'),
  (111, 'Resorte', 'verde');

-- UPDATE: modificar filas existentes
UPDATE Empleados
SET    salary = salary * 1.10    -- aumento del 10%
WHERE  eid = 101;

-- DELETE: eliminar filas
DELETE FROM Catalog
WHERE  cost > 500;
⚠️
UPDATE y DELETE sin WHERE afectan todas las filas. Siempre revisá el WHERE antes de ejecutar.

🏗️

DDL — Definir estructura

-- CREATE TABLE con restricciones
CREATE TABLE Jugador (
  noJugador  INT          PRIMARY KEY,
  nombre     VARCHAR(50) NOT NULL,
  edad       INT          CHECK (edad >= 16),
  equipoId   INT          REFERENCES Equipo(noEquipo)
);

-- ALTER: agregar columna
ALTER TABLE Jugador ADD posicion VARCHAR(30);

-- ALTER: eliminar columna
ALTER TABLE Jugador DROP COLUMN posicion;

-- DROP: eliminar tabla completa (¡irreversible!)
DROP TABLE Jugador;
DROP TABLE IF EXISTS Jugador;  -- sin error si no existe

Tipos de datos en PostgreSQL

TipoUso
INTEGER / INTEnteros
REAL / FLOATDecimales de punto flotante
NUMERIC(p,s)Decimal exacto (p dígitos, s decimales)
VARCHAR(n)Texto variable hasta n caracteres
TEXTTexto sin límite
BOOLEANTRUE / FALSE
DATEFecha (YYYY-MM-DD)
TIMEHora (HH:MM:SS)
TIMESTAMPFecha + hora

BD · UTN · Martín Malgor · 2026