Cuando tienes una base de datos relacional y deseas hacer una consulta, debes utilizar la instrucción SELECT.
Una instrucción SELECT en SQL permite extraer datos de una o varias tablas de una base de datos relacional. En vista de que el lenguaje SQL es un lenguaje declarativo, la instrucción SELECT lo que hace es especificar el resultado que queremos obtener, y no la manera como lo vamos a obtener. Es el motor de la base de datos el que se encarga de crear el mejor plan de ejecución posible para poder obtener el resultado que le pedimos.
La instrucción SELECT está formada por varias partes o cláusulas, algunas obligatorias y otras opcionales, que deben seguir un orden específico y que responden cada una a una pregunta específica:
En resumen, al construir una consulta SELECT, puedes guiarte por las siguientes preguntas:
Me pasaron estos ejercicios para resolverlos con Javascript. Son excelentes para practicar con arreglos y objetos, pero sin duda son muy buenos también para practicar SQL (la prueba de que siempre hay muchas maneras de solucionar un mismo problema).
Comencemos por hablar del negocio:
Se trata de una base de datos relacional que almacena información sobre bancos, sus clientes y las transacciones que éstos realizan.
Un banco posee n clientes. Un cliente puede realizar varias transacciones en varios bancos. Se trata de una relación muchos a muchos que se traduce en 3 tablas a nivel de la base de datos:
Según el modelo Entidad - Relación, las entidades principales son Clients y Banks. Clients realizan transacciones en Banks, es la relación que une a estas dos entidades. Esta relación, al ser muchos a muchos, se traduce en una tabla suplementaria en la base de datos, la tabla Accounts
Tener conocimiento del esquema de la base de datos, facilita la escritura de las consultas, pues tienes más claro la información que está almacenada, en cuáles tablas debes buscarla, cuál es la relación entre las tablas… Si no tienes un esquema de tu base de datos, puedes buscar en Google alguna herramienta que te lo genere.
Si deseas saber cómo se realiza un esquema de base de datos, te recomiendo mi artículo ¿Cómo construir tu base de datos relacional?.
Aunque el modelo del ejemplo es sencillo, permite realizar una serie de consultas bastante interesantes. Con cada nueva pregunta, iremos aumentando el grado de dificultad.
Aquí en mi Github puedes descargar la base de datos SQLite para probar los ejemplos. También podrás encontrar allí mismo unos archivos JSON con los datos y una pequeña aplicación en Python que utiliza la librería SQLAlchemy para crear la base de datos e integrar los datos para la prueba.
¡Ahora sí! Vamos a aplicar la teoría que aprendimos más arriba.
Como son los primeros ejercicios, podemos guiarnos por la tabla de preguntas, con el tiempo y la práctica seguro lo harás mentalmente.
La consulta resultante es entonces:
SELECT name, taxNumber
FROM Clients
ORDER BY taxNumber
No tenemos que aplicar ninguna condición WHERE porque queremos todos los registros. Por defecto el orden es ascendente, por eso tampoco hay necesidad de especificarlo.
El nombre del cliente lo encontramos en la tabla Clients pero el saldo lo encontramos en la tabla Accounts, por lo tanto utilizamos JOIN ... ON ... para indicar cuáles tablas vamos a necesitar y a través de qué campos vamos a hacer el enlace.
El saldo total no se guarda en la base, porque cambia constantemente. Así que debemos calcularlo cada vez que lo necesitamos. Para eso, utilizamos la función SUM. Y como debemos hacer el cálculo para cada cliente, debemos indicarlo en la cláusula GROUP BY
SELECT cl.id, cl.name,
sum(ac.balance)
FROM Clients cl JOIN Accounts ac
ON cl.id = ac.clientId
GROUP BY cl.id
ORDER BY sum(ac.balance) DESC
En este caso, necesitamos información de las 3 tablas:
Utilizamos DISTINCT, de lo contrario obtendremos tantas líneas como registros hay en la tabla Accounts. Haz la prueba y verás.
SELECT DISTINCT bk.name,
cl.name,
cl.taxNumber
FROM Banks bk JOIN Accounts ac
ON bk.id = ac.bankId
JOIN Clients cl
ON cl.id = ac.clientId
ORDER BY bk.name, cl.name
Primero que nada, debemos calcular el saldo total para cada cliente, como lo hicimos en el ejercicio 2. Sin embargo, tenemos una condición sobre ese saldo total, pues queremos sólo aquellos clientes cuyo saldo sea mayor de 25000. Para eso, utilizamos la cláusula HAVING. Esta cláusula permite indicar condiciones sobre los campos producto de una función de agregación, en este caso SUM.
Para facilitar la consulta, utilizamos directamente el identificador (ID) del banco Santander (ac.bankId = 1) en la cláusula WHERE para indicar que sólo queremos los clientes del banco Santander.
SELECT cl.id,
cl.name,
sum(ac.balance)
FROM Clients cl JOIN Accounts ac
ON cl.id = ac.clientId
WHERE ac.bankId = 1
GROUP BY cl.id
HAVING sum(ac.balance) >= 25000
ORDER BY sum(ac.balance) DESC
¡Atención! Lo más importante de esta consulta, es aprender la diferencia entre WHERE y HAVING. Las 2 cláusulas permiten indicar condiciones o filtros sobre los registros, sin embargo, WHERE se aplica directamente a los registros de las tablas, pero HAVING sirve para aplicar condiciones sobre las columnas calculadas.
Esta consulta es muy similar a la ya realizada para obtener el saldo total de cada cliente (ejercicio 2), pero en este caso, cambiamos la tabla Clients por la tabla Banks.
SELECT bk.id,
bk.name,
sum(ac.balance)
FROM Banks bk JOIN Accounts ac
ON bk.id = ac.bankId
GROUP BY bk.id
ORDER BY sum(ac.balance)
Aquí unimos lo que ya hicimos en los ejercicios 2 y 5.
SELECT bk.name,
cl.name,
sum(ac.balance)
FROM Banks bk JOIN Accounts ac
ON bk.id = ac.bankId
JOIN Clients cl
ON ac.clientId = cl.id
GROUP BY bk.name, cl.name
Para cada banco, contamos la cantidad de clientes distintos que tiene. Excluimos los clientes que tienen cuenta en otro banco (NOT IN subconsulta).
SELECT ac.bankId,
count(DISTINCT ac.clientId)
FROM Accounts ac
WHERE ac.clientId NOT IN
(SELECT ac2.clientId
FROM Accounts ac2
WHERE ac.bankId <> ac2.bankId)
GROUP BY ac.bankId
Analicemos la consulta:
Puedes utilizar la siguiente consulta para identificar los clientes que solo tienen una cuenta en un banco:
SELECT ac.bankId,
ac.clientId
FROM Accounts ac
WHERE ac.clientId NOT IN
(SELECT ac2.clientId
FROM Accounts ac2
WHERE ac.bankId <> ac2.bankId)
GROUP BY ac.bankId
Esta consulta debemos hacerla en 2 partes: Una que nos devuelve el saldo total para cada cliente en cada banco y otra parte que selecciona el cliente con el saldo más pequeño.
Aquí te presento 3 soluciones:
Podemos utilizar una consulta con una subconsulta. La subconsulta nos devuelve los ID del banco, del cliente y el saldo total de cada cliente en el banco dado.
Luego, la consulta principal selecciona el cliente con el saldo más pequeño (min(total)).
SELECT bk.name,
cl.name,
min(total)
FROM Banks bk JOIN
(SELECT ac.bankId AS bankId,
ac.clientId AS clientId,
sum(ac.balance) AS total
FROM Accounts ac
GROUP BY ac.bankId, ac.clientId)
ON bk.id = bankId
JOIN Clients cl
ON clientId = cl.id
GROUP BY bankId
En este caso, la subconsulta la asimilamos a una tabla, por eso la incluimos en un JOIN.
Esta solución no es la más óptima, ya que el código es más difícil de comprender.
Las Common Table Expression (CTE) o Expresiones Comunes de Tabla permiten definir una consulta que luego podrá ser utilizada dentro de otra instrucción. Facilitan la escritura de consultas complejas y son temporales.
WITH TotalBalanceBankClient AS ( SELECT ac.bankId AS bankId, ac.clientId AS clientId, sum(ac.balance) AS total FROM Accounts ac GROUP BY ac.bankId, ac.clientId)
SELECT bk.name, cl.name, min(tb.total) FROM Banks bk JOIN TotalBalanceBankClient tb ON bk.id = tb.bankId JOIN Clients cl ON tb.clientId = cl.id GROUP BY tb.bankId
Las CTE son sin duda una mejor solución que la utilización de subconsultas, no solo porque facilitan la lectura y comprensión de consultas complejas, también tienen un mejor performance en caso de que una misma consulta se utilice varias veces en un mismo bloque.
Otra solución es crear una Vista (CREATE VIEW) con la subconsulta de la solución y luego utilizarla dentro de la consulta principal.
Las vistas son una especie de tablas virtuales que se crean a partir de una consulta SQL. Tienen la ventaja de facilitar la escritura de consultas complejas, haciéndolas más lisibles, comprensibles y reutilizables en varias consultas.
CREATE VIEW TotalBalanceBankClient AS
SELECT ac.bankId AS bankId,
ac.clientId AS clientId,
sum(ac.balance) AS total
FROM Accounts ac
GROUP BY ac.bankId, ac.clientId
SELECT bk.name, cl.name, min(tb.total)
FROM Banks bk JOIN TotalBalanceBankClient tb
ON bk.id = tb.bankId
JOIN Clients cl ON tb.clientId = cl.id
GROUP BY tb.bankId
La ventaja de las vistas sobre las CTE es que son elementos que existen dentro de la base de datos y pueden ser utilizdas en diferentes instrucciones.
¿Cuál de las 3 soluciones te parece más sencilla de realizar / comprender?
El SQL es un lenguaje declarativo, es decir, lo que hacemos es indicar el resultado que queremos obtener, luego el motor de la base de datos es el encargado de "decidir" cómo hacer la ejecución.
Conocer el esquema de una base de datos te facilita la realización de consultas, ya que tienes más claro que información tienes y en qué tabla se encuentra.
Aquí presentamos solo unos cuantos ejercicios, queda de tu parte seguir practicando y estudiando este maravilloso lenguaje. Aquí te dejo algunos cursos: