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
DUALy 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
COMMITimplícito antes y después de ejecutarse. Si tienes cambios pendientes de unINSERTy ejecutas unCREATE TABLE, esos cambios quedan confirmados y ya no podrás deshacerlos conROLLBACK. 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 conSET 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 llamadaAnay falla conORA-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
CURSOse crea automáticamente el esquemaCURSO, 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 porC##(usuarios comunes) y rechazaráCREATE USER cursoconORA-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 tablespaceUSERSen la PDB. Si la usas, antes de crear tu usuario conéctate comoSYSTEMaFREEPDB1y 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 instally 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_ROLEagrupa 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
SYSTEMpuedes 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 conNLS_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
FROMes opcional y puedes escribir directamenteSELECT 7 * 6 AS resultado;(devuelve lo mismo). En 19c y 21c es obligatorioFROM dual. ComoFROM dualfunciona 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 PAGESIZEpequeñ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_TERRITORYreinicia el formato de fecha al valor por defecto del territorio (DD/MM/RRen España,DD-MON-RRen EE. UU.). Si cambias el territorio, vuelve a fijar después tuNLS_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 deNLS_LANGUAGEy 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 erroresORA-, 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 enlaceHelp: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-xxxxxen un buscador (o seguir el enlaceHelp:) 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 |