Programación con Python: SQLite

SQLite es una librería que proporciona un sistema de gestión de bases de datos (SGBD) relacional y se incluye con Python de forma predeterminada. Es una base de datos extremadamente liviana, eficiente y fácil de usar, lo que la hace ideal para aplicaciones pequeñas o medianas que requieren almacenamiento local de datos. A continuación mostraremos cómo utilizar SQLite en Python, con explicaciones y ejemplos prácticos.

Instalación

Python incluye SQLite como parte de la biblioteca estándar, por lo que no es necesario instalar nada adicional para comenzar a trabajar con SQLite en Python.

Conexión a la base de datos

Antes de interactuar con la base de datos, necesitamos establecer una conexión. Para ello, utilizamos el módulo sqlite3 que viene integrado con Python:

import sqlite3

# Crear una conexión o conectarse a una base de datos existente (si no existe, se creará)
conexion = sqlite3.connect("mi_base_de_datos.db")

Crear una tabla

Después de establecer la conexión, ya podemos crear una tabla en la base de datos. Para ello, necesitamos un objeto «cursor» que nos permitirá ejecutar comandos SQL:

# Obtener un objeto cursor
cursor = conexion.cursor()

# Crear una tabla
cursor.execute("CREATE TABLE IF NOT EXISTS usuarios (id INTEGER PRIMARY KEY, nombre TEXT, edad INTEGER)")

Insertar datos

Podemos insertar datos en una tabla utilizando el método execute() para ejecutar un comando SQL de inserción:

# Insertar datos en la tabla
cursor.execute("INSERT INTO usuarios (nombre, edad) VALUES ('Juan', 30)")
cursor.execute("INSERT INTO usuarios (nombre, edad) VALUES ('María', 25)")

# Guardar los cambios (commit) en la base de datos
conexion.commit()

Consultar datos

Para obtener datos de una tabla, podemos utilizar el método execute() para ejecutar una consulta SQL de selección y luego utilizar los métodos fetchone() o fetchall() para obtener los resultados:

# Consultar datos de la tabla
cursor.execute("SELECT * FROM usuarios")

# Obtener un solo resultado
primer_usuario = cursor.fetchone()
print(primer_usuario)  # Salida: (1, 'Juan', 30)

# Obtener todos los resultados
todos_los_usuarios = cursor.fetchall()
print(todos_los_usuarios)  # Salida: [(1, 'Juan', 30), (2, 'María', 25)]

Actualizar datos

Para actualizar los datos de una tabla también usaremos el método execute(), utilizando en este caso como parámetro un comando SQL de actualización:

# Actualizar datos en la tabla
cursor.execute("UPDATE usuarios SET edad = 31 WHERE nombre = 'Juan'")

# Guardar los cambios (commit) en la base de datos
conexion.commit()

Eliminar datos

Para eliminar datos de una tabla, también haremos uso del método execute() utilizando un comando SQL de eliminación:

# Eliminar datos de la tabla
cursor.execute("DELETE FROM usuarios WHERE nombre = 'María'")

# Guardar los cambios (commit) en la base de datos
conexion.commit()

Cerrar la conexión

Es importante cerrar la conexión una vez que hayamos terminado de trabajar con la base de datos:

# Cerrar la conexión
conexion.close()

Transacciones

Las transacciones son bloques de operaciones que se ejecutan como una sola unidad. SQLite permite trabajar con transacciones para asegurar la integridad de los datos. Las transacciones se inician con BEGIN y se confirman con COMMIT. Si algo sale mal, se pueden revertir con ROLLBACK.

Veamos un ejemplo a continuación. Si todas las operaciones dentro de la transacción se realizan correctamente, se confirman las inserciones de los nuevos registros y los cambios se guardan en la base de datos. Si alguna operación falla, los inserciones se revierten y no se realizan cambios en la base de datos:

import sqlite3

conexion = sqlite3.connect("mi_base_de_datos.db")
cursor = conexion.cursor()

# Iniciar transacción
cursor.execute("BEGIN")

try:
    # Realizar operaciones en la base de datos
    cursor.execute("INSERT INTO usuarios (nombre, edad) VALUES ('Ana', 28)")
    cursor.execute("INSERT INTO usuarios (nombre, edad) VALUES ('Pedro', 35)")
    # ... más operaciones SQL ...

    # Confirmar transacción
    cursor.execute("COMMIT")
    print("Transacción exitosa")
except Exception as e:
    # Revertir transacción en caso de error
    cursor.execute("ROLLBACK")
    print("Error en la transacción:", e)

conexion.close()

Consultas parametrizadas

Es recomendable utilizar consultas parametrizadas para evitar la inyección de SQL y mejorar la seguridad de nuestras aplicaciones. Las consultas parametrizadas se crean mediante el método execute() y los valores que se utilizan se pueden pasar como una tupla o un diccionario:

# Consulta parametrizada con tupla
nombre = "Luis"
edad = 40
cursor.execute("INSERT INTO usuarios (nombre, edad) VALUES (?, ?)", (nombre, edad))

# Consulta parametrizada con diccionario
usuario = {"nombre": "Sofía", "edad": 27}
cursor.execute("INSERT INTO usuarios (nombre, edad) VALUES (:nombre, :edad)", usuario)

Ejemplo completo con una tabla

A continuación mostramos un ejemplo completo que resume todas las operaciones que hemos visto:

import sqlite3

# Conexión a la base de datos
conexion = sqlite3.connect("mi_base_de_datos.db")
cursor = conexion.cursor()

# Crear tabla si no existe
cursor.execute("CREATE TABLE IF NOT EXISTS usuarios (id INTEGER PRIMARY KEY, nombre TEXT, edad INTEGER)")

# Insertar datos
cursor.execute("INSERT INTO usuarios (nombre, edad) VALUES ('Juan', 30)")
cursor.execute("INSERT INTO usuarios (nombre, edad) VALUES ('María', 25)")

# Consultar datos
cursor.execute("SELECT * FROM usuarios")
todos_los_usuarios = cursor.fetchall()
print(todos_los_usuarios)  # Salida: [(1, 'Juan', 30), (2, 'María', 25)]

# Actualizar datos
cursor.execute("UPDATE usuarios SET edad = 31 WHERE nombre = 'Juan'")
cursor.execute("SELECT * FROM usuarios")
todos_los_usuarios = cursor.fetchall()
print(todos_los_usuarios)  # Salida: [(1, 'Juan', 31), (2, 'María', 25)]

# Eliminar datos
cursor.execute("DELETE FROM usuarios WHERE nombre = 'María'")
cursor.execute("SELECT * FROM usuarios")
todos_los_usuarios = cursor.fetchall()
print(todos_los_usuarios)  # Salida: [(1, 'Juan', 31)]

# Cerrar conexión
conexion.close()

Creación de múltiples tablas

SQLite ofrece muchas más funcionalidades, como la capacidad de trabajar con múltiples tablas, realizar consultas complejas, utilizar funciones agregadas y trabajar con claves ajenas para establecer relaciones entre tablas.

Para crear múltiples tablas en una base de datos SQLite, simplemente debemos ejecutar múltiples comandos CREATE TABLE. Cada tabla se crea con su propia estructura y columnas:

import sqlite3

conexion = sqlite3.connect("mi_base_de_datos.db")
cursor = conexion.cursor()

# Crear una tabla de usuarios
cursor.execute("CREATE TABLE IF NOT EXISTS usuarios (id INTEGER PRIMARY KEY, nombre TEXT, edad INTEGER)")

# Crear una tabla de productos
cursor.execute("CREATE TABLE IF NOT EXISTS productos (id INTEGER PRIMARY KEY, nombre TEXT, precio REAL)")

conexion.close()

Relaciones entre tablas y claves ajenas

SQLite permite definir claves ajenas para establecer relaciones entre los registros de dos o más tablas. En el siguiente ejemplo crearemos una tabla de «pedidos» que tiene una clave ajena que nos permite llegar a la tabla de «usuarios»:

import sqlite3

conexion = sqlite3.connect("mi_base_de_datos.db")
cursor = conexion.cursor()

# Crear tabla de usuarios
cursor.execute("CREATE TABLE IF NOT EXISTS usuarios (id INTEGER PRIMARY KEY, nombre TEXT, edad INTEGER)")

# Crear tabla de pedidos con clave ajena
cursor.execute("CREATE TABLE IF NOT EXISTS pedidos (id INTEGER PRIMARY KEY, usuario_id INTEGER, fecha TEXT, FOREIGN KEY (usuario_id) REFERENCES usuarios(id))")

conexion.close()

Consultas más complejas

SQLite permite realizar consultas SQL más complejas utilizando cláusulas JOIN, GROUP BY, HAVING, ORDER BY, y otras. Por ejemplo, supongamos que tenemos una tabla «ventas» que contiene información sobre las ventas realizadas en una empresa, relacionando los productos que se han vendido con los usuarios que los han comprado. En este caso, podemos llegar a obtener información sobre el total de ventas por producto utilizando la cláusula GROUP BY y la función agregada SUM:

import sqlite3

conexion = sqlite3.connect("mi_base_de_datos.db")
cursor = conexion.cursor()

# Crear tabla de usuarios
cursor.execute("CREATE TABLE IF NOT EXISTS usuarios (id INTEGER PRIMARY KEY, nombre TEXT, edad INTEGER)")

# Crear tabla de productos
cursor.execute("CREATE TABLE IF NOT EXISTS productos (id INTEGER PRIMARY KEY, nombre TEXT, precio REAL)")

# Crear tabla de ventas
cursor.execute("CREATE TABLE IF NOT EXISTS ventas (id INTEGER PRIMARY KEY, usuario_id INTEGER, producto_id INTEGER, cantidad INTEGER)")

# Insertar datos de ejemplo
cursor.execute("INSERT INTO usuarios (nombre, edad) VALUES ('Juan', 30)")
cursor.execute("INSERT INTO usuarios (nombre, edad) VALUES ('María', 25)")

cursor.execute("INSERT INTO productos (nombre, precio) VALUES ('Producto A', 10.5)")
cursor.execute("INSERT INTO productos (nombre, precio) VALUES ('Producto B', 20.0)")

cursor.execute("INSERT INTO ventas (usuario_id, producto_id, cantidad) VALUES (1, 1, 3)")
cursor.execute("INSERT INTO ventas (usuario_id, producto_id, cantidad) VALUES (2, 2, 2)")
cursor.execute("INSERT INTO ventas (usuario_id, producto_id, cantidad) VALUES (1, 2, 1)")

# Consulta para obtener el total de ventas por producto
cursor.execute("SELECT productos.nombre, SUM(ventas.cantidad) FROM ventas JOIN productos ON ventas.producto_id = productos.id GROUP BY productos.nombre")

resultado = cursor.fetchall()
print(resultado) # [('Producto A', 3), ('Producto B', 3)]

conexion.close()