Bases de datos relacionales en Python

En este post vamos a ver como trabajar con las bases de datos relacionales en Python. Para ello emplearemos SQLite que es Open Source y viene instalada por defecto con Python.

SQLite se presenta en un único fichero, como Access, y tiene por objetivo ser parte de la aplicación a la que proporciona los datos. Es decir, no cumple los conceptos de cliente y servidor como otros gestores de bases de datos relacionales como MySQL, SQLServer u Oracle. No obstante, para iniciarnos con las bases de datos relaciones en Python nos vale.

Conexión

Lo primero que tenemos que hacer es establecer una conexión a la base de datos. En el ejemplo que nos ocupa, con SQLite, es bastante sencillo. Utilizamos el método connect al que pasamos como parámetro la ruta de la base de datos.

import sqlite3
import os

#Establezco el directorio de trabajo
os.chdir("c:\Python")

#Abro conexion con la BBDD, si no existe la crea
conexion = sqlite3.connect("Pedidos.db")
conexion.close()

Para simplificar y no tener que ir introduciendo la ruta cada vez que me conecte a la BBDD, defino como directorio de trabajo la carpeta en la que tengo el script de python y la BBDD. Si existe la BBDD nos la abrirá, si no existe, la crea.

Una vez creada la conexión, podemos utilizar el método execute para ejecutar instrucciones SQL sobre la BBDD a la que nos hemos conectado. Vamos a verlo para las distintas operaciones básicas.

Definición de la estructura de la base de datos

En este apartado vamos a ver las operaciones SQL básicas para definir la estructura de nuestra base de datos. Estas serán todas aquellas que nos permitan crear, eliminar o modificar, las distintas tablas e índices de la base de datos.

Estructura de la base de datos

Para practicar las principales operaciones que podemos realizar con una base de datos relacional, vamos a diseñarnos una BBDD muy sencilla.

Esta base de datos va a guardar los pedidos de los clientes de una frutería. Tendremos tres tablas:

  • Productos
  • Clientes
  • Pedidos

La estructura de nuestra BBDD es la siguiente:

Estructura de la base de datos
Estructura de la base de datos

Para asegurar la robustez de la base de datos, defino claves únicas en cada tabla que serán las que emplee para establecer las relaciones.

Creación de una tabla

Empezamos creando las tablas, para lo que tenemos varias opciones.

1.- Utilizando un bloque try-except: pasamos al método execute la correspondiente instrucción SQL.

conexion = sqlite3.connect("Pedidos.db")
try:
    conexion.execute("""create table Productos (
        Id_Producto integer primary key AUTOINCREMENT,
        nombre text,precio real)""")
    print("se creo la tabla Productos")
except sqlite3.OperationalError:
    print("la tabla Productos ya existe")
conexion.close()

2.- Otra manera de crear una tabla sin tener que manejar excepciones.

conexion = sqlite3.connect("Pedidos.db")
conexion.execute("""create table if not exists Clientes(
    Id_Cliente integer primary key AUTOINCREMENT,
    nombre text, tfno text )""")
print("se creo la tabla Clientes")
conexion.close()

3.- Y vamos a crear una tercera tabla: pedidos, que registre el producto, el cliente y las unidades de producto en el pedido. En este caso vamos a definir una clave primaria que se incremente automáticamente cada vez que se añada un pedido

conexion = sqlite3.connect("Pedidos.db")
conexion.execute("""create table if not exists Pedidos(
    Id_Pedido integer primary key AUTOINCREMENT,
    cliente integer,
    producto integer,
    unidades integer)""")
print("se creo la tabla Pedidos")
conexion.close()

Para comprobar si hemos creado las tablas correctamente, podemos usar el siguiente código que hace un select en la tabla especial sqlite_master, donde se almacena información de la estructura de la base de datos.

conexion = sqlite3.connect("Pedidos.db")
# Crea un cursor
cursor = conexion.cursor()
# Ejecuta la consulta para obtener las tablas
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
# Recorre los resultados e imprime los nombres de las tablas
print("Tablas en la base de datos:")
print("----------------------------")
for tabla in cursor.fetchall():
    print(tabla[0])
# Cierra la conexión
conexion.close()

El resultado sería:

Tablas en la base de datos:
----------------------------
Productos
sqlite_sequence
Clientes
Pedidos

Vemos que nos aparece una tabla que no hemos creado, la tabla sqlite_sequence. SQLite la crea automáticamente y la utiliza para mantener un seguimiento de los valores de las claves primarias autoincrementales, asegurando que cada vez que se inserta un nuevo registro en una tabla con una columna de clave primaria autoincremental, el valor de esa columna se incremente correctamente.

Eliminación de una tabla

Para eliminar una tabla, tendremos que utilizar la sentencia SQL: DROP TABLE.

Con el siguiente código eliminaríamos la tabla “Clientes”

conexion = sqlite3.connect("Pedidos.db")
# Crear un cursor para ejecutar comandos SQL
cursor = conexion.cursor()
# Ejecutar la sentencia SQL para eliminar la tabla
tabla_a_eliminar="Clientes"
cursor.execute(f'DROP TABLE IF EXISTS {tabla_a_eliminar};')
# Confirmar los cambios
conexion.commit()
# Cerrar la conexión
conexion.close()

La cláusula IF EXISTS asegura que no se genere un error si la tabla no existe.

Tenemos que confirmar los cambios con conexion.commit() después de ejecutar la sentencia SQL y cerrar la conexión al final.

Si ahora, volvemos a comprobar las tablas que tiene nuestra base de datos, el resultado sería el siguiente:

Tablas en la base de datos:
----------------------------
Productos
sqlite_sequence
Pedidos

Y volvemos a generar la tabla Clientes para dejar la base de datos completa, tal y como la diseñamos.

Creación de un índice

Para crear un índice en una tabla, usamos la sentencia SQL: CREATE INDEX

Con el código siguiente se crearía un índice de nombre: “id_tfno” asociado al campo “tfno” de la tabla “Clientes”

conexion = sqlite3.connect("Pedidos.db")
# Crear un cursor para ejecutar comandos SQL
cursor = conexion.cursor()
# Sentencia SQL para crear un indice en el campo "tfno" de la tabla "Clientes"
sentencia_sql=""" CREATE INDEX IF NOT EXISTS id_tfno ON Clientes (tfno);"""
# Ejecutar la sentencia SQL para crear el índice
cursor.execute(sentencia_sql)
# Confirmar los cambios
conexion.commit()
# Cerrar la conexión
conexion.close()

Con la cláusula IF NOT EXISTS nos aseguramos que el índice solo se cree si no existe ya en la base de datos, evitando posibles errores por ejecutar el script varias veces.

Siempre hay que confirmar los cambios con conexion.commit() después de ejecutar la sentencia SQL y cerrar la conexión cuando terminamos.

Para verificar que el índice se ha generado correctamente, consultamos el esquema de la base de datos para ver si el índice está presente.

conexion = sqlite3.connect("Pedidos.db")
# Crear un cursor para ejecutar comandos SQL
cursor = conexion.cursor()
# Obtener información sobre los índices de la tabla 'Clientes'
tabla = "Clientes"
cursor.execute(f"PRAGMA index_list({tabla});")
indices = cursor.fetchall()
# Verificar si el índice que hemos creado está en la lista de índices
nombre_indice = 'id_tfno'
if any(nombre_indice in index for index in indices):
    print(f"El índice {nombre_indice} está presente en la tabla {tabla}.")
else:
    print(f"El índice {nombre_indice} no está presente en la tabla {tabla}.")
# Cerrar la conexión
conexion.close()

En nuestro caso, la ejecución del código anterior nos daría el resultado:

El índice id_tfno está presente en la tabla Clientes.

Eliminación de un índice

Y para eliminar un índice, usamos la sentencia SQL: DROP INDEX.

Por ejemplo, para eliminar el índice “id_tfno” que creamos en el apartado anterior, usaríamos el siguiente código:

conexion = sqlite3.connect("Pedidos.db")
# Crear un cursor para ejecutar comandos SQL
cursor = conexion.cursor()
# Nombre del índice a eliminar
nombre_indice = 'id_tfno'
# Sentencia SQL para eliminar el índice
sentencia_sql = f"DROP INDEX IF EXISTS {nombre_indice};"
# Ejecutar la sentencia SQL para eliminar el índice
cursor.execute(sentencia_sql)
# Confirmar los cambios
conexion.commit()
# Cerrar la conexión
conexion.close()

Si ejecutamos de nuevo el código para comprobar si existe el índice “id_tfno” en la tabla Clientes, el resultado sería:

El índice id_tfno no está presente en la tabla Clientes.

Modificación de la estructura de una tabla

Una vez generada la estructura, puede darse la situación de que queramos cambiar parte de esa estructura. Podemos querer cambiar el tipo de dato de un campo, añadir un campo nuevo a una tabla u otro tipo de operaciones que modifican la estructura. En general, todas aquellas operaciones que en SQL hacemos con la sentencia: ALTER TABLE.

Supongamos que en nuestro caso, queremos añadir el campo “email” a la tabla “Clientes”. Podríamos hacerlo con el siguiente código:

conexion = sqlite3.connect("Pedidos.db")
# Crear un cursor para ejecutar comandos SQL
cursor = conexion.cursor()
# Sentencia SQL para agregar el campo 'email' a la tabla 'Clientes'
sentencia_sql = """
    ALTER TABLE Clientes
    ADD COLUMN email TEXT;"""
# Ejecutar la sentencia SQL para agregar el campo 'email'
cursor.execute(sentencia_sql)
# Confirmar los cambios
conexion.commit()
# Cerrar la conexión
conexion.close()

Imagino que a estas alturas ya se aprecia que el proceso siempre es el mismo, y sigue los siguientes pasos:

  1. Nos conectamos a la base de datos.
  2. Generamos un cursor que apunta a la base de datos y que emplearemos para ejecutar las sentencias SQL.
  3. Usando el cursor, ejecutamos la sentencia SQL que queramos, empleando el método execute del cursor.
  4. Confirmamos los cambios con el método commit.
  5. Y cerramos la conexión con el método close.

Para comprobar que hemos creado bien el campo “email” en la tabla “Clientes”, podemos ejecutar el siguiente código que nos mostrara la estructura de la tabla “Clientes”

conexion = sqlite3.connect("Pedidos.db")
# Crear un cursor para ejecutar comandos SQL
cursor = conexion.cursor()
# Nombre de la tabla
tabla = 'Clientes'
# Consulta SQL para obtener la estructura de la tabla
consulta_sql = f"PRAGMA table_info({tabla});"
# Ejecutar la consulta SQL
cursor.execute(consulta_sql)
# Obtener los resultados de la consulta
estructura_tabla = cursor.fetchall()
# Imprimir la estructura de la tabla
print("Estructura de la tabla", tabla)
print("----------------------------")
for columna in estructura_tabla:
    nombre_columna = columna[1]
    tipo_dato = columna[2]
    print(f"Nombre: {nombre_columna}, Tipo de dato: {tipo_dato}")
# Cerrar la conexión
conexion.close()

La sentencia PRAGMA en SQLite es una forma de acceder y modificar parámetros específicos del motor de base de datos SQLite, así como obtener información sobre la base de datos, tablas, índices, entre otros. Es importante destacar que PRAGMA no es parte del estándar SQL y su comportamiento puede variar entre diferentes sistemas de gestión de bases de datos.

Si ejecutamos el código anterior, el resultado sería:

Estructura de la tabla Clientes
----------------------------
Nombre: Id_Cliente, Tipo de dato: INTEGER
Nombre: nombre, Tipo de dato: TEXT
Nombre: tfno, Tipo de dato: TEXT
Nombre: email, Tipo de dato: TEXT

Manipulación de datos

Dentro de este apartado, incluyo todas las operaciones para añadir, eliminar o modificar, los datos de las distintas tablas de nuestra base de datos.

Añadir datos

Damos por finalizado el trabajo con la estructura de la base de datos, y ahora comenzamos a trabajar directamente con los datos.

Lo primero que tendremos que hacer será introducir algunos datos, lo que hacíamos en SQL con la sentencia: INSERT INTO.

Por ejemplo, si quisiéramos introducir en la Tabla productos los datos de la siguiente tabla:

NombrePrecio
Manzana2
Pera3
Aguacate7

Tendríamos que usar el siguiente código:

conexion = sqlite3.connect("Pedidos.db")
conexion.execute("insert into Productos (nombre,precio) values (?,?)",("Manzana",2))
conexion.execute("insert into Productos (nombre,precio) values (?,?)",("Pera",3))
conexion.execute("insert into Productos (nombre,precio) values (?,?)",("Aguacate",7))
conexion.commit()  
conexion.close()

Voy a adelantar ahora un código sencillo para recuperar los datos de una tabla y de esta forma poder comprobar que se han añadido los datos anteriores. Sería una consulta SELECT de todos los campos, sin ninguna condición, con lo que recuperamos todos los registros. El código sería:

conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("select * from Productos")
for fila in cursor:
    print(fila)
conexion.close()

Si ejecutamos ese código, el resultado sería:

(1, 'Manzana', 2.0)
(2, 'Pera', 3.0)
(3, 'Aguacate', 7.0)

Donde podemos observar que el campo: “Id_Producto”, se ha rellenado automáticamente por SQLite, como era de esperar, ya que este era un campo Autonumérico.

Actualizar datos

Otra operación muy habitual que realizar con los datos es la operación de actualización de valores que se realiza con la sentencia SQL: UPDATE

Supongamos que al introducir el precio del aguacate nos equivocamos y que su verdadero precio es 5. El código para actualizar el dato sería.

conexion = sqlite3.connect("Pedidos.db")
cursor = conexion.cursor()
nuevo_valor= 5
fruta="Aguacate"
cursor.execute("UPDATE Productos SET precio = ? WHERE nombre = ?",
               (nuevo_valor,fruta))
conexion.commit()  
conexion.close()

Si volvemos a ejecutar la consulta de selección para recuperar todos los registros de la tabla Productos, obtendríamos el siguiente resultado:

(1, 'Manzana', 2.0)
(2, 'Pera', 3.0)
(3, 'Aguacate', 5.0)

Borrar datos

Para borrar datos empleamos la sentencia SQL: DELETE

El código siguiente te muestra un ejemplo de como utilizarla

conexion = sqlite3.connect("Pedidos.db")
cursor = conexion.cursor()
fruta="Aguacate"
cursor.execute("DELETE FROM Productos WHERE nombre = ?",(fruta,))
conexion.commit()  
conexion.close()

Si volvemos a ejecutar la consulta de selección para recuperar todos los registros de la tabla Productos, obtendríamos el siguiente resultado:

(1, 'Manzana', 2.0)
(2, 'Pera', 3.0)

Recuperar datos

Finalmente, tenemos las famosas consultas SELECT para recuperar datos.

Recuerdo rápidamente en este punto la sintaxis de la sentencia SQL:

SELECT [ALL | DISTINCT] <lista de campos> FROM <lista de tablas>
[WHERE ….]
[GROUP BY…]
[HAVING….]
[ORDER BY…]

No obstante, para poder practicar con esta sentencia, tenemos que introducir más datos en nuestra base de datos. Ejecuta el siguiente código para añadir más datos a todas las tablas:

conexion = sqlite3.connect("Pedidos.db")
#rellenamos la tabla Producto
conexion.execute("insert into Productos (nombre,precio) values (?,?)",("Limon",2.5))
conexion.execute("insert into Productos (nombre,precio) values (?,?)",("Melocoton",3.7))
conexion.execute("insert into Productos (nombre,precio) values (?,?)",("Naranja",4))
#llenamos la tabla Clientes
conexion.execute("insert into Clientes (nombre,tfno,email) values (?,?,?)",("Pepe","123456789","pepe@gm.es"))
conexion.execute("insert into Clientes (nombre,tfno,email) values (?,?,?)",("Maria","234567890","maria@gm.es"))
conexion.execute("insert into Clientes (nombre,tfno,email) values (?,?,?)",("Elena","345678901","elena@gm.es"))
#llenamos la tabla Pedidos
conexion.execute("insert into Pedidos (cliente,producto,unidades) values (?,?,?)",(1,2,5))
conexion.execute("insert into Pedidos (cliente,producto,unidades) values (?,?,?)",(1,1,3))
conexion.execute("insert into Pedidos (cliente,producto,unidades) values (?,?,?)",(1,5,1))
conexion.execute("insert into Pedidos (cliente,producto,unidades) values (?,?,?)",(2,4,3))
conexion.execute("insert into Pedidos (cliente,producto,unidades) values (?,?,?)",(2,5,3))
conexion.execute("insert into Pedidos (cliente,producto,unidades) values (?,?,?)",(2,6,2))
conexion.execute("insert into Pedidos (cliente,producto,unidades) values (?,?,?)",(3,2,7))
conexion.execute("insert into Pedidos (cliente,producto,unidades) values (?,?,?)",(3,5,2))
conexion.execute("insert into Pedidos (cliente,producto,unidades) values (?,?,?)",(3,4,2))
conexion.commit()  
conexion.close()

Recuerda que podemos ver los registros de una tabla con el código siguiente, actualizando el nombre de la tabla

tabla="Clientes"
print(f"Registros tabla: {tabla}")
conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute(f"select * from {tabla}")
for fila in cursor:
    print(fila)
conexion.close()

Si lo hemos hecho todo según lo indicado, los contenidos de nuestras tablas serían:

Tabla: “Clientes”:

Registros tabla: Clientes
(1, 'Pepe', '123456789', 'pepe@gm.es')
(2, 'Maria', '234567890', 'maria@gm.es')
(3, 'Elena', '345678901', 'elena@gm.es')

Tabla: “Productos”:

Registros tabla: Productos
(1, 'Manzana', 2.0)
(2, 'Pera', 3.0)
(4, 'Limon', 2.5)
(5, 'Melocoton', 3.7)
(6, 'Naranja', 4.0)

Tabla: “Pedidos”:

Registros tabla: Pedidos
(1, 1, 2, 5)
(2, 1, 1, 3)
(3, 1, 5, 1)
(4, 2, 4, 3)
(5, 2, 5, 3)
(6, 2, 6, 2)
(7, 3, 2, 7)
(8, 3, 5, 2)
(9, 3, 4, 2)

Ahora que tenemos algunos datos en nuestra base de datos, vamos a ver cómo realizar las consultas SELECT más habituales

Seleccionar todos los datos de una tabla

Imaginemos que queremos recuperar todos los datos de la tabla Clientes. La sentencia SQL para realizar esa operación sería:

SELECT * FROM nombre_de_la_tabla;

Y un código para ejecutar esta consulta y presentar los resultados:

#Recuperar todos los registros de una tabla -> SELECT
tabla="Clientes"
print(f"Registros tabla: {tabla}")
# nos conectamos a la base de datos
conexion = sqlite3.connect("Pedidos.db")
# ejecutamos la consulta
cursor=conexion.execute(f"select * from {tabla}")
# pintamos el resultado de la consulta, fila a fila
for fila in cursor:
    print(fila)
# cerramos la conexión con la base de datos
conexion.close()

Al ejecutar el código anterior, obtendríamos:

Registros tabla: Clientes
(1, 'Pepe', '123456789', 'pepe@gm.es')
(2, 'Maria', '234567890', 'maria@gm.es')
(3, 'Elena', '345678901', 'elena@gm.es')
(4, 'Pepe', '123456789', 'pepe@gm.es')
(5, 'Maria', '234567890', 'maria@gm.es')
(6, 'Elena', '345678901', 'elena@gm.es')

Donde puede observarse que he duplicado los registros. Ojo, hay que tener en cuenta que el primer campo: “Id_Cliente” es autonumérico, generado automáticamente por la BBDD cuando insertamos un dato nuevo. Por tanto, si consideramos este campo, nunca tendríamos duplicados. En nuestra consulta consideraremos todos los campos salvo “Id_Cliente”

Teclea el código:

#Recuperar todos los registros únicos de una tabla con la clausula DISTINCT
tabla="Clientes"
print(f"Registros tabla: {tabla}")
conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute(f"select DISTINCT nombre,tfno,email from {tabla}")
for fila in cursor:
    print(fila)
conexion.close()

Si ejecutamos ese código, obtenemos el resultado:

Registros tabla: Clientes
('Pepe', '123456789', 'pepe@gm.es')
('Maria', '234567890', 'maria@gm.es')
('Elena', '345678901', 'elena@gm.es')

Seleccionar campos especificos de una tabla

La sentencia SQL para realizar esa operación sería:

SELECT campo_1, campo_2 FROM nombre_de_la_tabla;

Con el código siguiente, obtendríamos el nombre y el email de los distintos clientes.

conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute(f"select nombre,email from {tabla}")
for fila in cursor:
    print(fila)
conexion.close()

Y obtenemos:

('Pepe', 'pepe@gm.es')
('Maria', 'maria@gm.es')
('Elena', 'elena@gm.es')
('Pepe', 'pepe@gm.es')
('Maria', 'maria@gm.es')
('Elena', 'elena@gm.es')

Lógicamente nos salen duplicados ya que tenemos duplicados en la tabla Clientes. Si quisiéramos obtener resultados únicos, bastaría con añadir la clausula DISTINCT a la consulta.

Seleccionar registros que cumplan una condición

La sentencia SQL para realizar esa operación sería:

SELECT * FROM nombre_de_la_tabla WHERE condicion;

Ahora paso a utilizar la tabla productos, que tenía los valores

(1, 'Manzana', 2.0)
(2, 'Pera', 3.0)
(4, 'Limon', 2.5)
(5, 'Melocoton', 3.7)
(6, 'Naranja', 4.0)

Imaginemos que queremos seleccionar todos aquellos productos que tengan un precio inferior a un valor que introduzca el usuario. Podríamos usar el código siguiente:

max_precio=int(input("Introduce el precio maximo\n"))
conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute(f"select * from Productos WHERE precio<{max_precio}")
for fila in cursor:
    print(fila)
conexion.close()

Y el resultado obtenido, si por ejemplo el usuario introduce 3 como precio máximo, sería:

Introduce el precio maximo
3
(1, 'Manzana', 2.0)
(4, 'Limon', 2.5)

Supongamos que queremos extraer solamente la información de la Manzana. Podríamos hacerlo con cualquiera de las dos sentencias siguientes:

cursor=conexion.execute("select * from Productos where nombre='Manzana' ")
cursor=conexion.execute("""select * from Productos where nombre="Manzana" """)

Si para el argumento del método execute empleamos triple comilla doble, podremos utilizar las comillas dobles para establecer condiciones con textos. Si preferimos usar una comilla doble, tendremos que usar comilla simple para establecer las condiciones con textos.

Por otro lado, la triple comilla doble te permite partir la sentencia SQL en varias líneas, lo cual la hace más fácil del leer.

Ordenar los resultados

Si queremos ordenar los resultados en orden ascendente, la sentencia SQL para realizar esa operación sería:

SELECT * FROM nombre_de_la_tabla ORDER BY columna ASC;

Y si fuera en orden descendente:

SELECT * FROM nombre_de_la_tabla ORDER BY columna DESC;

Con el siguiente código, obtenemos dos consultas: los productos ordenados por precio en orden ascendente y en orden descendente:

# Ordenando ascendente por precio
print("Consulta ordenada por precio ascendente")
conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("select * from Productos order by precio ASC")
for fila in cursor:
    print(fila)
conexion.close()
# Ordenando ascendente por precio
print("Consulta ordenada por precio descendente")
conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("select * from Productos order by precio DESC")
for fila in cursor:
    print(fila)
conexion.close()

El resultado de ejecutar el código anterior sería:

Consulta ordenada por precio ascendente
(1, 'Manzana', 2.0)
(4, 'Limon', 2.5)
(2, 'Pera', 3.0)
(5, 'Melocoton', 3.7)
(6, 'Naranja', 4.0)
Consulta ordenada por precio descendente
(6, 'Naranja', 4.0)
(5, 'Melocoton', 3.7)
(2, 'Pera', 3.0)
(4, 'Limon', 2.5)
(1, 'Manzana', 2.0)

Combinar múltiples condiciones con operadores lógicos

La sentencia SQL para realizar esa operación sería:

SELECT * FROM nombre_de_la_tabla WHERE condicion1 AND/OR condicion2;

Por ejemplo:

conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("select * from Productos where nombre='Manzana' AND precio<5 ")
for fila in cursor:
    print(fila)
conexion.close()

Nos dará el resultado:

(1, 'Manzana', 2.0)

Cálculos con los datos de un campo

Existen varias funciones que se pueden aplicar a un campo, que devuelven distintos cálculos realizados con los datos del campo. Por ejemplo, si quisiéramos obtener el máximo de los valores del campo, tenemos la función MAX(campo). Si fuera el mínimo, la función MIN(campo).

Las sentencias SQL para realizar las operaciones más habituales son:

1.- Máximo de los valores de un campo

SELECT MAX(campo) FROM nombre_de_la_tabla;

2.- Mínimo de los valores de un campo

SELECT MIN(campo) FROM nombre_de_la_tabla;

3.- Suma de los valores de un campo

SELECT SUM(campo) FROM nombre_de_la_tabla;

4.- Promedio de los valores de un campo

SELECT AVG(campo) FROM nombre_de_la_tabla;

Para practicar con estas funciones, prueba el código siguiente:

# Calculo el máximo
conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("select max(precio) from Productos")
resultado = cursor.fetchone()
print(f"precio máximo: {resultado[0]}")
conexion.close()
# Calculo el mínimo
conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("select min(precio) from Productos")
resultado = cursor.fetchone()
print(f"precio mínimo: {resultado[0]}")
conexion.close()
# Calculo el precio medio
conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("select avg(precio) from Productos")
resultado = cursor.fetchone()
print(f"precio medio: {resultado[0]}")
conexion.close()

El resultado de la ejecución es:

precio máximo: 4.0
precio mínimo: 2.0
precio medio: 3.04

Empleando varias tablas

En no pocas ocasiones, necesitaremos cruzar los datos de nuestras tablas. La sentencia SQL que nos permite hacer esto es:

SELECT tabla1.columna, tabla2.columna FROM tabla1 JOIN tabla2 ON tabla1.columna_comun = tabla2.columna_comun;

Cuando trabajemos con este tipo de consultas en las que manejamos varias tablas, tenemos que tener muy presente la estructura de la BBDD. Recupero en este punto la estructura de la nuestra:

Estructura de nuestra base de datos
Estructura de nuestra base de datos

Supongamos que queremos obtener para cada producto del que tenemos un pedido, su precio y las unidades vendidas. Además, queremos que se nos muestre en la consulta el nombre del producto. Así pues, seleccionaremos los campos “nombre” y “precio” de la tabla “Productos” y el campo “unidades” de la tabla “Pedidos”.

El código sería:

conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("""
                        select Productos.nombre, Productos.precio, Pedidos.unidades 
                        from Productos 
                        join Pedidos on Productos.Id_Producto=Pedidos.producto
                        """)
for fila in cursor:
    print(fila)
conexion.close()

Y el resultado obtenido:

('Pera', 3.0, 5)
('Manzana', 2.0, 3)
('Melocoton', 3.7, 1)
('Limon', 2.5, 3)
('Melocoton', 3.7, 3)
('Naranja', 4.0, 2)
('Pera', 3.0, 7)
('Melocoton', 3.7, 2)
('Limon', 2.5, 2)

Si queremos extender la consulta y añadir la información del cliente, pero queremos el nombre del Cliente y no el identificador que aparece en Pedidos, tendremos que involucrar la tabla “Clientes” en la consulta, y habrá que especificar la relación de esta con la tabla “Pedidos”. Además, vamos a aprovechar y recuperamos también el email del Cliente.

El código a ejecutar sería:

conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("""
                        select  Clientes.nombre, Clientes.email, Productos.nombre,
                        Productos.precio, Pedidos.unidades
                        from Productos
                        join Pedidos on Productos.Id_Producto=Pedidos.producto
                        join Clientes on Clientes.Id_Cliente=Pedidos.cliente
                        """)
for fila in cursor:
    print(fila)
conexion.close()

El resultado de la ejecución del código anterior es:

('Pepe', 'pepe@gm.es', 'Pera', 3.0, 5)
('Pepe', 'pepe@gm.es', 'Manzana', 2.0, 3)
('Pepe', 'pepe@gm.es', 'Melocoton', 3.7, 1)
('Maria', 'maria@gm.es', 'Limon', 2.5, 3)
('Maria', 'maria@gm.es', 'Melocoton', 3.7, 3)
('Maria', 'maria@gm.es', 'Naranja', 4.0, 2)
('Elena', 'elena@gm.es', 'Pera', 3.0, 7)
('Elena', 'elena@gm.es', 'Melocoton', 3.7, 2)
('Elena', 'elena@gm.es', 'Limon', 2.5, 2)

Agrupación

Para agrupar los datos en las consultas, disponemos de la clausula GROUP BY.

Supongamos que para la consulta en la que teníamos todos los pedidos de los productos con su nombre, precio y unidades, queremos agrupar los resultado por nombre y precio, y en las columnas presentar el total de las unidades para cada pedidas para cada producto.

Un código que implementaría esa consulta es:

conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("""
                        select Productos.nombre, Productos.precio, 
                        SUM(Pedidos.unidades) as unidades_totales 
                        from Productos 
                        join Pedidos on Productos.Id_Producto=Pedidos.producto
                        group by Productos.nombre,Productos.precio
                        """)
for fila in cursor:
    print(fila)
conexion.close()

Puede observarse que se usa la función SUM para indicar que en esa columna de la consulta presentamos la suma de las unidades pedidas, además hemos definido el nombre de esa columna en la consulta como: “unidades_totales”. Luego, con la clausula GROUP BY, indicamos porque campos queremos agrupar los resultados de la consulta.

Para apreciar el resultado de la consulta, te muestro los datos agrupados que genera esta consulta, frente a los mismos datos sin agrupar:

AgrupadosSin agrupar
(‘Limon’, 2.5, 5)
(‘Manzana’, 2.0, 3)
(‘Melocoton’, 3.7, 6)
(‘Naranja’, 4.0, 2)
(‘Pera’, 3.0, 12)
(‘Pera’, 3.0, 5)
(‘Manzana’, 2.0, 3)
(‘Melocoton’, 3.7, 1)
(‘Limon’, 2.5, 3)
(‘Melocoton’, 3.7, 3)
(‘Naranja’, 4.0, 2)
(‘Pera’, 3.0, 7)
(‘Melocoton’, 3.7, 2)
(‘Limon’, 2.5, 2)

Acceder a los valores de los campos de los resultados de una consulta

Los resultados de una consulta se entregan en forma de una lista de filas. Cada fila se correspondería con una línea de la consulta. Hasta ahora hemos venido recuperando las líneas de la consulta una a una, recorriendo la consulta con un bucle for. Por ejemplo, con el código:

conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("select * from Productos")
for fila in cursor:
    print(fila)
conexion.close()

Cada línea del resultado de la consulta es una tupla que contiene los valores de los campos de esa fila. Por tanto, si queremos recuperar el valor de un campo concreto de una de esas líneas, accederemos a él como accederíamos a los valores de cualquier tupla, utilizando la indexación de la tupla.

Supongamos que queremos ver únicamente los productos y sus precios. Obviamente podríamos hacerlo directamente con una consulta que sólo nos devolviera esos campos, o podemos extraerlos de una consulta más general. Prueba el siguiente código para la segunda opción:

#pinto todas las lineas
conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("select * from Productos")
print("primero presento la consulta pintando todas las lineas")
for fila in cursor:
    print(fila)
conexion.close()
#pinto los campos que me interesan
conexion = sqlite3.connect("Pedidos.db")
cursor=conexion.execute("select * from Productos")
print("ahora pinto solo los campos que me interesan")
for fila in cursor:
    print(f” {fila[1]} : {fila[2]} €")
conexion.close()

Si lo ejecutas, obtendrás el resultado:

primero presento la consulta pintando todas las lineas
(1, 'Manzana', 2.0)
(2, 'Pera', 3.0)
(4, 'Limon', 2.5)
(5, 'Melocoton', 3.7)
(6, 'Naranja', 4.0)
ahora pinto solo los campos que me interesan
Manzana : 2.0 €
Pera : 3.0 €
Limon : 2.5 €
Melocoton : 3.7 €
Naranja : 4.0 €

NOTA:

Este post es parte de la colección “Python”. Puedes ver el índice de esta colección aquí.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.