Módulo 26: SQL y el entorno Oracle

Objetivos del módulo

  • Saber qué es SQL, de dónde viene y cómo ha evolucionado a través de sus estándares ANSI/ISO
  • Distinguir entre el SQL estándar y los dialectos de cada fabricante
  • Clasificar las sentencias SQL en sus sublenguajes: DDL, DML, DCL y TCL
  • Conocer las reglas léxicas del lenguaje: mayúsculas, terminadores, comentarios y literales
  • Entender la arquitectura básica de Oracle: instancia, base de datos contenedora (CDB) y conectable (PDB), tablespaces, usuarios y esquemas
  • Instalar Oracle AI Database Free con Docker (o con el instalador nativo) o usar el servicio en línea FreeSQL
  • Conectarte con SQL Developer, la extensión de VS Code, SQLcl, SQL*Plus o DBeaver
  • Crear el usuario del curso, cargar el esquema EMPRESA y conocer en detalle sus tablas
  • Lanzar tus primeras consultas, usar la tabla DUAL y los comandos más útiles de SQL*Plus/SQLcl
  • Comprender la configuración regional (NLS) y cómo afecta a fechas, números y mensajes

1. ¿Qué es SQL?

📘 Concepto: SQL (Structured Query Language, lenguaje de consulta estructurado) es el lenguaje estándar para definir, manipular, consultar y controlar los datos de una base de datos relacional. Es un lenguaje declarativo: describes qué datos quieres obtener o modificar, no cómo hay que recorrer los ficheros para conseguirlos.

Durante los bloques 2 y 3 del curso has diseñado bases de datos sobre el papel: diagramas E/R, esquemas relacionales, normalización. A partir de este módulo vas a convertir esos diseños en objetos reales dentro de un SGBD y a trabajar con sus datos. El vehículo para todo ello es SQL.

Compara estas dos formas de pedir “los empleados del departamento 30”:

Enfoque procedimental (lo que harías con ficheros, módulo 01):
    abrir fichero EMPLEADOS
    mientras no sea fin de fichero:
        leer registro
        si registro.dept_id = 30 entonces mostrar registro
    cerrar fichero

Enfoque declarativo (SQL):
    SELECT * FROM empleados WHERE dept_id = 30;

En SQL no dices si se usa un índice, en qué orden se leen los bloques del disco ni cómo se combinan las tablas: de eso se encarga el optimizador del SGBD (módulo 03), que elige el plan de ejecución más eficiente.

💡 Consejo: Se pronuncia de dos maneras: deletreado (“ese-cu-ele”) o “sícuel”, herencia de su nombre original, SEQUEL. Ambas son correctas.

1.1. SQL y el modelo relacional

SQL es la implementación práctica del modelo relacional de Codd (módulo 16) y del álgebra relacional (módulo 18), aunque con algunas diferencias importantes que conviene tener presentes desde el principio:

Modelo relacional teórico SQL real
Una relación es un conjunto: no hay tuplas repetidas Una tabla es una multiconjunto (bag): puede haber filas repetidas si no hay clave primaria
No existe orden entre tuplas ni entre atributos Las columnas tienen un orden de definición; las filas no tienen orden salvo con ORDER BY
Lógica bivaluada (verdadero/falso) Lógica trivaluada por culpa de NULL: verdadero, falso y desconocido
Relación, tupla, atributo Tabla, fila, columna
Dominio Tipo de dato (y, desde Oracle 23ai, dominio SQL)

2. Historia de SQL y sus estándares

2.1. Los orígenes: de SEQUEL a SQL

  • 1970: Edgar F. Codd, investigador de IBM, publica “A Relational Model of Data for Large Shared Data Banks”, que define el modelo relacional.
  • 1974: en el laboratorio de IBM en San José, Donald D. Chamberlin y Raymond F. Boyce presentan SEQUEL (Structured English Query Language), un lenguaje de consulta con aspecto de inglés para el prototipo relacional System R.
  • 1976-1977: aparece SEQUEL/2. Por un conflicto de marca registrada (SEQUEL ya pertenecía a otra empresa), el nombre se acorta a SQL.
  • 1979: la empresa Relational Software Inc. (fundada por Larry Ellison, Bob Miner y Ed Oates, y que años después se llamaría Oracle Corporation) lanza Oracle V2, el primer SGBD relacional comercial basado en SQL. Se adelantó a la propia IBM.
  • 1981-1983: IBM comercializa SQL/DS y después DB2.
  • 1986: el ANSI (instituto de normalización de EE. UU.) publica el primer estándar de SQL; la ISO lo adopta en 1987.

2.2. Los estándares ANSI/ISO

El estándar oficial es la norma ISO/IEC 9075 (“Information technology — Database languages — SQL”), dividida hoy en varias partes (Framework, Foundation, SQL/PSM, SQL/XML, SQL/JSON, SQL/PGQ…).

Versión Año Nombre informal Principales novedades
SQL-86 1986 (ISO 1987) SQL1 Primera norma: SELECT, INSERT, UPDATE, DELETE, CREATE TABLE, GRANT. Muy incompleta
SQL-89 1989 SQL1 revisado Restricciones de integridad: PRIMARY KEY, FOREIGN KEY, CHECK, DEFAULT
SQL-92 1992 SQL2 La gran revisión: JOIN explícito (INNER, LEFT/RIGHT/FULL OUTER), CASE, CAST, tipos DATE, TIME, TIMESTAMP, INTERVAL, VARCHAR, INTERSECT y EXCEPT, dominios, ALTER/DROP, tablas temporales, INFORMATION_SCHEMA, restricciones diferibles. Tres niveles de conformidad (Entry, Intermediate, Full)
SQL:1999 1999 SQL3 Consultas recursivas (WITH RECURSIVE), disparadores, procedimientos (SQL/PSM), tipo BOOLEAN, ROLLUP, CUBE, GROUPING SETS, tipos definidos por el usuario y orientación a objetos, BLOB/CLOB, expresiones regulares (SIMILAR TO), roles
SQL:2003 2003   Funciones de ventana (analíticas), MERGE, secuencias, columnas identidad, columnas generadas, BIGINT, SQL/XML
SQL:2006 2006   Ampliación de SQL/XML e integración con XQuery
SQL:2008 2008   TRUNCATE TABLE, FETCH FIRST n ROWS ONLY, disparadores INSTEAD OF
SQL:2011 2011   Datos temporales: periodos y tablas versionadas por el sistema; mejoras en ventanas y OFFSET
SQL:2016 2016   SQL/JSON, reconocimiento de patrones de filas (MATCH_RECOGNIZE), LISTAGG, funciones de tabla polimórficas
SQL:2019 2019   Parte 15, SQL/MDA: matrices multidimensionales
SQL:2023 2023   Parte 16, SQL/PGQ (consultas sobre grafos de propiedades), tipo de dato JSON, ANY_VALUE, GREATEST/LEAST, UNIQUE NULLS [NOT] DISTINCT
timeline
    title Evolución de SQL
    1974 : SEQUEL (IBM, System R)
    1979 : Oracle V2, primer SGBD SQL comercial
    1986 : SQL-86 (ANSI)
    1992 : SQL-92 (SQL2)
    1999 : SQL 1999 (SQL3)
    2003 : SQL 2003 ventanas, MERGE, identidad
    2011 : SQL 2011 datos temporales
    2016 : SQL 2016 JSON
    2023 : SQL 2023 grafos (PGQ)

📘 Concepto: Ningún SGBD implementa el estándar completo, y todos añaden extensiones propias. Por eso se habla de grado de conformidad: un producto cumple la parte obligatoria (Core SQL) y una selección de características opcionales.


3. SQL estándar y dialectos

Cada fabricante habla un dialecto de SQL: comparte el núcleo estándar (SELECT ... FROM ... WHERE, CREATE TABLE, INSERT…) pero tiene tipos de datos, funciones y sintaxis propios. Además, cada uno ofrece un lenguaje procedimental que extiende SQL con variables, bucles y bloques (que no es SQL propiamente dicho).

SGBD Dialecto / extensión procedimental Algunas particularidades
Oracle Database Oracle SQL / PL/SQL VARCHAR2, NUMBER, tabla DUAL, la cadena vacía '' es NULL, CONNECT BY, ROWNUM
Microsoft SQL Server T-SQL (Transact-SQL) SELECT TOP n, concatenación con +, IDENTITY, GETDATE()
PostgreSQL SQL de PostgreSQL / PL/pgSQL Muy fiel al estándar, LIMIT/OFFSET, SERIAL, tipos de matriz, ILIKE
MySQL / MariaDB SQL de MySQL / procedimientos almacenados LIMIT, AUTO_INCREMENT, comillas invertidas para identificadores, motores de almacenamiento
IBM Db2 SQL de Db2 / SQL PL FETCH FIRST, compatibilidad opcional con Oracle
SQLite SQL de SQLite Tipado dinámico (“afinidad de tipos”), base de datos en un único fichero

Un ejemplo de la misma consulta (“los 3 empleados mejor pagados”) en varios dialectos:

-- Estándar SQL:2008 (válido en Oracle 12c o superior, PostgreSQL, Db2)
SELECT nombre, salario FROM empleados ORDER BY salario DESC FETCH FIRST 3 ROWS ONLY;

-- MySQL, MariaDB, PostgreSQL, SQLite
SELECT nombre, salario FROM empleados ORDER BY salario DESC LIMIT 3;

-- SQL Server
SELECT TOP 3 nombre, salario FROM empleados ORDER BY salario DESC;

-- Oracle clásico (anterior a 12c)
SELECT * FROM (SELECT nombre, salario FROM empleados ORDER BY salario DESC) WHERE ROWNUM <= 3;

💡 Consejo: Cuando exista una forma estándar y otra propietaria de hacer lo mismo, este curso te enseñará ambas, pero en tus proyectos prefiere la estándar: tu SQL será más portable y lo entenderá cualquier profesional, venga del SGBD que venga.

⚠️ Importante: PL/SQL no forma parte de este curso. Todo lo que verás aquí es SQL puro (sentencias que el SGBD ejecuta de una en una). La programación dentro de la base de datos (bloques BEGIN ... END, procedimientos, funciones, cursores y disparadores) se estudia en el Curso de Oracle PL/SQL.


4. Los sublenguajes de SQL

Las sentencias SQL se agrupan según su finalidad. La clasificación clásica distingue cuatro sublenguajes:

Sublenguaje Significado Para qué sirve Sentencias principales Módulos
DDL Data Definition Language, lenguaje de definición de datos Crear, modificar y eliminar la estructura: tablas, restricciones, vistas, índices, secuencias… CREATE, ALTER, DROP, TRUNCATE, RENAME, COMMENT, PURGE, FLASHBACK TABLE 19-22
DML Data Manipulation Language, lenguaje de manipulación de datos Consultar y modificar el contenido de las tablas SELECT, INSERT, UPDATE, DELETE, MERGE 23-32
DCL Data Control Language, lenguaje de control de datos Gestionar la seguridad: usuarios, privilegios y roles GRANT, REVOKE (y en Oracle CREATE USER, CREATE ROLE…) 33
TCL Transaction Control Language, control de transacciones Confirmar o deshacer grupos de cambios COMMIT, ROLLBACK, SAVEPOINT, SET TRANSACTION 31

Algunos autores separan la consulta SELECT en un quinto sublenguaje, el DQL (Data Query Language). La documentación de Oracle usa una clasificación algo distinta: DDL, DML, control de transacciones, control de sesión (ALTER SESSION, SET ROLE), control del sistema (ALTER SYSTEM) y SQL embebido.

⚠️ Importante: En Oracle, toda sentencia DDL hace un COMMIT implícito antes y después de ejecutarse. Si tienes cambios pendientes de un INSERT y ejecutas un CREATE TABLE, esos cambios quedan confirmados y ya no podrás deshacerlos con ROLLBACK. Lo comprobarás con un ejemplo real en el módulo 24.

✏️ Ejercicio: Antes de seguir, clasifica estas sentencias: CREATE INDEX, MERGE, REVOKE, SAVEPOINT, TRUNCATE, COMMENT ON TABLE. (Soluciones: DDL, DML, DCL, TCL, DDL, DDL).


5. Características léxicas del lenguaje

5.1. Mayúsculas y minúsculas

  • Las palabras clave (SELECT, FROM…) y los identificadores sin comillas (nombres de tablas y columnas) no distinguen mayúsculas de minúsculas. Oracle los convierte internamente a MAYÚSCULAS.
  • Los literales de texto (entre comillas simples) sí distinguen mayúsculas: 'Ana' no es igual a 'ana'.

💻 Ejemplo: las palabras clave e identificadores pueden escribirse como quieras…

select NOMBRE, Salario from EMPLEADOS where EMP_ID = 100;
NOMBRE                            SALARIO
------------------------------ ----------
Ana                                  6500

1 fila seleccionada.

…pero el contenido de los textos se compara exactamente:

SELECT nombre FROM empleados WHERE nombre = 'ana';
ninguna fila seleccionada
SELECT nombre FROM empleados WHERE nombre = 'Ana';
NOMBRE
------------------------------
Ana

1 fila seleccionada.

💡 Consejo: La convención de este curso (y de muchas empresas) es escribir las palabras reservadas en MAYÚSCULAS y los identificadores en minúsculas. No lo exige Oracle, pero hace el código mucho más legible.

5.2. Formato libre y terminador

SQL es de formato libre: los espacios, tabuladores y saltos de línea entre palabras son indiferentes. Una sentencia puede ocupar una o muchas líneas. Para que la herramienta sepa dónde termina cada sentencia se usa el terminador ; (punto y coma). En SQL*Plus y SQLcl también puedes terminar una sentencia escribiendo una barra / sola en la línea siguiente, que significa “ejecuta lo que hay en el búfer”.

SELECT nombre
FROM   empleados
WHERE  emp_id = 100;

SELECT nombre FROM empleados WHERE emp_id = 101
/
NOMBRE
------------------------------
Ana

1 fila seleccionada.


NOMBRE
------------------------------
Luis

1 fila seleccionada.

⚠️ Importante: El ; no forma parte de la sentencia SQL: es una señal para la herramienta cliente. Por eso, cuando escribas SQL dentro de un programa (Java, Python, PHP…) normalmente no debes incluirlo.

5.3. Comentarios

-- Comentario de una línea: desde los dos guiones hasta el final de la línea

/* Comentario de varias líneas:
   útil para cabeceras de scripts
   o para desactivar temporalmente un bloque de código */

SELECT nombre      -- también puede ir al final de una línea de código
FROM   empleados
WHERE  emp_id = 100;

💡 Consejo: En SQLPlus evita poner comentarios /* ... */ en una línea que empiece por /* justo después de una sentencia sin terminar, y no dejes líneas en blanco dentro de una sentencia: SQLPlus interpreta una línea en blanco como fin de la sentencia (puedes cambiarlo con SET SQLBLANKLINES ON).

5.4. Literales

Tipo Ejemplos Observaciones
Texto 'Madrid', 'O''Brien' Comillas simples. Una comilla dentro del texto se duplica
Texto alternativo q'[O'Brien]' Mecanismo de Oracle para no duplicar comillas
Número 42, -3.75, 1.5E3 El separador decimal en el código es siempre el punto
Fecha DATE '2026-10-05' Literal ANSI, formato fijo AAAA-MM-DD
Marca de tiempo TIMESTAMP '2026-10-05 17:45:30'  
Intervalo INTERVAL '3' DAY, INTERVAL '2-6' YEAR TO MONTH  
Booleano TRUE, FALSE Solo desde Oracle 23ai
Nulo NULL Ausencia de valor

⚠️ Importante: Las comillas dobles no delimitan textos: delimitan identificadores (nombres de objetos). Si escribes WHERE nombre = "Ana", Oracle busca una columna llamada Ana y falla con ORA-00904: "Ana": invalid identifier. Lo verás en la sección 15.


6. Oracle como SGBD

Oracle Database es el SGBD relacional comercial más extendido en grandes organizaciones (banca, administración pública, telecomunicaciones). Su versión actual es Oracle AI Database 26ai (número de versión técnico 23.26), sucesora de 23ai y versión de soporte a largo plazo junto a la todavía muy utilizada 19c.

Versión Año Hito principal
Oracle V2 1979 Primer SGBD relacional comercial con SQL
Oracle 8i / 9i 1999 / 2001 Integración con Internet y Java
Oracle 10g / 11g 2003 / 2007 Computación en grid, autoajuste
Oracle 12c 2013 Arquitectura multitenant (CDB/PDB), columnas identidad
Oracle 18c / 19c 2018 / 2019 19c es la versión de soporte a largo plazo más implantada
Oracle 21c 2021 Tipo JSON nativo; la arquitectura multitenant pasa a ser obligatoria
Oracle 23ai 2024 BOOLEAN, IF [NOT] EXISTS, dominios SQL, VECTOR, DB_DEVELOPER_ROLE
Oracle AI Database 26ai 2025 Sustituye a 23ai; añade, entre otras cosas, aserciones SQL

6.1. Instancia y base de datos

En Oracle se distingue con mucho cuidado entre dos conceptos que en el lenguaje coloquial se confunden:

📘 Concepto: La base de datos es el conjunto de ficheros físicos en disco (ficheros de datos, ficheros de control y ficheros de redo). La instancia es el conjunto de estructuras de memoria (la SGA, System Global Area) y procesos en segundo plano que se arrancan en un servidor para dar acceso a esos ficheros.

Una analogía: la base de datos es la biblioteca (los libros en las estanterías); la instancia es el personal y el mostrador que te atiende. Sin personal, los libros siguen ahí, pero nadie puede consultarlos.

6.2. Arquitectura multitenant: CDB y PDB

Desde 12c (y de forma obligatoria desde 21c), una base de datos Oracle es una base de datos contenedora (CDB, Container Database) que aloja una o varias bases de datos conectables (PDB, Pluggable Database):

flowchart TB
    subgraph CDB["CDB: FREE (instancia FREE)"]
        ROOT["CDB$ROOT<br/>diccionario común, usuarios comunes"]
        SEED["PDB$SEED<br/>plantilla de solo lectura"]
        PDB1["FREEPDB1<br/>tu base de datos de trabajo"]
    end
    LIS["Listener<br/>puerto 1521"] --> ROOT
    LIS --> PDB1
    CLI["Tu cliente<br/>(SQL Developer, SQLcl...)"] -->|"servicio FREEPDB1"| LIS
  • CDB$ROOT: el contenedor raíz, con los metadatos comunes. No se trabaja aquí con datos de aplicación.
  • PDB$SEED: plantilla para crear nuevas PDB.
  • PDB (en la edición Free, FREEPDB1): una base de datos completa a ojos de las aplicaciones, con sus propios usuarios, tablas y datos. Aquí es donde trabajarás.

Cada contenedor se publica como un servicio al que te conectas por red a través del listener (el “recepcionista” que escucha conexiones, por defecto en el puerto 1521). Puedes comprobar a qué contenedor y servicio estás conectado:

SELECT SYS_CONTEXT('USERENV', 'CON_NAME')     AS contenedor,
       SYS_CONTEXT('USERENV', 'SERVICE_NAME') AS servicio
FROM   dual;
CONTENEDOR   SERVICIO
------------ ------------
FREEPDB1     freepdb1

1 fila seleccionada.

6.3. Tablespaces

📘 Concepto: Un tablespace es una unidad lógica de almacenamiento que agrupa uno o varios ficheros de datos. Las tablas e índices se guardan en tablespaces, no directamente en ficheros.

Tablespace Contenido
SYSTEM El diccionario de datos
SYSAUX Componentes auxiliares del sistema
UNDO (UNDOTBS1) Información para deshacer transacciones y lecturas consistentes
TEMP Espacio temporal para ordenaciones y tablas temporales
USERS Tablespace habitual para los datos de los usuarios

Un usuario solo puede crear tablas en un tablespace en el que tenga cuota (espacio asignado). Lo verás al crear el usuario del curso.

6.4. Usuarios y esquemas

📘 Concepto: Un esquema es la colección de objetos (tablas, vistas, índices, secuencias…) que pertenecen a un usuario. En Oracle, usuario y esquema son lo mismo: al crear el usuario CURSO se crea automáticamente el esquema CURSO, y todo lo que ese usuario cree quedará dentro de él.

El nombre completo de un objeto es esquema.objeto, por ejemplo curso.empleados. Si omites el esquema, Oracle busca primero en el tuyo. Así, dos alumnos pueden tener cada uno su propia tabla empleados sin conflicto: ana.empleados y luis.empleados son tablas distintas.

Usuarios administrativos que encontrarás en cualquier instalación:

Usuario Papel
SYS Propietario del diccionario de datos. Máximo poder; se conecta AS SYSDBA. No lo uses para trabajar
SYSTEM Administrador con privilegios de DBA. Lo usarás solo para crear tu usuario de trabajo
PDBADMIN Administrador local de la PDB (lo crea la instalación de la edición Free)

6.5. La cadena de conexión

Para conectarte necesitas cinco datos: usuario, contraseña, máquina, puerto y servicio. El formato abreviado más cómodo es el de Easy Connect (EZConnect):

usuario/contraseña@máquina:puerto/servicio

curso/Curso2026@localhost:1521/FREEPDB1        ← Oracle en tu propio equipo (Docker)
curso/Curso2026@192.168.1.50:1521/FREEPDB1     ← Oracle en otro equipo de la red
curso/Curso2026@//localhost:1521/freepdb1      ← variante con // inicial (también válida)

Si omites el puerto se usa el 1521. Existe un formato más largo (descriptor TNS) que suele guardarse con un alias en el fichero tnsnames.ora; por ejemplo, dentro del contenedor Docker de Oracle existe el alias FREEPDB1, y por eso allí basta con escribir curso/Curso2026@FREEPDB1.

FREEPDB1 =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = FREEPDB1)
    )
  )

⚠️ Importante: Conéctate siempre al servicio de la PDB (FREEPDB1), no al de la CDB (FREE). Si creas tu usuario en el contenedor raíz, Oracle exige que su nombre empiece por C## (usuarios comunes) y rechazará CREATE USER curso con ORA-65096: invalid common user or role name.


7. Instalar Oracle para el curso

Oracle AI Database 26ai Free es una edición gratuita y completa (para desarrollo, aprendizaje e incluso producción a pequeña escala) con estos límites: hasta 2 hilos de CPU, 2 GB de RAM y 12 GB de datos de usuario. Más que suficiente para este curso. Tienes tres caminos:

Opción Ventajas Inconvenientes Recomendada para
Docker con la imagen oficial Instalación en minutos, idéntica en Windows, Linux y macOS (Intel); fácil de borrar y recrear Hay que instalar Docker; consume unos 2-3 GB de RAM mientras está arrancado La mayoría de alumnos (opción recomendada)
Instalador nativo (Windows o Linux) Sin capas intermedias; servicio integrado en el sistema Instalación más lenta y más difícil de desinstalar limpiamente Equipos donde no se puede usar Docker
FreeSQL en el navegador Nada que instalar; funciona desde cualquier equipo, incluso una tableta Depende de Internet y de la disponibilidad del servicio; menos control Practicar desde cualquier sitio o como plan B

7.1. Opción 1: Docker (recomendada)

Paso 1. Instala Docker. En Windows y macOS instala Docker Desktop (en Windows con el motor WSL 2); en Linux, Docker Engine desde el gestor de paquetes de tu distribución. Comprueba que funciona:

docker --version

Paso 2. Descarga la imagen oficial desde el registro de contenedores de Oracle (es pública y no requiere iniciar sesión):

docker pull container-registry.oracle.com/database/free:latest

La imagen ocupa varios GB; la descarga tardará unos minutos según tu conexión.

Paso 3. Crea y arranca el contenedor:

docker run -d --name oracle-free `
    -p 1521:1521 `
    -e ORACLE_PWD=MiClaveSegura2026 `
    -v oracle-datos:/opt/oracle/oradata `
    container-registry.oracle.com/database/free:latest

(En Bash o en la terminal de macOS/Linux, sustituye las comillas invertidas de final de línea por \, o escribe todo en una sola línea.)

Opción Significado
-d Ejecuta el contenedor en segundo plano
--name oracle-free Nombre del contenedor, para referirte a él después
-p 1521:1521 Publica el puerto del listener: el 1521 de tu equipo apunta al 1521 del contenedor
-e ORACLE_PWD=... Contraseña de SYS, SYSTEM y PDBADMIN. Si no la indicas, se genera una aleatoria que aparece en los registros
-v oracle-datos:/opt/oracle/oradata Guarda los ficheros de datos en un volumen de Docker: tus tablas sobreviven aunque borres y recrees el contenedor

Paso 4. Espera a que la base de datos esté lista. El primer arranque tarda unos minutos. Sigue los registros:

docker logs -f oracle-free

Cuando veas este mensaje, ya puedes conectarte (pulsa Ctrl+C para dejar de seguir los registros; el contenedor sigue funcionando):

Starting Oracle Net Listener.
Oracle Net Listener started.
Starting Oracle Database instance FREE.
Oracle Database instance FREE started.
Pluggable Database FREEPDB1 opened.
...
#########################
DATABASE IS READY TO USE!
#########################

Paso 5. Prueba la conexión con el SQL*Plus que trae el propio contenedor:

docker exec -it oracle-free sqlplus system/MiClaveSegura2026@FREEPDB1

Datos de conexión que usarás desde cualquier herramienta de tu equipo:

Dato Valor
Máquina (host) localhost
Puerto 1521
Servicio FREEPDB1 (la PDB). El servicio FREE corresponde a la CDB
Usuario administrador system con la contraseña de ORACLE_PWD

Comandos de gestión del día a día:

docker stop oracle-free      # parar (los datos se conservan)
docker start oracle-free     # volver a arrancar (tarda menos que la primera vez)
docker ps -a                 # ver el estado de los contenedores
docker exec oracle-free ./setPassword.sh NuevaClave2026   # cambiar la contraseña de los administradores

⚠️ Importante: Existe una variante más pequeña de la imagen, container-registry.oracle.com/database/free:latest-lite, que ocupa mucho menos porque elimina componentes opcionales. Funciona igual para este curso, pero no incluye el tablespace USERS en la PDB. Si la usas, antes de crear tu usuario conéctate como SYSTEM a FREEPDB1 y créalo:

CREATE TABLESPACE users DATAFILE '/opt/oracle/oradata/FREE/FREEPDB1/users01.dbf' SIZE 100M AUTOEXTEND ON NEXT 50M;

💡 Consejo: Si tu equipo tiene un procesador Apple Silicon (M1, M2…) o poca memoria, consulta en el registro de Oracle si hay imagen para tu arquitectura; si no, usa la opción 3 (FreeSQL).

7.2. Opción 2: instalador nativo

Desde la página de descargas de Oracle AI Database Free puedes obtener:

  • Linux: paquetes RPM para Oracle Linux 8 y 9 (y compatibles: RHEL, Rocky, AlmaLinux). Se instala con dnf install y se configura con el script /etc/init.d/oracle-free-26ai configure, que pide la contraseña de los administradores.
  • Windows (64 bits): un fichero ZIP con un instalador gráfico (setup.exe). Durante la instalación eliges la carpeta y la contraseña; al terminar, la base de datos queda registrada como servicio de Windows que arranca con el sistema.

Los datos de conexión son los mismos que con Docker: localhost, puerto 1521, servicio FREEPDB1. La instalación nativa incluye SQL*Plus, que puedes lanzar desde una terminal:

sqlplus system/MiClaveSegura2026@localhost:1521/FREEPDB1

7.3. Opción 3: FreeSQL en el navegador

FreeSQL (freesql.com) es el servicio gratuito de Oracle para escribir y ejecutar SQL en una base de datos Oracle real desde el navegador, sin instalar nada. Es el sucesor del antiguo Oracle Live SQL (livesql.oracle.com) y ofrece:

  • Un editor de SQL con resultados en tabla y un esquema personal donde crear tus tablas.
  • Tutoriales y scripts de la comunidad.
  • Con una cuenta gratuita de Oracle, tu trabajo se conserva entre sesiones y puedes consultar los datos de conexión de tu esquema para usarlo también desde SQLcl o VS Code.

Para cargar EMPRESA en FreeSQL, abre cada script del curso, copia su contenido en el editor y ejecútalo como script.

⚠️ Importante: FreeSQL es un servicio compartido y gratuito: ha tenido caídas puntuales y puede cambiar sus condiciones. Úsalo como complemento o plan B, pero para trabajar con tranquilidad durante todo el curso es preferible tener tu propia base de datos (opción 1 o 2). En FreeSQL no tienes privilegios de administrador, así que puedes saltarte la sección 9 (creación del usuario).


8. Herramientas cliente

El SGBD es un servidor: no tiene ventanas. Para hablar con él necesitas un cliente que envíe tus sentencias y muestre los resultados.

Herramienta Tipo Puntos fuertes
SQL Developer Aplicación de escritorio gratuita de Oracle (Windows, macOS, Linux) Navegador de objetos, editor con autocompletado, modelador de datos, exportación de resultados
Extensión SQL Developer para VS Code Extensión oficial de Oracle para Visual Studio Code Si ya usas VS Code; es la línea de evolución de SQL Developer
SQLcl Línea de comandos moderna de Oracle (requiere Java) Compatible con los scripts de SQL*Plus; añade historial, autocompletado, INFO, DDL, formatos de salida
SQL*Plus Línea de comandos clásica, incluida con la base de datos Está en todas las instalaciones; imprescindible para administración y scripts
DBeaver Cliente universal de código abierto (Community Edition) Un mismo programa para Oracle, MySQL, PostgreSQL…

8.1. Crear una conexión

En todas las herramientas gráficas el formulario es parecido:

Campo Valor para el curso
Nombre de la conexión curso@FREEPDB1 (el que quieras)
Usuario / contraseña curso / Curso2026 (o system para la sección 9)
Tipo de conexión Básica (Basic)
Nombre del host localhost
Puerto 1521
Nombre del servicio FREEPDB1 (marca la opción Service name, no SID)
  • SQL Developer: en el panel Conexiones, botón + (Nueva conexión). Rellena los campos, pulsa Probar (debe aparecer “Estado: Correcto”) y después Conectar. Se abre una hoja de trabajo SQL.
  • VS Code: instala la extensión Oracle SQL Developer Extension for VS Code, abre su panel lateral, pulsa Crear conexión y rellena los mismos datos.
  • DBeaver: Base de datos > Nueva conexión > Oracle. En Database elige Service Name y escribe FREEPDB1. La primera vez te ofrecerá descargar el controlador JDBC de Oracle.
  • SQLcl y SQL*Plus: la conexión se escribe en la propia orden, con el formato EZConnect:
sql curso/Curso2026@localhost:1521/FREEPDB1        # SQLcl
sqlplus curso/Curso2026@localhost:1521/FREEPDB1    # SQL*Plus

💡 Consejo: En las herramientas de línea de comandos, si escribes solo sqlplus curso@localhost:1521/FREEPDB1, te pedirá la contraseña sin mostrarla en pantalla ni dejarla en el historial de la terminal. Es más seguro.


9. Crear el usuario del curso

Nunca trabajes con SYS ni con SYSTEM: un error con un administrador puede dañar la base de datos. Conéctate una sola vez como SYSTEM a la PDB FREEPDB1 y crea tu usuario de trabajo.

9.1. En Oracle 23ai / 26ai

-- Conectado como SYSTEM a FREEPDB1
CREATE USER curso IDENTIFIED BY Curso2026
    DEFAULT TABLESPACE users
    TEMPORARY TABLESPACE temp
    QUOTA UNLIMITED ON users;

GRANT DB_DEVELOPER_ROLE TO curso;

⚠️ Importante: Disponible desde Oracle 23ai. El rol predefinido DB_DEVELOPER_ROLE agrupa los privilegios que necesita un desarrollador (crear sesión, tablas, vistas, secuencias, sinónimos, procedimientos, disparadores, tipos, dominios…) sin dar poderes de administrador. En versiones anteriores no existe: usa la alternativa de la sección 9.2.

Cláusula Significado
IDENTIFIED BY Curso2026 Contraseña. Desde 11g distingue mayúsculas y minúsculas
DEFAULT TABLESPACE users Dónde se guardarán sus tablas si no se indica otro tablespace
TEMPORARY TABLESPACE temp Espacio para ordenaciones (es el valor por defecto; se pone por claridad)
QUOTA UNLIMITED ON users Espacio que puede ocupar en USERS. Sin cuota, podría crear tablas pero no insertar filas: ORA-01950: no privileges on tablespace 'USERS'

9.2. En Oracle 19c o 21c

-- Conectado como SYSTEM a la PDB (en 19c suele llamarse ORCLPDB o XEPDB1 en la edición XE)
CREATE USER curso IDENTIFIED BY Curso2026
    DEFAULT TABLESPACE users
    QUOTA UNLIMITED ON users;

GRANT CREATE SESSION,
      CREATE TABLE,
      CREATE VIEW,
      CREATE SEQUENCE,
      CREATE SYNONYM,
      CREATE MATERIALIZED VIEW,
      CREATE PROCEDURE,
      CREATE TRIGGER,
      CREATE TYPE
TO curso;

Los dos últimos bloques del curso (vistas, secuencias, sinónimos, vistas materializadas) necesitan los privilegios correspondientes; CREATE PROCEDURE y CREATE TRIGGER te servirán si continúas con el curso de PL/SQL. En el módulo 37 estudiarás a fondo usuarios, privilegios y roles.

💡 Consejo: Si te equivocas y quieres empezar de cero, como SYSTEM puedes borrar el usuario con todos sus objetos: DROP USER curso CASCADE;. Úsalo con cuidado: no se puede deshacer.

Después, desconéctate de SYSTEM y crea una conexión nueva con el usuario curso. Comprueba quién eres:

SHOW USER
USER es "CURSO"

10. Ejecutar scripts y cargar el esquema EMPRESA

10.1. Qué es un script

Un script es un fichero de texto (normalmente con extensión .sql) que contiene una secuencia de sentencias SQL y comandos de la herramienta, cada una con su terminador. Ejecutarlo equivale a escribir todas esas sentencias una tras otra.

Herramienta Cómo ejecutar un script
SQL*Plus / SQLcl @ruta\al\fichero.sql (o START fichero.sql). @@fichero.sql busca el fichero en la misma carpeta que el script que lo llama
SQL Developer Abre el fichero (o pega su contenido) en la hoja de trabajo y pulsa F5 (Ejecutar script). La salida aparece en la pestaña Salida de script. También puedes escribir @C:\ruta\fichero.sql en la hoja de trabajo y pulsar F5
VS Code (extensión) Abre el fichero .sql, asócialo a tu conexión y usa la acción Ejecutar script
DBeaver En el editor SQL, Ejecutar script (Alt+X)

⚠️ Importante: En SQL Developer distingue F5 (ejecutar todo como script, con salida de texto como SQL*Plus) de Ctrl+Intro (ejecutar solo la sentencia donde está el cursor, con resultado en rejilla). Para cargar un script completo usa siempre F5.

10.2. Los tres scripts de EMPRESA

El curso proporciona tres scripts en la carpeta recursos:

Script Qué hace
empresa_crear.sql Crea las 9 tablas con todas sus restricciones (DDL)
empresa_datos.sql Inserta los datos de ejemplo y hace COMMIT (DML)
empresa_borrar.sql Borra las 9 tablas en orden inverso a sus dependencias, para empezar de cero

Descárgalos en una carpeta de tu equipo (por ejemplo C:\curso-bd\), conéctate como curso y ejecuta:

@C:\curso-bd\empresa_crear.sql
@C:\curso-bd\empresa_datos.sql

La salida de empresa_crear.sql es una línea por tabla:

Tabla creada.

Tabla creada.

...

y la de empresa_datos.sql, una línea por fila insertada, terminando con:

1 fila creada.

Confirmación terminada.

Para comprobar que todo está en su sitio, cuenta las filas de cada tabla:

SELECT 'EMPLEADOS' AS tabla, COUNT(*) AS filas FROM empleados
UNION ALL SELECT 'DEPARTAMENTOS', COUNT(*) FROM departamentos
UNION ALL SELECT 'CLIENTES',      COUNT(*) FROM clientes
UNION ALL SELECT 'CATEGORIAS',    COUNT(*) FROM categorias
UNION ALL SELECT 'PRODUCTOS',     COUNT(*) FROM productos
UNION ALL SELECT 'PEDIDOS',       COUNT(*) FROM pedidos
UNION ALL SELECT 'LINEAS_PEDIDO', COUNT(*) FROM lineas_pedido
UNION ALL SELECT 'PROYECTOS',     COUNT(*) FROM proyectos
UNION ALL SELECT 'ASIGNACIONES',  COUNT(*) FROM asignaciones;
TABLA              FILAS
------------- ----------
EMPLEADOS             24
DEPARTAMENTOS          7
CLIENTES              14
CATEGORIAS            11
PRODUCTOS             20
PEDIDOS               24
LINEAS_PEDIDO         51
PROYECTOS              5
ASIGNACIONES          14

9 filas seleccionadas.

💡 Consejo: Cuando en algún ejercicio estropees los datos (borres filas, cambies salarios…), recarga el esquema ejecutando los tres scripts en este orden: empresa_borrar.sql, empresa_crear.sql, empresa_datos.sql. En medio minuto vuelves al estado original.

⚠️ Importante: Los datos contienen tildes y eñes. Si tu cliente de línea de comandos no está configurado en UTF-8, los textos se guardarán corruptos (verás cosas como DIRECCI��N). Es un problema real y frecuente: en la sección 14.4 aprenderás a evitarlo con NLS_LANG. SQL Developer, VS Code y DBeaver usan Unicode y no tienen este problema.


11. El esquema EMPRESA en detalle

EMPRESA modela una compañía que vende productos informáticos: tiene departamentos y empleados (organizados en una jerarquía de jefes), clientes atendidos por comerciales, un catálogo de productos clasificados en categorías jerárquicas, pedidos con sus líneas y proyectos internos en los que participan los empleados.

11.1. Diagrama del esquema físico

erDiagram
    DEPARTAMENTOS |o--o{ EMPLEADOS : "dept_id"
    EMPLEADOS |o--o{ EMPLEADOS : "jefe_id"
    EMPLEADOS |o--o{ CLIENTES : "comercial_id"
    CLIENTES ||--o{ PEDIDOS : "cliente_id"
    EMPLEADOS |o--o{ PEDIDOS : "empleado_id"
    PEDIDOS ||--o{ LINEAS_PEDIDO : "pedido_id"
    PRODUCTOS ||--o{ LINEAS_PEDIDO : "producto_id"
    CATEGORIAS ||--o{ PRODUCTOS : "categoria_id"
    CATEGORIAS |o--o{ CATEGORIAS : "categoria_padre_id"
    DEPARTAMENTOS |o--o{ PROYECTOS : "dept_id"
    EMPLEADOS ||--o{ ASIGNACIONES : "emp_id"
    PROYECTOS ||--o{ ASIGNACIONES : "proyecto_id"

    DEPARTAMENTOS {
        NUMBER dept_id PK
        VARCHAR2 nombre UK "NOT NULL"
        VARCHAR2 localidad
        NUMBER presupuesto "CHECK >= 0"
    }
    EMPLEADOS {
        NUMBER emp_id PK
        VARCHAR2 nombre "NOT NULL"
        VARCHAR2 apellidos "NOT NULL"
        VARCHAR2 email UK "NOT NULL"
        VARCHAR2 telefono
        DATE fecha_alta "NOT NULL"
        VARCHAR2 oficio "NOT NULL"
        NUMBER salario "NOT NULL, CHECK > 0"
        NUMBER comision "CHECK >= 0"
        NUMBER jefe_id FK
        NUMBER dept_id FK
    }
    CLIENTES {
        NUMBER cliente_id PK
        VARCHAR2 nombre "NOT NULL"
        VARCHAR2 nif UK "NOT NULL"
        VARCHAR2 email
        VARCHAR2 telefono
        VARCHAR2 ciudad
        VARCHAR2 provincia
        DATE fecha_alta "DEFAULT SYSDATE"
        NUMBER limite_credito "DEFAULT 3000"
        NUMBER comercial_id FK
    }
    CATEGORIAS {
        NUMBER categoria_id PK
        VARCHAR2 nombre UK "NOT NULL"
        NUMBER categoria_padre_id FK
    }
    PRODUCTOS {
        NUMBER producto_id PK
        VARCHAR2 nombre "NOT NULL"
        VARCHAR2 descripcion
        NUMBER categoria_id FK "NOT NULL"
        NUMBER precio "NOT NULL"
        NUMBER stock "DEFAULT 0"
        DATE fecha_alta
        CHAR descatalogado "S o N"
    }
    PEDIDOS {
        NUMBER pedido_id PK
        NUMBER cliente_id FK "NOT NULL"
        NUMBER empleado_id FK
        DATE fecha_pedido "DEFAULT SYSDATE"
        DATE fecha_envio
        VARCHAR2 estado "DEFAULT PENDIENTE"
    }
    LINEAS_PEDIDO {
        NUMBER pedido_id PK, FK
        NUMBER num_linea PK
        NUMBER producto_id FK "NOT NULL"
        NUMBER cantidad "CHECK > 0"
        NUMBER precio_unitario "NOT NULL"
        NUMBER descuento "0 a 100"
    }
    PROYECTOS {
        NUMBER proyecto_id PK
        VARCHAR2 nombre UK "NOT NULL"
        NUMBER dept_id FK
        DATE fecha_inicio "NOT NULL"
        DATE fecha_fin
        NUMBER presupuesto
    }
    ASIGNACIONES {
        NUMBER emp_id PK, FK
        NUMBER proyecto_id PK, FK
        DATE fecha_asignacion "NOT NULL"
        NUMBER horas "DEFAULT 0"
        VARCHAR2 funcion
    }

11.2. Esquema relacional

Con la notación del bloque 3 (clave primaria subrayada, claves ajenas en cursiva, (N) = admite nulos):

DEPARTAMENTOS (dept_id, nombre, localidad (N), presupuesto (N))

  • UK: nombre

EMPLEADOS (emp_id, nombre, apellidos, email, telefono (N), fecha_alta, oficio, salario, comision (N), jefe_id (N), dept_id (N))

  • UK: email · jefe_id → EMPLEADOS(emp_id) · dept_id → DEPARTAMENTOS(dept_id)

CLIENTES (cliente_id, nombre, nif, email (N), telefono (N), ciudad (N), provincia (N), fecha_alta, limite_credito (N), comercial_id (N))

  • UK: nif · comercial_id → EMPLEADOS(emp_id)

CATEGORIAS (categoria_id, nombre, categoria_padre_id (N))

  • UK: nombre · categoria_padre_id → CATEGORIAS(categoria_id)

PRODUCTOS (producto_id, nombre, descripcion (N), categoria_id, precio, stock, fecha_alta (N), descatalogado)

  • categoria_id → CATEGORIAS(categoria_id)

PEDIDOS (pedido_id, cliente_id, empleado_id (N), fecha_pedido, fecha_envio (N), estado)

  • cliente_id → CLIENTES(cliente_id) · empleado_id → EMPLEADOS(emp_id)

LINEAS_PEDIDO (*pedido_id*, num_linea, producto_id, cantidad, precio_unitario, descuento)

  • pedido_id → PEDIDOS(pedido_id) con borrado en cascada · producto_id → PRODUCTOS(producto_id)

PROYECTOS (proyecto_id, nombre, dept_id (N), fecha_inicio, fecha_fin (N), presupuesto (N))

  • UK: nombre · dept_id → DEPARTAMENTOS(dept_id)

ASIGNACIONES (*emp_id*, *proyecto_id*, fecha_asignacion, horas (N), funcion (N))

  • emp_id → EMPLEADOS(emp_id) · proyecto_id → PROYECTOS(proyecto_id) con borrado en cascada

11.3. Las tablas una a una

Tabla Filas Clave primaria Qué representa y detalles a recordar
DEPARTAMENTOS 7 dept_id (10, 20… 70) Departamentos con su localidad y presupuesto. El 70 (INVESTIGACIÓN) no tiene empleados y su presupuesto es nulo
EMPLEADOS 24 emp_id (100-160) jefe_id apunta a otro empleado (autorrelación): la directora general (100) no tiene jefe. El 160 (becario) no tiene departamento. Solo los vendedores tienen comisión (uno de ellos, 0)
CLIENTES 14 cliente_id (1001-1014) comercial_id es el vendedor asignado. 1009 y 1013 no tienen comercial; 1013 y 1014 no tienen pedidos
CATEGORIAS 11 categoria_id (1-11) Jerarquía de tres niveles mediante categoria_padre_id: Informática > Periféricos > Monitores
PRODUCTOS 20 producto_id (1-20) descatalogado vale 'S' o 'N'. El 14 está descatalogado y nunca se ha vendido
PEDIDOS 24 pedido_id (5001-5024) 5014 y 5023 son pedidos en línea sin empleado. fecha_envio es nula en los pendientes y en el cancelado
LINEAS_PEDIDO 51 (pedido_id, num_linea) Clave compuesta: es una entidad débil de PEDIDOS; si se borra un pedido, sus líneas se borran en cascada
PROYECTOS 5 proyecto_id (1-5) El 5 no tiene empleados asignados y su presupuesto es nulo
ASIGNACIONES 14 (emp_id, proyecto_id) Relación N:M entre empleados y proyectos con atributos propios: fecha, horas y función

Puedes ver la estructura de cualquier tabla con DESCRIBE (sección 13). Por ejemplo:

DESCRIBE empleados
 Nombre                                    ¿Nulo?   Tipo
 ----------------------------------------- -------- ----------------------------
 EMP_ID                                    NOT NULL NUMBER(6)
 NOMBRE                                    NOT NULL VARCHAR2(30)
 APELLIDOS                                 NOT NULL VARCHAR2(50)
 EMAIL                                     NOT NULL VARCHAR2(60)
 TELEFONO                                           VARCHAR2(15)
 FECHA_ALTA                                NOT NULL DATE
 OFICIO                                    NOT NULL VARCHAR2(30)
 SALARIO                                   NOT NULL NUMBER(8,2)
 COMISION                                           NUMBER(8,2)
 JEFE_ID                                            NUMBER(6)
 DEPT_ID                                            NUMBER(4)
DESCRIBE lineas_pedido
 Nombre                                    ¿Nulo?   Tipo
 ----------------------------------------- -------- ----------------------------
 PEDIDO_ID                                 NOT NULL NUMBER(8)
 NUM_LINEA                                 NOT NULL NUMBER(3)
 PRODUCTO_ID                               NOT NULL NUMBER(6)
 CANTIDAD                                  NOT NULL NUMBER(5)
 PRECIO_UNITARIO                           NOT NULL NUMBER(8,2)
 DESCUENTO                                 NOT NULL NUMBER(4,2)

Observa que pedido_id y num_linea aparecen como NOT NULL aunque en el script no se escribió esa restricción para ellas: forman la clave primaria, y una clave primaria nunca admite nulos. Lo estudiarás en el módulo 24, donde además analizaremos empresa_crear.sql línea a línea.

Y el número de restricciones por tipo que tiene el esquema (P = clave primaria, U = única, R = clave ajena, C = CHECK y NOT NULL):

SELECT constraint_type, COUNT(*)
FROM   user_constraints
GROUP BY constraint_type
ORDER BY 1;
C   COUNT(*)
- ----------
C         41
P          9
R         12
U          5

4 filas seleccionadas.

12. La tabla DUAL y las primeras consultas

12.1. La tabla DUAL

En SQL de Oracle, toda sentencia SELECT necesitaba (hasta 23ai) una cláusula FROM. Para calcular expresiones que no dependen de ninguna tabla se usa DUAL, una tabla especial del diccionario con una sola columna y una sola fila:

SELECT * FROM dual;
D
-
X

1 fila seleccionada.
DESCRIBE dual
 Nombre                                    ¿Nulo?   Tipo
 ----------------------------------------- -------- ----------------------------
 DUMMY                                              VARCHAR2(1)

Como tiene exactamente una fila, cualquier expresión calculada “sobre DUAL” devuelve un único resultado. Es la “calculadora” de Oracle:

SELECT 7 * 6 AS resultado FROM dual;
 RESULTADO
----------
        42

1 fila seleccionada.
SELECT UPPER('hola mundo') AS mayus, LENGTH('Oracle') AS longitud FROM dual;
MAYUS        LONGITUD
---------- ----------
HOLA MUNDO          6

1 fila seleccionada.
SELECT USER, SYSDATE FROM dual;
USER         SYSDATE
------------ ----------
CURSO        03/10/2026

1 fila seleccionada.

USER devuelve el usuario conectado y SYSDATE la fecha y hora actuales del servidor (la hora no se ve porque el formato de fecha de la sesión solo muestra día, mes y año; sección 14).

⚠️ Importante: Disponible desde Oracle 23ai: la cláusula FROM es opcional y puedes escribir directamente SELECT 7 * 6 AS resultado; (devuelve lo mismo). En 19c y 21c es obligatorio FROM dual. Como FROM dual funciona en todas las versiones, es lo que verás en el curso.

12.2. Tus primeras consultas sobre EMPRESA

Todavía no has estudiado SELECT (lo harás a fondo desde el módulo 27), pero su forma básica se entiende sola. Prueba estas consultas para familiarizarte con los datos.

Antes de empezar, pide a SQL*Plus que muestre los valores nulos con un texto visible (por defecto los deja en blanco y no se distinguen de un texto vacío o de espacios). Es la configuración que usa todo el curso:

SET NULL '(nulo)'

Todos los departamentos (el asterisco significa “todas las columnas”):

SELECT * FROM departamentos;
   DEPT_ID NOMBRE                LOCALIDAD    PRESUPUESTO
---------- --------------------- ------------ -----------
        10 DIRECCIÓN             Madrid            500000
        20 VENTAS                Madrid            350000
        30 INFORMÁTICA           Valencia          420000
        40 CONTABILIDAD          Sevilla           150000
        50 MARKETING             Barcelona         200000
        60 LOGÍSTICA             Zaragoza          180000
        70 INVESTIGACIÓN         Bilbao       (nulo)

7 filas seleccionadas.

El presupuesto de INVESTIGACIÓN es un valor nulo (NULL), un “dato desconocido o no aplicable”. El texto (nulo) no está guardado en la tabla: es solo cómo lo presenta SQL*Plus con SET NULL.

Nombre, oficio y salario de los empleados de Informática:

SELECT nombre, apellidos, oficio, salario
FROM   empleados
WHERE  dept_id = 30;
NOMBRE     APELLIDOS          OFICIO              SALARIO
---------- ------------------ ---------------- ----------
Marta      Sánchez Gil        DIRECTOR               5000
Pablo      Moreno Herrera     ANALISTA               3500
Raquel     Molina Prieto      PROGRAMADOR            2800
Iván       Castro Méndez      PROGRAMADOR            2600
Sara       Ortiz Delgado      PROGRAMADOR            2400
Hugo       Rubio Marín        TÉCNICO SISTEMAS       2500

6 filas seleccionadas.

Los empleados que cobran más de 4000 €, de mayor a menor salario:

SELECT nombre, apellidos, salario
FROM   empleados
WHERE  salario > 4000
ORDER BY salario DESC;
NOMBRE     APELLIDOS             SALARIO
---------- ------------------ ----------
Ana        García López             6500
Marta      Sánchez Gil              5000
Luis       Martínez Ruiz            4800
Laura      Fernández Moreno         4300
Pedro      Gómez Díaz               4200

5 filas seleccionadas.

La jerarquía de categorías (las de nivel superior no tienen padre):

SELECT * FROM categorias;
CATEGORIA_ID NOMBRE                   CATEGORIA_PADRE_ID
------------ ------------------------ ------------------
           1 Informática              (nulo)
           2 Portátiles                                1
           3 Ordenadores sobremesa                     1
           4 Periféricos                               1
           5 Ratones y teclados                        4
           6 Monitores                                 4
           7 Electrónica              (nulo)
           8 Telefonía                                 7
           9 Audio                                     7
          10 Oficina                  (nulo)
          11 Mobiliario                               10

11 filas seleccionadas.

💡 Consejo: En los listados de este curso los anchos de columna se han ajustado con el comando COLUMN ... FORMAT (sección 13) para que quepan en la página. Si en tu pantalla las columnas salen más anchas o partidas en varias líneas, no es un error: es la presentación por defecto de SQL*Plus.


13. Comandos útiles de SQL*Plus y SQLcl

Además de sentencias SQL, SQL*Plus y SQLcl entienden comandos propios para controlar la sesión y el formato de la salida. No son SQL: no los entiende el servidor sino la herramienta, y no necesitan ; al final (aunque lo toleran). SQL Developer ejecuta la mayoría de ellos cuando lanzas un script con F5.

Comando Abreviatura Para qué sirve
DESCRIBE tabla DESC Muestra las columnas, si admiten nulos y su tipo
SHOW USER   Muestra el usuario conectado
SHOW ALL   Muestra el valor de todas las variables de SQL*Plus
SET LINESIZE n SET LIN Ancho de línea en caracteres (por defecto 80)
SET PAGESIZE n SET PAGES Filas por página antes de repetir cabeceras (0 = sin cabeceras ni saltos)
SET FEEDBACK ON/OFF SET FEED Mostrar u ocultar el mensaje “n filas seleccionadas”
SET HEADING ON/OFF SET HEA Mostrar u ocultar las cabeceras de columna
SET NULL 'texto'   Texto con el que se muestran los valores nulos (por defecto, en blanco). El curso usa SET NULL '(nulo)'
SET TIMING ON SET TIMI Muestra el tiempo que tarda cada sentencia
COLUMN col FORMAT A20 COL Ancho de una columna de texto
COLUMN col FORMAT 99G990D00   Formato de una columna numérica (G = separador de miles, D = decimal)
COLUMN col HEADING 'Texto'   Cambia la cabecera
CLEAR COLUMNS   Borra todos los formatos de columna
SPOOL fichero.txt … SPOOL OFF   Copia toda la salida en un fichero
PROMPT texto PRO Escribe un mensaje (útil en scripts)
CONNECT usuario/clave@servicio CONN Cambia de usuario sin salir
HOST orden ! en Linux Ejecuta una orden del sistema operativo
EXIT QUIT Sale de la herramienta (confirmando la transacción pendiente)

💻 Ejemplo: formatear una consulta con COLUMN:

COLUMN nombre    FORMAT A10
COLUMN apellidos FORMAT A18
COLUMN salario   FORMAT 99G990D00
COLUMN comision  FORMAT 9990 HEADING 'Comisión'

SELECT nombre, apellidos, salario, comision
FROM   empleados
WHERE  dept_id = 20;
NOMBRE     APELLIDOS             SALARIO Comisión
---------- ------------------ ---------- --------
Luis       Martínez Ruiz        4.800,00 (nulo)
Carmen     Ruiz Ortega          2.200,00      500
David      Torres Romero        2.100,00      800
Elena      Navarro Castro       2.300,00      300
Sergio     Jiménez Vidal        1.900,00        0
Lucía      Romero Serrano       1.700,00 (nulo)

6 filas seleccionadas.

Fíjate en dos detalles: los separadores de miles y decimales siguen la configuración regional española (punto para miles, coma para decimales, sección 14), y la comisión de Sergio es un cero, que no es lo mismo que un nulo: Sergio es vendedor y no ha generado comisión; Luis y Lucía no tienen comisión.

💻 Ejemplo: SET PAGESIZE pequeño repite las cabeceras cada pocas filas:

SET PAGESIZE 6
SELECT emp_id, nombre FROM empleados WHERE dept_id = 30;
    EMP_ID NOMBRE
---------- ------------------------------
       102 Marta
       120 Pablo
       121 Raquel

    EMP_ID NOMBRE
---------- ------------------------------
       122 Iván
       123 Sara
       124 Hugo

6 filas seleccionadas.

💻 Ejemplo: guardar un listado en un fichero con SPOOL:

SET PAGESIZE 100
SPOOL C:\curso-bd\departamentos.txt
SELECT dept_id, nombre FROM departamentos WHERE dept_id <= 30;
SPOOL OFF

El fichero departamentos.txt contiene exactamente lo que se vio en pantalla entre SPOOL y SPOOL OFF:

   DEPT_ID NOMBRE
---------- ----------------------------------------
        10 DIRECCIÓN
        20 VENTAS
        30 INFORMÁTICA

3 filas seleccionadas.

💡 Consejo: Un buen comienzo para tus scripts de práctica (es la configuración con la que se han obtenido todas las salidas del curso) es:

SET LINESIZE 200 PAGESIZE 100 FEEDBACK ON

SET NULL '(nulo)'

ALTER SESSION SET NLS_LANGUAGE = 'SPANISH' NLS_TERRITORY = 'SPAIN';

ALTER SESSION SET NLS_DATE_FORMAT = 'DD/MM/YYYY';

En SQL Developer, el texto para los nulos se configura en Herramientas > Preferencias > Base de datos > Avanzada > Mostrar nulo como.

13.1. Extras de SQLcl

SQLcl acepta todos los comandos anteriores y añade otros muy cómodos:

Comando de SQLcl Qué hace
INFO tabla Como DESCRIBE, pero añade comentarios, índices, restricciones y estadísticas
DDL tabla Muestra la sentencia CREATE TABLE completa de una tabla existente
SET SQLFORMAT ansiconsole Ajusta automáticamente el ancho de las columnas al contenido
SET SQLFORMAT csv / json Saca los resultados en CSV o JSON
HISTORY Historial de sentencias ejecutadas
Tabulador Autocompletado de nombres de tablas y columnas

14. Configuración regional (NLS)

14.1. Qué es NLS

📘 Concepto: NLS (National Language Support) es el conjunto de parámetros que adaptan Oracle a un idioma y a un país: formato de fechas, nombres de meses y días, separadores numéricos, símbolo de moneda, reglas de ordenación alfabética e idioma de los mensajes.

Hay tres niveles, de menor a mayor prioridad: los parámetros de la base de datos (fijados al crearla), los del cliente (variable de entorno NLS_LANG o preferencias de la herramienta) y los de la sesión (ALTER SESSION), que prevalecen sobre los demás mientras dure la conexión.

Parámetro Qué controla Ejemplo España Ejemplo EE. UU.
NLS_LANGUAGE Idioma de mensajes, nombres de días y meses, ordenación por defecto SPANISH AMERICAN
NLS_TERRITORY Formatos por defecto de fecha y números, moneda, primer día de la semana SPAIN AMERICA
NLS_DATE_FORMAT Formato de fecha por defecto (derivado del territorio) DD/MM/RR DD-MON-RR
NLS_DATE_LANGUAGE Idioma de los nombres de días y meses SPANISH AMERICAN
NLS_NUMERIC_CHARACTERS Separador decimal y de miles ,. .,
NLS_CURRENCY Símbolo de moneda local € $
NLS_SORT Reglas de ordenación de textos SPANISH BINARY

Consulta los valores de tu sesión:

COLUMN parameter FORMAT A25
COLUMN value     FORMAT A30
SELECT parameter, value
FROM   nls_session_parameters
WHERE  parameter IN ('NLS_LANGUAGE', 'NLS_TERRITORY', 'NLS_DATE_FORMAT', 'NLS_DATE_LANGUAGE',
                     'NLS_NUMERIC_CHARACTERS', 'NLS_CURRENCY', 'NLS_SORT');
PARAMETER                 VALUE
------------------------- ------------------------------
NLS_LANGUAGE              SPANISH
NLS_TERRITORY             SPAIN
NLS_CURRENCY              €
NLS_NUMERIC_CHARACTERS    ,.
NLS_DATE_FORMAT           DD/MM/RR
NLS_DATE_LANGUAGE         SPANISH
NLS_SORT                  SPANISH

7 filas seleccionadas.

Y los de la base de datos, que en la edición Free están en inglés americano y con juego de caracteres Unicode:

SELECT parameter, value
FROM   nls_database_parameters
WHERE  parameter IN ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET', 'NLS_LANGUAGE',
                     'NLS_TERRITORY', 'NLS_DATE_FORMAT');
PARAMETER                 VALUE
------------------------- ------------------------------
NLS_DATE_FORMAT           DD-MON-RR
NLS_NCHAR_CHARACTERSET    AL16UTF16
NLS_CHARACTERSET          AL32UTF8
NLS_TERRITORY             AMERICA
NLS_LANGUAGE              AMERICAN

5 filas seleccionadas.

14.2. Efecto de NLS_DATE_FORMAT

Las fechas se guardan internamente en un formato binario de 7 bytes; el formato solo afecta a cómo se muestran (y a cómo se interpretan los textos que conviertes en fechas). Observa la misma fecha de alta con distintos formatos de sesión:

SELECT fecha_alta FROM empleados WHERE emp_id = 100;
FECHA_AL
--------
15/01/10

1 fila seleccionada.

Con el formato español por defecto (DD/MM/RR) el año se muestra con dos cifras: ¿2010 o 1910? Cámbialo:

ALTER SESSION SET NLS_DATE_FORMAT = 'DD/MM/YYYY';
SELECT fecha_alta FROM empleados WHERE emp_id = 100;
Sesión modificada.


FECHA_ALTA
----------
15/01/2010

1 fila seleccionada.
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';
SELECT fecha_alta, SYSDATE FROM empleados WHERE emp_id = 100;
FECHA_ALTA                    SYSDATE
----------------------------- -----------------------------
15-ENE-2010 00:00:00          03-OCT-2026 22:47:26

1 fila seleccionada.

Ahora se ve algo que estaba oculto: el tipo DATE de Oracle siempre guarda también la hora. Las fechas de alta se insertaron sin hora (medianoche, 00:00:00), pero SYSDATE lleva la hora exacta. Lo estudiarás en el módulo 23.

⚠️ Importante: No escribas nunca una fecha como texto confiando en el formato de la sesión. Esta consulta funciona con DD/MM/YYYY…

SELECT nombre FROM empleados WHERE fecha_alta = '15/01/2010';

…pero falla en cuanto otra persona (o tu propio programa) tenga otro formato:

ALTER SESSION SET NLS_DATE_FORMAT = 'MM/DD/YYYY';
SELECT nombre FROM empleados WHERE fecha_alta = '15/01/2010';
SELECT nombre FROM empleados WHERE fecha_alta = '15/01/2010'
                                                *
ERROR en línea 1:
ORA-01843: An invalid month was specified.
Help: https://docs.oracle.com/error-help/db/ora-01843/

El literal ANSI DATE 'AAAA-MM-DD' funciona siempre, sea cual sea la configuración:

SELECT nombre FROM empleados WHERE fecha_alta = DATE '2010-01-15';
NOMBRE
------------------------------
Ana

1 fila seleccionada.

Por eso el script empresa_datos.sql usa DATE '...' en todas las fechas.

14.3. Efecto de NLS_LANGUAGE y NLS_TERRITORY

El idioma cambia los nombres de días y meses:

SELECT TO_CHAR(DATE '2026-10-05', 'Day, DD "de" Month "de" YYYY') AS fecha_larga FROM dual;
FECHA_LARGA
-----------------------------------
Lunes    , 05 de Octubre    de 2026

1 fila seleccionada.
ALTER SESSION SET NLS_LANGUAGE = 'AMERICAN';
SELECT TO_CHAR(DATE '2026-10-05', 'Day, DD Month YYYY') AS fecha_larga FROM dual;
FECHA_LARGA
----------------------------
Monday   , 05 October   2026

1 fila seleccionada.

(Los espacios de relleno se eliminan con el modificador FM, que verás en el módulo 28.) El territorio cambia los separadores y la moneda:

ALTER SESSION SET NLS_TERRITORY = 'AMERICA';
SELECT 1234.56 AS numero, TO_CHAR(1234.56, 'L9G999D99') AS formateado FROM dual;
    NUMERO FORMATEADO
---------- -------------------
   1234.56           $1,234.56

1 fila seleccionada.
ALTER SESSION SET NLS_LANGUAGE = 'SPANISH' NLS_TERRITORY = 'SPAIN';
SELECT 1234.56 AS numero, TO_CHAR(1234.56, 'L9G999D99') AS formateado FROM dual;
    NUMERO FORMATEADO
---------- -------------------
   1234,56         €1.234,56

1 fila seleccionada.

⚠️ Importante: ALTER SESSION SET NLS_TERRITORY reinicia el formato de fecha al valor por defecto del territorio (DD/MM/RR en España, DD-MON-RR en EE. UU.). Si cambias el territorio, vuelve a fijar después tu NLS_DATE_FORMAT.

💡 Consejo: Aunque la sesión muestre los decimales con coma, en el código SQL los números se escriben siempre con punto: WHERE salario > 1500.50. La coma en el código separa elementos de una lista.

14.4. NLS_LANG: el cliente y el juego de caracteres

Las herramientas de línea de comandos (SQL*Plus, y en parte SQLcl) toman su configuración de la variable de entorno NLS_LANG, con el formato IDIOMA_TERRITORIO.JUEGO_DE_CARACTERES:

# Windows (PowerShell), para la terminal actual
$env:NLS_LANG = "SPANISH_SPAIN.AL32UTF8"
# Linux / macOS / Docker
export NLS_LANG=SPANISH_SPAIN.AL32UTF8
# Ejecutar SQL*Plus del contenedor Docker con el cliente en UTF-8
docker exec -it -e NLS_LANG=SPANISH_SPAIN.AL32UTF8 oracle-free sqlplus curso/Curso2026@FREEPDB1

La parte del juego de caracteres es la más delicada: le dice a Oracle en qué codificación le llegan los textos desde el cliente. Si no coincide con la real, Oracle “traduce” mal los caracteres no ingleses y los guarda corruptos para siempre. Es justamente lo que ocurre si cargas empresa_datos.sql (que está en UTF-8) desde un SQL*Plus sin NLS_LANG, que por defecto usa ASCII de 7 bits: en lugar de DIRECCIÓN se guarda DIRECCI seguido de dos caracteres de reemplazo. La solución es borrar y volver a cargar el esquema con el cliente bien configurado.

💡 Consejo: En SQL Developer el idioma y los formatos se configuran en Herramientas > Preferencias > Base de datos > NLS. El idioma de la propia interfaz depende del sistema operativo.

📘 Concepto: Sobre los mensajes de error: el idioma de los textos ORA- depende de NLS_LANGUAGE y de que la instalación incluya los ficheros de mensajes de ese idioma. La imagen Docker de la edición Free solo incluye los mensajes en inglés, así que verás los errores en inglés aunque tu sesión esté en español. En este curso los mensajes de la herramienta (filas seleccionadas, Tabla creada, ERROR en línea 1) aparecen en español y los textos de los errores ORA-, en inglés, tal como los muestra la imagen Docker. Lo importante es el código (ORA-00942), que es igual en todos los idiomas. Desde 23ai cada error incluye además un enlace Help: a su página de ayuda.


15. Errores frecuentes al empezar

Error Causa habitual Solución
ORA-01017: invalid credential or not authorized; logon denied Usuario o contraseña incorrectos (la contraseña distingue mayúsculas) Revisa los datos; si el usuario no existe, créalo (sección 9)
ORA-12541: Cannot connect. No listener at host ... port ... El puerto es incorrecto o la base de datos (el contenedor) no está arrancada docker start oracle-free y espera a DATABASE IS READY TO USE!; revisa el puerto
ORA-12514: ... Service X is not registered with the listener El nombre del servicio es incorrecto o la PDB aún no se ha abierto Usa FREEPDB1; espera un minuto tras arrancar
ORA-65096: invalid common user or role name Has creado el usuario conectado a la CDB (servicio FREE) Conéctate al servicio FREEPDB1
ORA-01950: no privileges on tablespace 'USERS' El usuario no tiene cuota ALTER USER curso QUOTA UNLIMITED ON users; (como SYSTEM)
ORA-00942: table or view ... does not exist Nombre mal escrito, tabla de otro esquema o no has cargado EMPRESA Revisa el nombre y el usuario con el que estás conectado (SHOW USER)
ORA-00904: ...: invalid identifier Columna mal escrita o texto entre comillas dobles Revisa el nombre; los textos van con comillas simples
SP2-0734: unknown command beginning ... Error de escritura en la primera palabra: SQL*Plus no sabe qué es Corrige la palabra clave

Así se ven algunos de ellos en la realidad. Una tabla mal escrita (empleado en lugar de empleados); el asterisco señala la posición del error:

SELECT nombre FROM empleado;
SELECT nombre FROM empleado
                   *
ERROR en línea 1:
ORA-00942: table or view "CURSO"."EMPLEADO" does not exist
Help: https://docs.oracle.com/error-help/db/ora-00942/

Una palabra clave mal escrita la detecta la propia herramienta, sin llegar al servidor:

SELEC nombre FROM empleados;
SP2-0734: unknown command beginning "SELEC nomb..." - rest of line ignored.
Help: https://docs.oracle.com/error-help/db/sp2-0734/

Una coma de más:

SELECT nombre, FROM empleados;
SELECT nombre, FROM empleados
               *
ERROR en línea 1:
ORA-00936: missing expression
Help: https://docs.oracle.com/error-help/db/ora-00936/

Comillas dobles en lugar de simples:

SELECT nombre FROM empleados WHERE nombre = "Ana";
SELECT nombre FROM empleados WHERE nombre = "Ana"
                                            *
ERROR en línea 1:
ORA-00904: "Ana": invalid identifier
Help: https://docs.oracle.com/error-help/db/ora-00904/

Y los errores de conexión, tal como los muestra SQL*Plus:

$ sqlplus curso/ClaveMala@localhost:1521/FREEPDB1
ERROR:
ORA-01017: invalid credential or not authorized; logon denied

$ sqlplus curso/Curso2026@localhost:1521/NOEXISTE
ERROR:
ORA-12514: Cannot connect to database. Service NOEXISTE is not registered with
the listener at host 127.0.0.1 port 1521.

$ sqlplus curso/Curso2026@localhost:1599/FREEPDB1
ERROR:
ORA-12541: Cannot connect. No listener at host 127.0.0.1 port 1599.

💡 Consejo: Lee los errores de abajo arriba y de izquierda a derecha: el código te dice qué tipo de problema es, el texto te da el detalle (qué tabla, qué columna) y el asterisco te señala dónde lo detectó Oracle. Copiar el código ORA-xxxxx en un buscador (o seguir el enlace Help:) suele darte la solución en segundos.


16. Ejercicios

Ejercicios resueltos

Ejercicio 1: Clasificar sentencias

Indica a qué sublenguaje (DDL, DML, DCL o TCL) pertenece cada sentencia y qué hace: a) ALTER TABLE clientes ADD web VARCHAR2(100); b) UPDATE productos SET precio = precio * 1.05; c) GRANT SELECT ON empleados TO ana; d) ROLLBACK; e) DROP VIEW v_ventas; f) SELECT COUNT(*) FROM pedidos; g) TRUNCATE TABLE lineas_pedido; h) SAVEPOINT antes_de_borrar;

Ver solución
Sentencia Sublenguaje Qué hace
a) ALTER TABLE ... ADD DDL Añade una columna a la estructura de la tabla
b) UPDATE DML Modifica datos: sube un 5 % todos los precios
c) GRANT DCL Da a ana permiso para consultar empleados
d) ROLLBACK TCL Deshace los cambios de la transacción en curso
e) DROP VIEW DDL Elimina la definición de una vista
f) SELECT DML (o DQL) Consulta datos: cuenta los pedidos
g) TRUNCATE DDL Vacía la tabla. Aunque “borra datos”, es DDL: no se puede deshacer con ROLLBACK y hace COMMIT implícito
h) SAVEPOINT TCL Marca un punto intermedio de la transacción al que se puede volver

El caso trampa es TRUNCATE: su efecto se parece al de DELETE (DML), pero Oracle lo implementa reinicializando el almacenamiento de la tabla, por eso es DDL.

Ejercicio 2: Cadenas de conexión

Escribe la cadena EZConnect para cada caso: a) usuario curso, clave Curso2026, Oracle en Docker en tu propio equipo; b) usuario ana, clave Ana#2026, servidor del instituto oracle.ies.local, puerto 1522, servicio CLASEPDB; c) el mismo caso b, pero sin escribir la contraseña en la orden de SQL*Plus. ¿Qué error obtendrías si en el caso a) usaras el servicio FREE, y podrías crear allí el usuario curso?

Ver solución
a) curso/Curso2026@localhost:1521/FREEPDB1
b) ana/Ana#2026@oracle.ies.local:1522/CLASEPDB
c) sqlplus ana@oracle.ies.local:1522/CLASEPDB      (SQL*Plus pedirá la contraseña)

Con el servicio FREE te conectarías a la CDB raíz (CDB$ROOT). La conexión de curso fallaría con ORA-01017 porque el usuario curso existe solo en la PDB FREEPDB1. Y si un administrador intentara crear allí CREATE USER curso, obtendría ORA-65096: en la raíz solo se pueden crear usuarios comunes, cuyo nombre debe empezar por C##.

Ten en cuenta que si la contraseña contiene caracteres especiales para la terminal (#, $, &, @…), puede que tengas que escribirla entre comillas dobles: ana/"Ana#2026"@.... Es otro buen motivo para que la herramienta te la pida.

Ejercicio 3: Ficha de la sesión

Escribe una única consulta que muestre tu usuario, el contenedor (PDB) al que estás conectado y la fecha y hora actuales con el formato DD/MM/YYYY HH24:MI:SS, con alias usuario, pdb y ahora.

Ver solución
SELECT USER AS usuario,
       SYS_CONTEXT('USERENV', 'CON_NAME') AS pdb,
       TO_CHAR(SYSDATE, 'DD/MM/YYYY HH24:MI:SS') AS ahora
FROM   dual;
USUARIO    PDB        AHORA
---------- ---------- --------------------
CURSO      FREEPDB1   03/10/2026 23:01:13

1 fila seleccionada.

Se usa DUAL porque ninguno de los datos sale de una tabla. TO_CHAR convierte la fecha en texto con el formato indicado, así que el resultado no depende de NLS_DATE_FORMAT (verás TO_CHAR a fondo en el módulo 28).

Ejercicio 4: Interpretar DESCRIBE

Ejecuta DESCRIBE pedidos y responde: a) ¿qué columnas son obligatorias?; b) ¿cuántos caracteres admite como máximo estado?; c) ¿cuál es el mayor número de pedido posible?; d) ¿qué información importante de la tabla no muestra DESCRIBE?

Ver solución
DESCRIBE pedidos
 Nombre                                    ¿Nulo?   Tipo
 ----------------------------------------- -------- ----------------------------
 PEDIDO_ID                                 NOT NULL NUMBER(8)
 CLIENTE_ID                                NOT NULL NUMBER(6)
 EMPLEADO_ID                                        NUMBER(6)
 FECHA_PEDIDO                              NOT NULL DATE
 FECHA_ENVIO                                        DATE
 ESTADO                                    NOT NULL VARCHAR2(12)

a) pedido_id, cliente_id, fecha_pedido y estado (las marcadas NOT NULL). empleado_id es opcional porque hay pedidos en línea sin empleado, y fecha_envio porque los pedidos pendientes aún no se han enviado.

b) 12 bytes (VARCHAR2(12)); como los estados son palabras en mayúsculas sin tildes, equivale a 12 caracteres.

c) NUMBER(8) admite enteros de hasta 8 cifras: el mayor es 99.999.999.

d) DESCRIBE no muestra la clave primaria, las claves ajenas, las restricciones CHECK ni los valores por defecto (DEFAULT SYSDATE, DEFAULT 'PENDIENTE'). Para verlos hay que consultar el diccionario de datos (USER_CONSTRAINTS, USER_TAB_COLUMNS), lo que harás en los módulos 23 y 24, o usar INFO pedidos en SQLcl.

Ejercicio 5: Inventario del esquema

Usando la vista del diccionario USER_TAB_COLUMNS (una fila por cada columna de cada tabla tuya), obtén cuántas columnas tiene cada tabla del esquema, ordenado por nombre de tabla.

Ver solución
COLUMN table_name FORMAT A15
SELECT table_name, COUNT(*) AS columnas
FROM   user_tab_columns
GROUP BY table_name
ORDER BY table_name;
TABLE_NAME        COLUMNAS
--------------- ----------
ASIGNACIONES             5
CATEGORIAS               3
CLIENTES                10
DEPARTAMENTOS            4
EMPLEADOS               11
LINEAS_PEDIDO            6
PEDIDOS                  6
PRODUCTOS                8
PROYECTOS                6

9 filas seleccionadas.

GROUP BY agrupa las filas de la vista por tabla y COUNT(*) cuenta cuántas hay en cada grupo (lo estudiarás en el módulo 29). Observa que los nombres aparecen en mayúsculas: así los guarda Oracle en el diccionario.

Ejercicio 6: Un listado con formato

Con comandos de SQL*Plus, obtén un listado de los clientes de la provincia de Valencia con su nombre (28 caracteres), ciudad (12 caracteres) y límite de crédito con separador de miles y la cabecera Crédito. Al terminar, deja los formatos de columna como estaban.

Ver solución
COLUMN nombre         FORMAT A28
COLUMN ciudad         FORMAT A12
COLUMN limite_credito FORMAT 99G990 HEADING 'Crédito'

SELECT nombre, ciudad, limite_credito
FROM   clientes
WHERE  provincia = 'Valencia';

CLEAR COLUMNS
NOMBRE                       CIUDAD       Crédito
---------------------------- ------------ -------
Talleres Martín S.L.         Valencia      10.000
Carlos Ibáñez Rey            Valencia       1.500

2 filas seleccionadas.

El formato 99G990 reserva hasta 5 cifras con separador de miles (G, que en España es el punto) y obliga a mostrar al menos un 0. Si un valor no cupiera en el formato, SQL*Plus mostraría #######. CLEAR COLUMNS elimina los formatos para que no afecten a consultas posteriores que usen columnas con el mismo nombre.

Ejercicio 7: La misma fecha, tres aspectos

Muestra la fecha del pedido 5001 con el formato por defecto de la sesión, después con DD/MM/YYYY y con YYYY-MM-DD (cambiando NLS_DATE_FORMAT), y por último como texto largo en español (Miércoles, 15 de Enero de 2025) sin depender del formato de sesión. ¿Ha cambiado el dato almacenado en algún momento?

Ver solución
SELECT fecha_pedido FROM pedidos WHERE pedido_id = 5001;

ALTER SESSION SET NLS_DATE_FORMAT = 'DD/MM/YYYY';
SELECT fecha_pedido FROM pedidos WHERE pedido_id = 5001;

ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD';
SELECT fecha_pedido FROM pedidos WHERE pedido_id = 5001;

SELECT TO_CHAR(fecha_pedido, 'FMDay, DD "de" Month "de" YYYY') AS fecha_larga
FROM   pedidos
WHERE  pedido_id = 5001;
FECHA_PE
--------
15/01/25

FECHA_PEDI
----------
15/01/2025

FECHA_PEDI
----------
2025-01-15

FECHA_LARGA
------------------------------
Miércoles, 15 de Enero de 2025

El dato almacenado nunca cambia: es el mismo valor DATE interno; solo cambia su representación como texto. Los textos fijos dentro del formato van entre comillas dobles ("de") y el modificador FM elimina los espacios de relleno de los nombres del día y del mes. Si la sesión estuviera en inglés (NLS_DATE_LANGUAGE = 'ENGLISH'), el mismo formato daría Wednesday, 15 de January de 2025.

Ejercicio 8: Diagnosticar errores

Un compañero te enseña estas cuatro sentencias y sus errores. Explica la causa de cada uno y escribe la sentencia correcta: a) SELECT * FROM departamento; → ORA-00942; b) SELECT nombre FROM clientes WHERE ciudad = "Madrid"; → ORA-00904; c) SELECT nombre apellidos, FROM empleados; → ORA-00936; d) conectado como system a FREE, CREATE USER curso IDENTIFIED BY Curso2026; → ORA-65096.

Ver solución

a) La tabla se llama departamentos (en plural). Oracle busca CURSO.DEPARTAMENTO, que no existe.

SELECT * FROM departamentos;

b) "Madrid" entre comillas dobles es un identificador: Oracle busca una columna llamada Madrid. Los textos van entre comillas simples.

SELECT nombre FROM clientes WHERE ciudad = 'Madrid';

c) Hay dos errores. La coma final deja una expresión vacía antes de FROM (eso provoca ORA-00936: missing expression). Además falta la coma entre nombre y apellidos: sin ella, Oracle interpretaría apellidos como un alias de la columna nombre, un error silencioso que no da fallo pero muestra la columna equivocada.

SELECT nombre, apellidos FROM empleados;

d) La conexión es al servicio FREE, es decir, a la CDB raíz, donde solo se admiten usuarios comunes C##.... Hay que conectarse a la PDB:

-- sqlplus system/MiClaveSegura2026@localhost:1521/FREEPDB1
CREATE USER curso IDENTIFIED BY Curso2026
    DEFAULT TABLESPACE users
    QUOTA UNLIMITED ON users;
GRANT DB_DEVELOPER_ROLE TO curso;

Ejercicio 9: Un script de bienvenida

Escribe un script bienvenida.sql que: fije el ancho de línea a 120 y el formato de fecha a DD/MM/YYYY; muestre el mensaje === Comprobación del esquema EMPRESA ===; muestre el usuario; cuente los empleados sin departamento y liste quiénes son; liste los clientes sin comercial asignado; y guarde todo en bienvenida.txt. Indica cómo lo ejecutarías en SQL*Plus y en SQL Developer.

Ver solución
-- bienvenida.sql
SET LINESIZE 120 PAGESIZE 100 FEEDBACK ON
ALTER SESSION SET NLS_DATE_FORMAT = 'DD/MM/YYYY';
SPOOL bienvenida.txt

PROMPT === Comprobación del esquema EMPRESA ===
SHOW USER

SELECT COUNT(*) AS sin_departamento FROM empleados WHERE dept_id IS NULL;
SELECT nombre, apellidos FROM empleados WHERE dept_id IS NULL;
SELECT nombre, ciudad FROM clientes WHERE comercial_id IS NULL;

SPOOL OFF

Ejecución: en SQL*Plus o SQLcl, @C:\curso-bd\bienvenida.sql; en SQL Developer, abrir el fichero y pulsar F5. El contenido de bienvenida.txt será:

=== Comprobación del esquema EMPRESA ===
USER es "CURSO"

SIN_DEPARTAMENTO
----------------
               1

1 fila seleccionada.


NOMBRE                         APELLIDOS
------------------------------ --------------------------------------------------
Tomás                          Reyes Fuentes

1 fila seleccionada.


NOMBRE                                                       CIUDAD
------------------------------------------------------------ ----------------------------------------
Laura Benítez Sanz                                           Bilbao
Carlos Ibáñez Rey                                            Valencia

2 filas seleccionadas.

Para comprobar si una columna es nula se usa IS NULL, nunca = NULL (que siempre da “desconocido” y no devuelve filas). Lo estudiarás en el módulo 27.

Ejercicios propuestos

Ejercicio 10: Línea del tiempo

Elabora una tabla con cinco características de SQL que uses en este curso (por ejemplo JOIN explícito, CASE, columnas identidad, FETCH FIRST, tipo JSON) e indica en qué versión del estándar aparecieron y desde qué versión de Oracle están disponibles.

Ejercicio 11: Instalación documentada

Instala Oracle AI Database Free con Docker en tu equipo y documenta el proceso con capturas: orden docker run usada, mensaje DATABASE IS READY TO USE!, conexión como SYSTEM y creación del usuario curso. Añade la salida de SELECT * FROM v$version; ejecutada como curso.

Ejercicio 12: Cuatro clientes, una base de datos

Conéctate al usuario curso con al menos tres herramientas distintas (por ejemplo SQL Developer, SQLcl y DBeaver) y compara: cómo se ejecuta una sentencia, cómo se ejecuta un script, cómo se ven los nulos y las fechas, y cómo se exporta un resultado a CSV.

Ejercicio 13: Explorar EMPRESA

Usando solo SELECT * FROM tabla y DESCRIBE, responde: ¿qué productos tienen stock 0?, ¿qué proyecto no tiene fecha de fin ni presupuesto?, ¿qué empleado tiene comisión igual a 0?, ¿qué pedidos no tienen empleado? Indica en cada caso si la respuesta corresponde a un valor nulo o a un valor conocido.

Ejercicio 14: Configuración regional a la carta

Escribe un script que muestre la fecha y hora actuales y el número 1234567.891 con formato monetario en tres configuraciones: España, Estados Unidos (AMERICAN/AMERICA) y Alemania (GERMAN/GERMANY). Explica las diferencias y deja la sesión al final en español con DD/MM/YYYY.

Ejercicio 15: Usuario para 19c

Un centro tiene Oracle 19c con la PDB ORCLPDB. Escribe el script completo que debería ejecutar el administrador para crear los usuarios alumno01 y alumno02 con 50 MB de cuota cada uno en USERS y los privilegios necesarios para seguir el curso. ¿Qué cambiaría en 26ai?

Ejercicio 16: SQL*Plus de memoria

Sin consultar el módulo, indica qué comando de SQL*Plus usarías para: ver las columnas de clientes; que la columna descripcion ocupe 30 caracteres; ocultar el mensaje de filas seleccionadas; guardar la salida en informe.txt; saber con qué usuario estás conectado; ejecutar el fichero carga.sql que está en la misma carpeta que el script actual.

Ejercicio 17: Carga corrupta

Un compañero cargó empresa_datos.sql desde SQL*Plus en Linux sin configurar nada y ahora ve INFORM��TICA. Explica por qué ha ocurrido, por qué no se arregla cambiando NLS_LANG y volviendo a consultar, y escribe los pasos exactos para dejarlo bien.


Resumen

Concepto Clave
SQL Lenguaje declarativo y estándar (ISO/IEC 9075) para bases de datos relacionales; nació como SEQUEL en IBM (1974)
Estándares principales SQL-86, SQL-89, SQL-92, SQL:1999, SQL:2003, SQL:2008, SQL:2011, SQL:2016, SQL:2019, SQL:2023
Dialectos Oracle SQL (+PL/SQL), T-SQL, PostgreSQL, MySQL…; mismo núcleo, extensiones propias
DDL CREATE, ALTER, DROP, TRUNCATE, RENAME, COMMENT (con COMMIT implícito)
DML SELECT, INSERT, UPDATE, DELETE, MERGE
DCL GRANT, REVOKE
TCL COMMIT, ROLLBACK, SAVEPOINT
Reglas léxicas Palabras clave sin distinción de mayúsculas; textos con '...'; identificadores especiales con "..."; ; termina; comentarios -- y /* */
Instancia / base de datos Memoria y procesos / ficheros en disco
CDB / PDB Contenedor (FREE) / base de datos conectable de trabajo (FREEPDB1)
Usuario = esquema Los objetos de curso son curso.tabla
Conexión EZConnect usuario/clave@host:1521/FREEPDB1
Docker docker run -d --name oracle-free -p 1521:1521 -e ORACLE_PWD=... container-registry.oracle.com/database/free:latest
Usuario del curso CREATE USER curso ... QUOTA UNLIMITED ON users; GRANT DB_DEVELOPER_ROLE TO curso; (23ai+)
Scripts @fichero.sql en SQL*Plus/SQLcl; F5 en SQL Developer
DUAL Tabla de una fila y una columna para calcular expresiones
Comandos SQL*Plus DESCRIBE, SET LINESIZE/PAGESIZE/FEEDBACK, COLUMN ... FORMAT, SPOOL, SHOW USER
NLS NLS_LANGUAGE, NLS_TERRITORY, NLS_DATE_FORMAT; literales DATE 'AAAA-MM-DD' siempre
NLS_LANG SPANISH_SPAIN.AL32UTF8 en el cliente para no corromper tildes y eñes