El SQL menos conocido

4
13989
89853477 - hand place wood letters for sql (structured query language) concept

Índice de contenidos

El objetivo de este post es dar un repaso a las funcionalidades menos conocidas de SQL.

1. Antes de comenzar

Para los ejemplos, se va a usar la BBDD de ejemplo de https://github.com/datacharmer/test_db

Que tiene el siguiente modelo:

Esta base de datos de ejemplo está hecha para MySQL, pero te costará muy poquito adaptarla al gestor que utilices. Para este ejemplo, se ha adaptado de forma que se ha recreado en un DB2 y el cliente de BBDD que he utilizado en este artículo es DBeaver v3.5.8

2. Evoluciones del estándar

El lenguaje SQL ha sufrido muchas revisiones. La última en 2016.

  • SQL-86
  • SQL-89
  • SQL-92
  • SQL-1999
  • SQL-2003
  • SQL-2008
  • SQL-2011
  • SQL-2016

La mayoría de gestores de base de datos implementan el último estándar, aunque cada uno tiene sus matices. Sin embargo, el uso mayoritario que se sigue haciendo se corresponde con el de SQL-92.

3. CTE. Common Table Expressions. WITH

SQL-1999 introduce las tablas comunes y consultas recursivas a través de la construcción WITH.

A cierto modo, WITH es como si crease una vista al vuelo y luego se utilizase en una query. En la BBDD que estamos manejando de ejemplo en este artículo, los directores actuales de cada departamento se pueden obtener de la siguiente manera.

SELECT EMP_NO, DEPT_NO 
    FROM DEPT_MANAGER 
   WHERE FROM_DATE < CURRENT_DATE 
     AND TO_DATE > CURRENT_DATE

Si guardamos el resultado de esta query en una variable, podemos usarla a continuación y hacer cosas del estilo.

WITH managers AS (
  SELECT EMP_NO, DEPT_NO 
    FROM DEPT_MANAGER 
   WHERE FROM_DATE < CURRENT_DATE 
     AND TO_DATE > CURRENT_DATE
)
SELECT 
  M.EMP_NO,
  E.FIRST_NAME,
  E.LAST_NAME,
  E.GENDER,
  D.DEPT_NO,
  D.DEPT_NAME
  FROM managers M
  INNER JOIN EMPLOYEES E ON M.EMP_NO=E.EMP_NO
  INNER JOIN DEPARTMENTS D ON M.DEPT_NO=D.DEPT_NO;

En este caso, esa consulta se podría resolver de forma más óptima con un INNER JOIN, pero ejemplifica el uso que se puede hacer de la cláusula WITH.

4. CTE Recursivas. WITH… LEVEL

En ocasiones nos encontraremos con estructuras jerárquicas que requieren de consultas con cierta recursividad. Lo bueno de las CTE es que las podemos usar dentro de su propia definición, de forma que ahí tenemos la recursividad.

Por ejemplo, imaginemos que tenemos una tabla donde guardamos el linaje por línea materna.

CREATE TABLE person (
    id             INT             NOT NULL,
    name           VARCHAR(30)     NOT NULL,
    mother         INT             NOT NULL,
    PRIMARY KEY (id)
);

Definimos una primera mujer a la que llamaremos Eva, y a partir de ahí vamos guardando su descendencia haciendo referencia a quien es su madre.

insert into person (id, name, mother) values 
(1, 'Eva', 0),
(2, 'Ana', 1),
(3, 'María', 1),
(4, 'Rocío', 2),
(5, 'Carmen', 2),
(6, 'Aurora', 3),
(7, 'Pilar', 4); 

Y al hacer una consulta sobre esta tabla, deseamos obtener en una columna su linaje materno desde Eva hasta la madre de cada niña.

Pues podemos utilizar una CTE recursiva para hacerlo, indicando el nivel de profundidad (recursividad) que deseamos emplear, en este ejemplo 4.

WITH RCTE (id, name, mother, family, level) as 
(
  select 
    root.id, 
    root.name, 
    root.mother, 
    CAST('' AS VARCHAR(50)) as family, 
    1 as level
  from PERSON root 
  where root.id=1
union all
  select 
    child.id, 
    child.name, 
    child.mother, 
    concat(concat(parent.family,' > '), parent.name), 
    parent.level +1 as level
  from RCTE parent, PERSON child
  where parent.id = child.mother 
    and parent.level < 4
)
select distinct id, name, mother, family from RCTE

Y este es el resultado de la consulta.

Este tipo de consultas, se suelen utilizar para obtener estructuras jerárquicas.

5. CUBE, ROLLUP y GROUPING SETS

También la especificación SQL-1999 amplía el significado de la cláusula GROUP BY con los operadores CUBE, ROLLUP y GROUPING SETS.

En el ejemplo que tenemos, vamos a obtener un listado de empleados, con su cargo actual y salario. La siguiente consulta nos podría dar este resultado:

select
  e.emp_no, 
  first_name, 
  last_name, 
  gender, 
  title, 
  salary
from titles t
  inner join employees e on t.EMP_NO = e.EMP_NO 
  inner join salaries s 
    on s.EMP_NO = e.EMP_NO AND current_date > s.from_date AND current_date < s.to_date
 where 
   current_date > t.from_date 
   AND current_date < t.to_date
   AND e.emp_no<10021;

E imaginemos que ahora queremos desglosar los sueldos medios por género y por cargo en la empresa. Podemos agrupar por género y cargo con cubo de la siguiente manera.

select
  gender, 
  title, 
  avg(salary)
from titles t
  inner join employees e on t.EMP_NO = e.EMP_NO 
  inner join salaries s 
    on s.EMP_NO = e.EMP_NO AND current_date > s.from_date AND current_date < s.to_date
 where 
   current_date > t.from_date 
   AND current_date < t.to_date
   AND e.emp_no<10021
   group by gender, title with cube;

Fijémonos en los bloques de null.

Las cuatro primeras filas nos dan el sueldo medio por cargo, independientemente del sexo.

La fila cinco nos da el salario medio independientemente del género (null) y del cargo (null). Es decir, nos da el sueldo medio de este grupo.

La fila 6 y 7 nos dan el salario medio desglosado por sexos independientemente del cargo (null)

Y el resto de filas nos dan el salario medio desglosado por cargo y género.

La opción WITH ROLLUP nos da el mismo resultado, pero sin tener en cuenta las cuatro primeras filas.

select
  gender, 
  title, 
  avg(salary)
from titles t
  inner join employees e on t.EMP_NO = e.EMP_NO 
  inner join salaries s 
    on s.EMP_NO = e.EMP_NO AND current_date > s.from_date AND current_date < s.to_date
 where 
   current_date > t.from_date 
   AND current_date < t.to_date
   AND e.emp_no<10021
   group by gender, title with rollup;

La primera fila nos da el salario medio de esos empleados.

La fila 2 y 3 el salario medio desglosado por género.

Y de la 5 a la 11, el salario medio, desglosado por género y cargo que ocupa.

En el caso de grouping sets, saca la información desglosada por uno y otro campo.

select
  gender, 
  title, 
  avg(salary)
from titles t
  inner join employees e on t.EMP_NO = e.EMP_NO 
  inner join salaries s 
    on s.EMP_NO = e.EMP_NO AND current_date > s.from_date AND current_date < s.to_date
 where 
   current_date > t.from_date 
   AND current_date < t.to_date
   AND e.emp_no<10021
   group by grouping sets (gender, title);

6. Funciones de Ventana

Las funciones de ventana fueron introducidas en SQL2003 y posteriormente ampliadas en SQL2008 y nos proporcionan una forma de tratar datos calculados referidos a una ventana de nuestra query.

Por ejemplo, volviendo a la query de nuestro ejemplo que sacaba datos de los empleados, ahora queremos una columna adicional, que de esos datos nos devuelva el salario medio desglosado por género.

Esa columna sería

avg(salary) over (partition by gender) as avg_salary_by_gender

y la consulta quedaría así:

select
  e.emp_no, 
  first_name, 
  last_name, 
  gender, 
  title, 
  salary,
  avg(salary) over (partition by gender) as avg_salary_by_gender
from titles t
  inner join employees e on t.EMP_NO = e.EMP_NO 
  inner join salaries s 
    on s.EMP_NO = e.EMP_NO 
    AND current_date > s.from_date 
    AND current_date < s.to_date
 where 
   current_date > t.from_date 
   AND current_date < t.to_date
   AND e.emp_no < 10021;

Y el resultado sería:

Igual que se calcula la media, se puede calcular también el max, min, sum, etc… así como otras funciones numéricas.

Algunas estrategias de optimización suelen hacer este tipo de queries y por fuera otra query que se queda sólo con un subconjunto de estos datos. Por ejemplo, en este caso sería muy fácil obtener los empleados que cobran por encima de la media según su género.

También se utiliza con frecuencia en combinación con rownumber() para paginar resultados.

Por ejemplo, queremos obtener los empleados ordenados por apellido y nombre, la segunda página, con un tamaño de página 20.

SELECT
  emp_no, first_name, last_name, gender, title, salary
FROM
(
  SELECT
    e.emp_no, 
    first_name, 
    LAST_name, 
    gender, 
    title, 
    salary,
    rownumber() OVER (ORDER BY last_name, first_name) AS rownumber
  FROM titles t
    INNER JOIN employees e ON t.EMP_NO = e.EMP_NO 
    INNER JOIN salaries s 
      ON s.EMP_NO = e.EMP_NO 
        AND CURRENT_DATE > s.from_date 
        AND CURRENT_DATE < s.to_date
   WHERE 
     CURRENT_DATE > t.from_date 
     AND CURRENT_DATE < t.to_date
)
WHERE
  ROWNUMBER >= 21 AND ROWNUMBER < 40

Y el resultado quedaría

7. Insertar o actualizar mediante MERGE

Una situación que nos encontramos con frecuencia es que si tenemos un registro en la BBDD debemos actualizarlo, y si no, debemos insertarlo.

La forma habitual es hacer el select del registro y si no nos devuelve resultados, lo insertamos, y si nos devuelve resultados, actualizamos sólo los campos afectados.

En SQL2003 se introdujo la sentencia MERGE que luego se ampliaría en SQL2008 y que nos permite esto mismo en una sentencia:

MERGE INTO tablename USING table_reference ON (condition)
    WHEN MATCHED THEN
      UPDATE SET column1 = value1, column2 = value2, ...
    WHEN NOT MATCHED THEN
      INSERT (column1, column2, ...) VALUES (value1, value2, ...);

8. Conclusiones

El SQL es más que las sentencias propias de un CRUD. Y cada gestor de BBDD implementa parte o la totalidad del estándar añadiendo sus particularidades. Pero antes que esas particularidades, conveniente conocer el estándar para sacar más partido al lenguaje.

9. Enlaces y referencias

4 COMENTARIOS

  1. hola, las consultas del numeral 5. CUBE, ROLLUP y GROUPING SETS no corresponden con el resultado obtenido: las columnas e.emp_no, first_name, last_name no deberían estar y salary debería estar precedida por avg()

    • Pues tienes toda la razón. Las capturas de pantalla no se corresponden con las consultas. Lo voy a corregir. Muchas gracias por el apunte

  2. Corrí el ejemplo del parentesco y me manda un error de que Los tipos de la parte de delimitador y la parte recursiva de la columna «family» y la consulta recursiva RCTE no coinciden. Perdón, soy nueva usando recursividad y por más que le busco, no hallo por qué ese error

    • Hola Claudia,
      ¿qué gestor estás usando? piensa que no todos los gestores implementan exactamente el estándar. Los que probablemente más se acercan al estándar son PostgreSQL y Db2.
      Dependiendo del gestor, tendrá sus matices. De hecho, por ejemplo, Oracle usa otro sistema «CONNECT BY PRIOR», etc…

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad