Publicado el

¿Qué es SQL? Su propósito, sus orígenes y cómo utilizarlo

Autores

    ¿Qué es SQL? Su propósito, sus orígenes y cómo utilizarlo

    La mayoría de las aplicaciones modernas, desde blogs sencillos hasta enormes redes sociales, almacenan y recuperan datos mediante un lenguaje llamado SQL.

    En este artículo, aprenderás:

    • Qué es SQL y por qué se creó
    • Cómo organizan la información las bases de datos relacionales
    • Un ejemplo concreto con dos tablas
    • Cómo ejecutar consultas SQL reales, incluidas operaciones JOIN
    • Por qué SQL sigue siendo el estándar después de más de 50 años

    Vamos a empezar.

    ¿Qué es SQL?

    SQL (Structured Query Language) es un lenguaje de programación declarativo creado para gestionar bases de datos relacionales, bases de datos construidas alrededor de tablas, filas y relaciones entre ellas.

    SQL permite:

    • Crear tablas
    • Insertar y modificar datos
    • Consultar datos mediante filtros
    • Unir datos relacionados de múltiples tablas
    • Aplicar restricciones como correos electrónicos únicos o claves foráneas

    SQL se utiliza en muchos sistemas populares de bases de datos relacionales. Estos son algunos ejemplos:

    • PostgreSQL: base de datos relacional avanzada de código abierto con sólido soporte de los estándares SQL.
    • MySQL: muy utilizado en aplicaciones web, conocido por su velocidad y sencillez.
    • MariaDB: versión derivada de MySQL impulsada por la comunidad con funciones de rendimiento mejoradas.
    • SQLite: base de datos ligera basada en archivos, utilizada en aplicaciones móviles, navegadores y sistemas integrados.
    • Microsoft SQL Server: base de datos comercial de nivel empresarial con herramientas potentes en Windows y entornos de nube.
    • Servicios SQL en la nube totalmente administrados como:
      • Amazon RDS
      • Google Cloud SQL
      • PlanetScale
        Estas plataformas (Amazon RDS, Google Cloud SQL, PlanetScale) se encargan de copias de seguridad, escalado, actualizaciones y mantenimiento, conservando toda la potencia de SQL.

    Por qué se creó SQL

    En la década de 1970, en IBM, los investigadores Edgar F. Codd y Donald Chamberlin trabajaban en una mejor forma de almacenar y recuperar datos. Por entonces, las bases de datos eran jerárquicas o basadas en redes, e interactuar con ellas era:

    • Complejo
    • Propenso a errores
    • Estrechamente vinculado al almacenamiento físico
    • Difícil de consultar con flexibilidad

    Para resolverlo, introdujeron el modelo relacional, que organiza los datos en tablas con relaciones bien definidas.

    SQL se creó porque:

    1. Los desarrolladores necesitaban un lenguaje sencillo y legible para consultar datos.
    2. Las empresas necesitaban reglas coherentes para almacenar relaciones.
    3. Era necesario acceder a los datos sin conocer los detalles internos del almacenamiento.

    ¿El resultado?
    SQL se convirtió en estándar en 1986 (ANSI SQL) y casi todas las grandes bases de datos actuales siguen construidas en torno a él.

    Comprender las tablas, columnas y relaciones

    En una base de datos relacional:

    • Una tabla es como una hoja de cálculo (filas + columnas)
    • Una fila representa un único registro
    • Una columna representa una propiedad
    • Una clave primaria identifica de forma única cada fila
    • Una clave foránea enlaza una tabla con otra

    Veamos un ejemplo sencillo con usuarios y pedidos.

    Esquema de ejemplo: Usuarios y pedidos

    Tabla 1: users

    idnombrecorreo electrónico
    1Alicealice@example.com
    2Bobbob@example.com
    3Carolinecaroline@example.com

    Tabla 2: orders

    idid de usuarioproductoprecio
    11Coffee Mug12.99
    21Hoodie39.99
    32Stickers4.99

    Relación

    • Cada pedido pertenece a un usuario (orders.user_id → users.id).
    • Un usuario puede tener varios pedidos (relación de uno a muchos).

    Creación de las tablas (ejemplo SQL)

    CREATE TABLE users (
      id SERIAL PRIMARY KEY,
      name TEXT NOT NULL,
      email TEXT UNIQUE NOT NULL
    );
    
    CREATE TABLE orders (
      id SERIAL PRIMARY KEY,
      user_id INTEGER NOT NULL REFERENCES users(id),
      product TEXT NOT NULL,
      price NUMERIC(10,2) NOT NULL
    );
    

    Esto crea dos tablas relacionales con tipado fuerte y una restricción de clave foránea. Pero ¿qué significa "tipado fuerte"? En bases de datos SQL como PostgreSQL y MySQL, cada columna tiene un tipo de datos estricto:

    • Las cadenas deben ser cadenas
    • Los números deben ser números
    • Las fechas deben ser fechas
    • Los tipos incorrectos provocan errores

    Esto evita errores como:

    • Guardar "veinte dólares" en una columna numérica
    • Guardar 123 en un campo de correo electrónico
    • Guardar fechas inválidas

    SQLite es diferente: tiene tipado flexible (“tipado dinámico”), lo que significa que aplica los tipos de forma menos estricta. PostgreSQL es la base de datos con tipado más fuerte entre las principales.

    Explicación de la creación de tablas línea por línea

    Tabla 1: users

    CREATE TABLE users (
    

    Explicación: Esto inicia la creación de una nueva tabla llamada users.

    • Funciona en: PostgreSQL, MySQL, MariaDB, SQLite, SQL Server
    • “Tabla" = conjunto estructurado de filas y columnas en la base de datos.
      id SERIAL PRIMARY KEY,
    

    Explicación:

    • id → el nombre de la columna.

    • SERIAL → entero que se incrementa automáticamente. Específico de PostgreSQL.

      • Equivalente en otros motores:

        • MySQL: INT AUTO_INCREMENT
        • SQLite: INTEGER PRIMARY KEY AUTOINCREMENT
    • PRIMARY KEY → garantiza la unicidad e identifica cada fila.

      name TEXT NOT NULL,
    

    Explicación:

    • name → columna para almacenar texto.
    • TEXT → texto de longitud variable (cadena).
    • NOT NULL → no puede estar vacío.

    Funciona en: PostgreSQL, MySQL, MariaDB, SQLite (pero SQL Server utiliza VARCHAR(MAX) en lugar de TEXT).

      email TEXT UNIQUE NOT NULL
    

    Explicación:

    • email → almacena la dirección de correo electrónico del usuario.
    • TEXT → tipo de texto.
    • UNIQUE → evita correos electrónicos duplicados.
    • NOT NULL → campo obligatorio.

    Funciona en: PostgreSQL, MySQL, MariaDB, SQLite (teniendo en cuenta diferencias en el funcionamiento interno del tipo TEXT).

    );
    

    Explicación: Cierra la definición de la tabla.

    Tabla 2: orders

    CREATE TABLE orders (
    

    Inicia una nueva tabla llamada orders. Funciona en todos los motores principales.

      id SERIAL PRIMARY KEY,
    

    Tiene el mismo significado que en la tabla users:

    • Clave primaria que se incrementa automáticamente
    • Tipo específico de PostgreSQL (SERIAL)
    • MySQL utiliza INT AUTO_INCREMENT
    • SQLite utiliza INTEGER PRIMARY KEY AUTOINCREMENT
      user_id INTEGER NOT NULL REFERENCES users(id),
    

    Explicación:

    • user_id → un número que enlaza el pedido con una fila de users.
    • INTEGER → número entero.
    • NOT NULL → cada pedido debe pertenecer a un usuario.
    • REFERENCES users(id) → restricción de clave foránea.

    Significado de clave foránea:

    • Garantiza que user_id coincida con un id real de la tabla users.
    • Si se elimina un usuario, la base de datos hará cumplir la relación según las reglas de la clave foránea.

    Compatibilidad con motores:

    • PostgreSQL: Totalmente compatible.
    • MySQL/MariaDB: Funciona, pero requiere que el motor de almacenamiento sea InnoDB.
    • SQLite: Compatible solo cuando está habilitado el soporte de claves foráneas (PRAGMA foreign_keys = ON;).
      product TEXT NOT NULL,
    

    Almacena el nombre del producto comprado.

    • Tipo TEXT → funciona de forma diferente entre motores, pero siempre almacena cadenas.
    • Obligatorio (NOT NULL).
      price NUMERIC(10,2) NOT NULL
    

    Explicación:

    • price → el coste del producto.

    • NUMERIC(10,2) → número de precisión fija con:

      • hasta 10 dígitos en total
      • 2 dígitos después del decimal

    Esto es ideal para el dinero porque evita errores de coma flotante.

    Compatibilidad con motores:

    • PostgreSQL: Soporte perfecto.
    • MySQL: DECIMAL(10,2) es equivalente.
    • SQLite: Lo acepta, pero almacena internamente como TEXT, INT o REAL (tipado dinámico).
    );
    

    Finaliza la tabla.

    Inserción de los datos de ejemplo

    Ahora que nuestras tablas están creadas, insertemos los datos de ejemplo que mostramos antes. No proporcionaremos manualmente ningún valor id, la base de datos los generará automáticamente mediante el tipo SERIAL (incremento automático).

    Inserción de los datos de users

    INSERT INTO users (name, email)
    VALUES
      ('Alice', 'alice@example.com'),
      ('Bob', 'bob@example.com'),
      ('Caroline', 'caroline@example.com');
    

    Explicación de la inserción en users línea por línea

    INSERT INTO users (name, email)
    
    • INSERT INTO users Estamos añadiendo nuevas filas a la tabla users.

    • (name, email) Solo proporcionamos name y email, no id.

      ¿Por qué no proporcionar el identificador? Como la columna id utiliza un tipo serial/de incremento automático (SERIAL de PostgreSQL), la base de datos creará los identificadores automáticamente:

    • Primer usuario → id = 1

    • Segundo usuario → id = 2

    • Tercer usuario → id = 3

    Esto es predecible en PostgreSQL, MySQL, MariaDB y SQLite (mediante sus respectivos mecanismos de incremento automático).

    VALUES
      ('Alice', 'alice@example.com'),
      ('Bob', 'bob@example.com'),
      ('Caroline', 'caroline@example.com');
    
    • VALUES introduce una lista de filas para insertar.
    • Cada grupo ( ... ) corresponde a una nueva fila.
    • La base de datos asignará los identificadores automáticamente en el orden en que aparecen estas filas.

    Tabla users resultante tras la inserción:

    idnombrecorreo electrónico
    1Alicealice@example.com
    2Bobbob@example.com
    3Carolinecaroline@example.com

    Aunque no escribimos ningún identificador manualmente, el mecanismo de incremento automático garantiza este orden.

    Inserción de los datos de orders

    Para insertar orders, sí necesitamos proporcionar user_id, porque un pedido debe pertenecer a un usuario concreto.

    Aunque no establecimos manualmente los identificadores de los usuarios, sabemos cuáles son:

    • Alice → ID 1
    • Bob → ID 2
    • Caroline → ID 3

    Se generaron automáticamente en el orden de inserción, y el incremento automático siempre asigna identificadores secuencialmente salvo que se eliminen filas.

    Esta es la inserción:

    INSERT INTO orders (user_id, product, price)
    VALUES
      (1, 'Coffee Mug', 12.99),
      (1, 'Hoodie', 39.99),
      (2, 'Stickers', 4.99);
    

    Explicación de la inserción en orders línea por línea

    INSERT INTO orders (user_id, product, price)
    
    • Estamos insertando nuevas filas en la tabla orders.

    • Especificamos:

      • user_id → a qué usuario pertenece el pedido
      • product → nombre
      • price → coste

    Observa de nuevo: no proporcionamos id (orders.id), porque se incrementa automáticamente.

    VALUES
      (1, 'Coffee Mug', 12.99),
      (1, 'Hoodie', 39.99),
      (2, 'Stickers', 4.99);
    
    • (1, 'Coffee Mug', 12.99)

      • Este pedido pertenece al usuario 1 (Alice).
    • (1, 'Hoodie', 39.99)

      • Otro pedido de Alice.
    • (2, 'Stickers', 4.99)

      • Pedido del usuario 2 (Bob).

      ¿Cómo sabemos qué identificador tiene cada usuario? Porque:

    1. Los usuarios se insertaron primero.

    2. Los identificadores de incremento automático siempre asignan enteros secuenciales.

    3. Por tanto:

      • Primer usuario insertado → ID 1
      • Segundo usuario insertado → ID 2
      • Tercer usuario insertado → ID 3

    Esto hace que referirse a los usuarios por su identificador sea completamente predecible.

    Tablas finales resultantes

    users

    idnombrecorreo electrónico
    1Alicealice@example.com
    2Bobbob@example.com
    3Carolinecaroline@example.com

    orders

    idid de usuarioproductoprecio
    11Coffee Mug12.99
    21Hoodie39.99
    32Stickers4.99

    Consultar datos con SQL

    Estas son consultas SQL que podemos ejecutar sobre las dos tablas que definimos antes.

    1. Seleccionar todos los usuarios

    SELECT * FROM users;
    

    Resultado:

    idnombrecorreo electrónico
    1Alicealice@example.com
    2Bobbob@example.com
    3Carolinecaroline@example.com

    2. Seleccionar usuarios con un filtro

    SELECT * FROM users
    WHERE email LIKE '%alice@example.com';
    

    Qué significa:

    • LIKE es un operador SQL utilizado para comparar texto mediante patrones.
    • % es un comodín que coincide con cualquier número de caracteres, incluidos cero caracteres.

    Así:

    • '%alice@example.com' significa: “Coincide con cualquier texto que termine en alice@example.com.”

    Esto devolverá la fila en la que el correo electrónico del usuario sea exactamente:

    alice@example.com
    

    pero también coincidiría con valores como:

    • test-alice@example.com
    • hello.alice@example.com
    • anythingalice@example.com

    si existieran.

    Resultado:

    idnombrecorreo electrónico
    1Alicealice@example.com

    Esta consulta devuelve solo al usuario cuyo correo electrónico coincide con el filtro.

    3. Insertar un nuevo pedido

    INSERT INTO orders (user_id, product, price)
    VALUES (2, 'T-Shirt', 29.99);
    

    Tabla orders resultante tras la inserción:

    idid de usuarioproductoprecio
    11Coffee Mug12.99
    21Hoodie39.99
    32Stickers4.99
    42T-Shirt29.99
    • La nueva fila tiene id = 4, generado automáticamente.
    • user_id = 2 enlaza este pedido con Bob.

    JOIN: Una parte potente de SQL

    Los JOIN permiten consultar datos relacionados entre múltiples tablas.

    Explorémoslos con nuestras tablas users y orders.

    Ejemplo 1: INNER JOIN (el más habitual)

    Obtén cada pedido con el usuario que lo realizó.

    SELECT users.name, orders.product, orders.price
    FROM orders
    INNER JOIN users
      ON orders.user_id = users.id;
    

    Resultado:

    nameproductoprice
    AliceCoffee Mug12.99
    AliceHoodie39.99
    BobStickers4.99
    BobT-Shirt29.99

    Bob aparece ahora dos veces, por el nuevo pedido insertado antes:

    INSERT INTO orders (user_id, product, price)
    VALUES (2, 'T-Shirt', 29.99);
    

    Ejemplo 2: LEFT JOIN

    Obtén todos los usuarios, incluso los que no tienen pedidos.

    SELECT users.name, orders.product
    FROM users
    LEFT JOIN orders
      ON users.id = orders.user_id;
    

    Resultado:

    nameproduct
    AliceCoffee Mug
    AliceHoodie
    BobStickers
    BobT-Shirt
    Carolinenull

    Caroline aparece con null porque no tiene pedidos.

    Ejemplo 3: Consulta de agregación

    Dinero total gastado por cada usuario:

    SELECT users.name, SUM(orders.price) AS total_spent
    FROM users
    LEFT JOIN orders ON users.id = orders.user_id
    GROUP BY users.name;
    

    Resultado:

    nametotal_spent
    Alice52.98
    Bob34.98
    Carolinenull

    Explicación

    • Alice: 12.99 + 39.99 = 52.98
    • Bob: 4.99 + 29.99 = 34.98
    • Caroline: sin pedidos → NULL

    Dónde aprender más

    Reflexiones finales

    SQL existe desde hace más de 50 años y sigue impulsando las aplicaciones más importantes del mundo porque es:

    • Declarativo
    • Coherente
    • Predecible
    • Escalable
    • Respaldado por décadas de investigación

    Una vez que comprendes cómo funcionan las tablas y las uniones, SQL se convierte en una de las herramientas más potentes de tu repertorio de programación.