Introducción a Pandas

Pandas es probablemente la libreria más popular entre los cientificos de datos para el trabajo y análisis con datos. En estas notas pretendo hacer una breve introducción a pandas que nos de una idea general de la libreria y como instalarla para poder empezar a usarla.

Es una iniciativa de código abierto y puedes ver su página oficial aquí.

En la página oficial vas a encontrar guías de usuarios super completas, pero al mismo tiempo es una documentación muy extensa, y a veces no es sencillo encontrar lo que uno busca. Siempre se puede recurrir también a la IA, aunque a mi modo de ver es mucho más útil para resolver dudas concretas cuando ya se tienen ciertos conocimientos.

Por ello, mi pretensión con estas notas de pandas, es hacer un recorrido sencillo sobre los fundamentos de esta libreria, para que el alumno pueda adquirir los fundamentos de la misma y empezar a usarla.

Pandas se construye sobre Numpy que es una libreria para calculo cientifico que trabaja con arrays. Si no has oido hablar de Numpy, echa primero un vistazo a esta página.

Estructuras de datos

Para trabajar con los datos, pandas provee tres estructuras de datos:

  • Series: Son arrays de una dimensión que se emplean habitualmente para trabajar con series temporales. Estos se parecen muchisimo a Numpy.
  • DataFrame: Esta es la madre del cordero, con los que trabajaremos a todas horas. Son estructuras de datos bidimensionales, tablas de datos que se organizan en filas y columnas. Cuando trabajemos con IA será la estructura en la que almacenemos los distintos datasets con los que entrenaremos y validaremos los modelos.
  • Panel: Son estructuras de datos de más de dos dimensiones. No son muy frecuentes y nosotros no vamos a usarlas.

Instalación

Pandas tiene algunas dependencias obligatorias de otras librerias, que tendremos que tenerlas instaladas antes de instalar pandas y poder trabajar con ella. En la práctica, tendremos que instalar numpy antes de instalar pandas, el resto de dependencias obligatorias vienen con la instalación habitual de python. Así que recuerda, instala numpy:

pip install numpy

Para saber si tienes instalado numpy puedes importar la libreria desde tu código. Ejecuta:

import numpy as np

Si no recibes ningún código de error es que la tienes instalada.

Instalación con pip

Para instalar pandas, disponemos de varias posibilidades. La más directa es usar el gestor de paquetes pip. Abre un terminal o línea de comandos y ejecuta la siguiente instrucción:

pip install pandas

Puedes encontrarte que necesites una versión concreta de pandas, por ejemplo si estás recuperando un proyecto y quieres asegurarte que pandas no tenga incompatibilidades entre las distintas librerias usadas en el proyecto, o si vas a usar una libreria que sólo funcione con una versión concreta de pandas. En cualquier caso, si necesitas instalar una versión concreta de pandas, indicala en la instrucción de instalación:

pip install pandas==2.0.3

Instalación en Jupyter Notebook

Si trabajas con Jupyter Notebook, puedes instalar pandas ejecutando el siguiente código:

!pip install pandas

Tenemos que usar el caracter de escape «!» como atajo para ejecutar comandos del sistema. Ten en cuenta que pip es un comando de consola.

Instalación en Anaconda

Anaconda es una suite que agrupa muchas utilidades para los cientificos de datos. A menudo se trabaja en este entorno, usando Anaconda Navigator para gestionar los entornos de los proyectos, instalando las librerias y herramientas que se necesiten para cada proyecto en su respectivo entorno.

Para instalar pandas en Anaconda empleamos la instrucción:

conda install pandas

Versión de pandas instalada

Finalmente, como comprobación de la instalación, y para asegurarnos de que hemos instalado la versión de pandas que queríamos, podemos ejecutar el siguiente código:

import pandas as pd
print(pd.__version__)

NOTA:

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

NumPy desde cero (Parte II): operaciones con arrays y estadísticas

En este post, continuo con las notas de la libreria Numpy. Me centraré en las explicaciones de las operaciones con los arrays de Numpy. Si es tu primer contacto con Numpy, probablemente sea mejor que eches un vistazo al primer post de Numpy.

Modificación de un array

Empezamos creando un Array unidimensional inicializado con los valores 0-14

array = np.arange(15)
print(f"Dimensiones: {array.shape}")
print(f"Array: \n {array}")

El resultado del script anterior sería:

[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14]

Empleamos el método shape para definir las dimensiones de un Array y sus longitudes

array.shape = (3, 5)
print(f"Dimensiones: {array.shape}")
print(f"Array:\n {array}")

El resultado de ejecutar el código anterior es:

Dimensiones: (3, 5)
Array:
 [[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]

Podemos usar el método reshape para modificar las dimensiones de un array. Este método nos devuelve un nuevo Array que apunta a los mismos datos. Cualquier modificación en un array modificará también el otro.

Modificando un array con reshape:

array_1 = np.arange(12)
print(f"array_1: \n {array_1}")
array_2 = array_1.reshape(3, 4)
print(f"array_2: \n {array_2}")

El resultado sería:

array_1: 
 [ 0  1  2  3  4  5  6  7  8  9 10 11]
array_2: 
 [[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

Modificación del nuevo array:

array_2[1, 1] = 255
print(f"array_2: \n {array_2}")
print(f"array_1: \n {array_1}")

Que nos da el resultado:

array_2: 
 [[  0   1   2   3]
 [  4 255   6   7]
 [  8   9  10  11]]
array_1: 
 [  0   1   2   3   4 255   6   7   8   9  10  11]

Donde se puede observar que se han modificado ambos arrays, ya que el nuevo array apuntaba a los mismos datos que el array inicial

Añadir elementos a un array

Podemos añadir elementos al final del array con la función append. Entrega el resultado en un vector lineal.

numeros=[2,3,4]
numeros.append(34)
print(números)
array_1 = np.arange(7)
elemento_añadir=9
array_2 = np.append(array_1,elemento_añadir)
print(f"array_1: \n {array_1}")
print(f"array_2: \n {array_2}")

El resultado es:

array_1: 
 [0 1 2 3 4 5 6]
array_2: 
 [0 1 2 3 4 5 6 9]

Los argumentos de la función append son el array de partida y el elemento que queramos añadir a dicho array.

O podemos añadir un valor en la posición que le indiquemos con el índice.

array_1 = np.arange(7)
posicion =2
elemento_añadir=9
array_2 = np.insert (array_1,posicion,elemento_añadir)
print(f"array_1: \n {array_1}")
print(f"array_2: \n {array_2}")

Resultado:

array_1: 
 [0 1 2 3 4 5 6]
array_2: 
 [0 1 9 2 3 4 5 6]

El método insert requiere tres parámetros: el array de partida, la posición en la que queremos insertar el elemento y el valor que queremos insertar.

Eliminar elementos de un array

Podemos eliminar elementos por su posición en el array, para lo que usaremos el índice.

Usando la técnica de slicing, indicando los índices que queramos eliminar.

array_1=np.arange(1,7)
indice_eliminar=1
array_2=np.delete(array_1,indice_eliminar)
print(f"array_1: \n {array_1}")
print(f"array_2: \n {array_2}")

Resultado:

array_1: 
 [1 2 3 4 5 6]
array_2: 
 [1 3 4 5 6]

O también podemos pasar una lista de los índices a eliminar

array_1=np.arange(1,7)
indices_eliminar=[1,3,5]
array_2=np.delete(array_1,indices_eliminar)
print(f"array_1: \n {array_1}")
print(f"array_2: \n {array_2}")

Resultado:

array_1: 
 [1 2 3 4 5 6]
array_2: 
 [1 3 5]

También podemos eliminar elementos por su valor, para lo que usaremos o bien una máscara booleana o el método where.

Utilizando una máscara booleana

array_1=np.arange(1,7)
mascara= array_1>=4
array_2= array_1 [mascara]
print(f"array_1: \n {array_1}")
print(f"array_2: \n {array_2}")

Resultado:

array_1: 
 [1 2 3 4 5 6]
array_2: 
 [4 5 6]

Utilizando el método where

array_1=np.arange(1,7)
condicion= array_1>4
array_2=np.delete(array_1, np.where(condicion))
print(f"array_1: \n {array_1}")
print(f"array_2: \n {array_2}")

Resultado:

array_1: 
 [1 2 3 4 5 6]
array_2: 
 [1 2 3 4]

Modificando valores a partir de una condición

Empleando el método where, podemos establecer condiciones para cambiar los valores de un array.

array_1=np.arange(1,11)
condicion= array_1>9
array_2=np.where(condicion,"Si","No")
print(f"array_1: \n {array_1}")
print(f"array_2: \n {array_2}")

Resultado:

array_1: 
 [ 1  2  3  4  5  6  7  8  9 10]
array_2: 
 ['No' 'No' 'No' 'No' 'No' 'No' 'No' 'No' 'No' 'Si']

El método devuelve un array sustituyendo los términos en los que se cumple la condición por el segundo parámetro y los que no cumplen la condición por el tercer parámetro. Tiene un funcionamiento similar a la función IF de Excel.

Operaciones aritméticas con arrays

Creación de dos Arrays unidimensionales

array_1 = np.arange(1, 14, 2)
array_2 = np.arange(1,8)
print(f"Array 1: {array_1}")
print(f"Array 2: {array_2}")

Resultado:

Array 1: [ 1  3  5  7  9 11 13]
Array 2: [1 2 3 4 5 6 7]

Suma de arrays

print(f"Array 1: {array_1}")
print(f"Array 2: {array_2}")
array_suma=array_1 + array_2
print(f"Suma: {array_suma}")

Resultado

Array 1: [ 1  3  5  7  9 11 13]
Array 2: [1 2 3 4 5 6 7]
Suma: [ 2  5  8 11 14 17 20]

Resta de arrays

print(f"Array 1: {array_1}")
print(f"Array 2: {array_2}")
array_resta=array_1 - array_2
print(f"Resta: {array_resta}")

Resultado:

Array 1: [ 1  3  5  7  9 11 13]
Array 2: [1 2 3 4 5 6 7]
Resta: [0 1 2 3 4 5 6]

Multiplicación. Importante: No es una multiplicación de matrices

print(f"Array 1: {array_1}")
print(f"Array 2: {array_2}")
array_multiplicacion=array_1 * array_2
print(f"Multiplicación: {array_multiplicacion}")

Resultado:

Array 1: [ 1  3  5  7  9 11 13]
Array 2: [1 2 3 4 5 6 7]
Multiplicación: [ 1  6 15 28 45 66 91]

División. Importante: Obtendremos un error si hay alguna división por cero

print(f"Array 1: {array_1}")
print(f"Array 2: {array_2}")
array_division=array_1 / array_2
print(f"División: {array_division}")

Resultado:

Array 1: [ 1  3  5  7  9 11 13]
Array 2: [1 2 3 4 5 6 7]
División: [1.         1.5        1.66666667 1.75       1.8        1.83333333
 1.85714286]

Cálculos estadísticos

Numpy proporciona varios métodos para realizar cálculos estadísticos.

En el siguiente programa puede verse varios de estos métodos:

array = np.arange(1,21)
maximo=array.max() #Devuelve el valor máximo del array
minimo=array.min() #Devuelve el valor mínimo del array
media=array.mean() #Media de los elementos del array
suma=array.sum() #Suma de los elementos del array
desviacion_std=array.std() #Desviación estándar de los elementos del array
# Funciones universales eficientes proporcionadas por numpy: ufunc
cuadrado=np.square(array) #Cuadrado de los elementos del array
raiz=np.sqrt(array) #Raiz cuadrada de los elementos del array
exponencial=np.exp(array) #Exponencial de los elementos del array
logaritmo=np.log(array) #Logaritmos de los elementos del array
print(f"Array: \n {array}")
print(f"el elemento mayor del array es: {maximo}")
print(f"el elemento menor del array es: {minimo}")
print(f"la suma de los elementos del array es: {suma}")
print(f"la media de los elementos del array es: {media}")
print(f"la desviación estandard de los elementos del array es: {desviacion_std}")
print("ARRAYS")
print(f"El array con los elementos al cuadrado es: \n {cuadrado}")
print(f"El array con las raices cuadradas de los elementos es: \n {raiz}")
print(f"El array con el exponencial de los elementos es: \n {exponencial}")
print(f"El array con el logaritmo de los elementos es: \n {logaritmo}")

Resultado:

Array: 
 [ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20]
el elemento mayor del array es: 20
el elemento menor del array es: 1
la suma de los elementos del array es: 210
la media de los elementos del array es: 10.5
la desviación estandard de los elementos del array es: 5.766281297335398
ARRAYS
El array con los elementos al cuadrado es: 
 [  1   4   9  16  25  36  49  64  81 100 121 144 169 196 225 256 289 324
 361 400]
El array con las raices cuadradas de los elementos es: 
 [1.         1.41421356 1.73205081 2.         2.23606798 2.44948974
 2.64575131 2.82842712 3.         3.16227766 3.31662479 3.46410162
 3.60555128 3.74165739 3.87298335 4.         4.12310563 4.24264069
 4.35889894 4.47213595]
El array con el exponencial de los elementos es: 
 [2.71828183e+00 7.38905610e+00 2.00855369e+01 5.45981500e+01
 1.48413159e+02 4.03428793e+02 1.09663316e+03 2.98095799e+03
 8.10308393e+03 2.20264658e+04 5.98741417e+04 1.62754791e+05
 4.42413392e+05 1.20260428e+06 3.26901737e+06 8.88611052e+06
 2.41549528e+07 6.56599691e+07 1.78482301e+08 4.85165195e+08]
El array con el logaritmo de los elementos es: 
 [0.         0.69314718 1.09861229 1.38629436 1.60943791 1.79175947
 1.94591015 2.07944154 2.19722458 2.30258509 2.39789527 2.48490665
 2.56494936 2.63905733 2.7080502  2.77258872 2.83321334 2.89037176
 2.94443898 2.99573227]

NOTA:

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

NumPy desde cero (I): arrays, creación y dtype

El paquete numpy  es una librería fundamental para la computación científica con Python y es probable que ya lo tengamos instalado con la instalación de Python.

El nombre de Numpy viene de “Numerical Python”. Se trata de una potente librería para operar con arrays multidimiensionales y a menudo es empleada por otros paquetes como Pandas, SciPy (Scientific Python) y Matplotlib.

Las estructuras de almacenamiento de Numpy tienen algunos beneficios sobre las listas de Python y garantizan cálculos eficientes sobre vectores y matrices. Dispone de una extensa biblioteca de funciones matemáticas.

  • Proporciona arrays N-dimensionales
  • Implementa funciones matemáticas sofisticadas
  • Proporciona herramientas para integrar C/C++ y Fortran
  • Proporciona mecanismos para facilitar la realización de tareas relacionadas con álgebra lineal o números aleatorios

Instalando el paquete Numpy

Este paquete suele venir instalado con la versión de Python, por lo que normalmente probaremos a importarlo directamente, asumiendo que viene instalado. Si al importar la librería no hemos tenido ningún error, significará que ya lo tenemos instalado y no tenemos que hacer nada más.

Si no fuera así, y tuviéramos que instalarlo, podemos hacerlo escribiendo la siguiente instrucción en el interprete:

-m pip install - -user numpy

O si estamos usando el paquete anaconda, desde nuestro entorno activo, abrimos línea de comando y tecleamos:

pip install numpy

En cualquier caso, en la web de la librería se dispone de la información necesaria para instalarla en los distintos entornos que podamos tener.

Para importar la libreria a nuestro programa y poder empezar a usarla, empleamos la instrucción import:

import numpy as np

Normalmente utilizaremos como alias np

Array

Un array es una estructura de datos donde cada uno de ellos está identificado por un índice o un conjunto de ellos.

El tipo más simple de array es el array lineal, también conocido como array unidimensional donde cada elemento es identificado por un índice.

Un array puede tener varias dimensiones y cada elemento del array será identificado por un conjunto de índices, una para cada dimensión. Por ejemplo, una tabla equivale a un array de 2 dimensiones: fila y columna, donde para localizar a un elemento es necesario indicar los valores de ambos índices que identifican al elemento.

En numpy:

  • Las dimensiones se denominan axis
  • El número de dimensiones se denomina rank
  • La distintas dimensiones y sus longitudes se denominan shape
  • El número total de elementos (multiplicación de las longitudes de las dimensiones) se denomina size

Creando un array

Podemos crear el array introduciendo directamente sus valores

import numpy as np
array=np.array([1,"hola",3,4,5,6])
print(array)

Lo que arrojaría el resultado:

['1' 'hola' '3' '4' '5' '6']

También podemos crear un array indicando un rango de valores:

array=np.arange(7)
print(array)

El resultado obtenido es:

[0 1 2 3 4 5 6]

Hay que recordar que los rangos empiezan en cero y terminan en el número anterior al indicado, en este caso 7.

O crear un array introduciendo el primer valor, el último, y el salto entre valores

array=np.arange(1,12,3)
print(array)

Resultado:

[ 1  4  7 10]

Otro opción útil, es crear un array de ceros:

array_ceros = np.zeros((3, 4))
print(array_ceros)

Que da el resultado:

[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

“array_ceros” es un array donde:

  • Tiene 2 axis (dimensiones: filas y columnas)
  • rank = 2 -> el número de dimensiones es 2
  • shape = (2, 4) -> la longitud de sus dimensiones es 2 y 4.
  • size = 8 -> tiene 8 elementos
print(f"shape = {array_ceros.shape}")
print(f"numero dimensiones = {array_ceros.ndim}")
print(f"size = {array_ceros.size}")

Y si ejecutamos el script anterior, obtenemos el resultado:

shape = (3, 4)
numero dimensiones = 2
size = 12

O un array que todos sus valores sean 1:

np.ones((2, 3, 4))

Resultado:

array([[[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]],
       [[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]]])

También podemos crear un array a partir de una lista:

lista=[1,2,3]
array=np.array(lista)
print(array)

Resultado:

[1 2 3]

O podemos utilizar varias listas, una por cada dimensión:

lista_1=[1,2,3]
lista_2=[9,3,11]
lista_3=[0,3,7]
array = np.array([lista_1,lista_2,lista_3])
print(array)

Resultado:

[[ 1  2  3]
 [ 9  3 11]
 [ 0  3  7]]

El argumento para el método array de numpy es una lista, por lo que si queremos construir un array multidimensional a partir de varias lista, tendremos que pasar a este método una lista de listas

print(f"shape = {array.shape}")
print(f"rank = {array.ndim}")
print(f"size = {array.size}")

Y el resultado:

shape = (3, 3)
rank = 2
size = 9

Inicialización del array con valores aleatorios

np.random.rand(2, 3)

Inicialización del array con valores aleatorios conforme a una distribución normal

import numpy as np
import matplotlib.pyplot as plt
np.random.randn(2, 3)
c = np.random.randn(1000000)
plt.hist(c, bins=200)
plt.show()

Y la gráfica que obtenemos seria:

Gráfica de array numpy con distribución gaussiana
Gráfica de array numpy con distribución gaussiana

Inicialización del Array utilizando una función personalizada. La función actúa sobre los valores de los índices

def func(x, y):
    return x + y
np.fromfunction(func, (3, 3))

Y el resultado:

array([[0., 1., 2.],
       [1., 2., 3.],
       [2., 3., 4.]])

Array unidimensional

Creación de un array unidimensional:

lista=[11,22,33,44,55]
array=np.array(lista)
print(f"El array es: {array} y su dimensión es {array.shape[0]}")

Resultado:

El array es: [11 22 33 44 55] y su dimensión es 5

Accediendo a los elementos de un array:

"""Accediendo al segundo elemento del Array"""
array[0]
"""Accediendo al tercer y cuarto elemento del Array"""
print(f"{array[2]} y {array[4]}")

Y el resultado sería: 33 y 55

Igual que con las listas, cuando indicamos un rango, el límite superior del rango no está incluido.

Array multidimensional

Creación de un array multidimensional

array_multidimensional = np.array([[11, 22, 33, 44], [55, 66, 77, 88]])
print(f"Dimensiones: {array_multidimensional.shape}")
print(f"Array multidimensional:  {array_multidimensional}")

Resultado:

Dimensiones: (2, 4)
Array multidimensional:  [[11 22 33 44]
 [55 66 77 88]]

Y para acceder a los elementos del array, lo hacemos igual que cuando accedíamos a los elementos de las listas

array_multidimensional[0, 3]
print(array_multidimensional)
array_multidimensional[1, :]
print(array_multidimensional)
array_multidimensional[0:2, 1]
print(array_multidimensional)

Resultado:

[[11 22 33 44]
 [55 66 77 88]]
[[11 22 33 44]
 [55 66 77 88]]
[[11 22 33 44]
 [55 66 77 88]]

Conversiones de tipo

Al igual que con otros objetos. Es habitual realizar conversiones de tipo con los arrays de Numpy.

Normalmente pasaremos de un array a una lista, empleando el método tolist():

import numpy as np
array1=np.array([1,4,6,7,9,34,45,56,78,99,11])
print(array1)
lista=array1.tolist()
print(lista)

Resultado:

[ 1  4  6  7  9 34 45 56 78 99 11]
[1, 4, 6, 7, 9, 34, 45, 56, 78, 99, 11]

NOTA:

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

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í.