Apuntes_Python/01_curso/Modulo_4/README.md

2187 lines
62 KiB
Markdown
Raw Normal View History

2022-12-24 22:41:20 -03:00
**Ir a:**
[*Repositorio*](https://gitea.kickto.net/devfzn/Apuntes_Python),
[*Modulo 3*](https://gitea.kickto.net/devfzn/Apuntes_Python/src/branch/master/01_curso/Modulo_3#modulo-3-python-basico)
## Modulo 4 - Python basico
# Bases de Datos
#### [Indice](#modulo-4-python-basico)
- [Bases de Datos](#bases-de-datos)
- [Esquemas y datos](#esquemas-y-datos)
- [Funcionalidad básica SQLiteBrowser](#funcionalidad-básica-sqlitebrowser)
- [El modelo Relacional](#el-modelo-relacional)
- [Integridad de Datos](#integridad-de-datos)
- [Especificación SQL-2003](#especificación-sql-año-2003)
- [Indices](#indices)
- [Structured Query Languaje](#structured-query-language)
- [Sentencias SQL](#sentencias-sql)
- [SQL Joins](#join)
- [SQLite - Manejo de BDs en Python](#manejo-de-bases-de-datos-en-python)
- [Conexion a base de datos](#conexiones-a-base-de-datos)
- [Cursores](#cursores)
- [INSERT](#guardar-objeto-en-base-de-datos)
- [SELECT](#consultar-objetos)
- [UPDATE](#actualizar-objetos)
- [DELETE](#borrar-objetos)
- [ORM y SQLAlchemy](#orm)
- [Mapeo de los modelos](#mapeo-de-los-modelos)
- [Guardar Objetos](#guardar-objetos)
- [Querys](#querys-sqlalchemy)
- [Construyendo relación entre objetos](#construyendo-relación-entre-objetos)
- [Consultando objetos relacionados](#consultando-objetos-relacionados)
- [Borrando objetos](#borrando-objetos)
- [Relaciones entre objetos](#relaciones-entre-objetos)
- [Crear relaciones entre modelos](#crear-relaciones-entre-modelos)
- [Querys 2](#querys-2)
- [Relación muchos a muchos](#relación-muchos-a-muchos)
- [Actividad - Sistema escuela](#actividad-sistema-escuela)
----
# Bases de Datos
Por lo general un programa necesita cargar y guardar datos externos al mismo.
Si los datos son pocos y sin estructura, se pueden utilizar archivos.
Si se necesita manejar grandes volumenes de datos, y además estos datos
están relacionados, usar archivos es inviable, poco eficiente, y requieren
código mas complejo.
El tipo de base de datos mas utilizado en la actualidad son las **bases
de datos relacionales** *(Edgar Frank Codd, IBM, sn Jose, California 1970)*.
Las bases de datos pueden considerarse como un **conjunto de relaciones** o tabas
que contienen registros o filas y cada registro contiene campos (dato especifico).
### Esquemas y Datos
- **Esquema**:
Definición de la estructura de la base de datos, almacena principalmente;
nombre de cada tabla, nombre de cada columna, y tipo de dato de cada columna.
- **Datos**:
Son el contenido de la base de datos en un momento dado
Las bases de datos relacionales utilizan lenguaje **SQL** (**Structured Query Languaje**)
> Se utilizará SQLite, por ser muy liviano.
>
> - SQLite3
> - SQLiteBrowser
### Funcionalidad básica SQLiteBrowser
- ***Database Structure :*** Modificar la estructura de la base de datos
- ***Browser Data :*** Buscar, insertar o eliminar datos en una tabla
- ***Edit Pragmas :*** Perminte modificar parametros de la DB (opcs. avanzadas)
- ***Execute SQL :*** Ejecutar consultas SQL
El programa permite Importar y Exportar tablas, *file import/export*
### El modelo Relacional
Está basado en la lógica de predicados y la teoría de conjuntos.
**Primary Key :**
Asegura que cada registro de una tabla sea único.
No se permite tener dos registros con la misma clave primaria en una misma tabla.
La ***llave primaria*** puede ser un campo o una combinación de estos, que identifiica
cada fila de una tabla de forma única.
**No** pueden existir dos filas que tengan la misma clave primaria.
Son valores ***únicos, no nulos.***
Ej. de posibles claves primarias:
- DNI - ISBM - Patente
- Nro. de tarjeta
**Foreign Key :**
La clave foránea identifica una columna o grupo de columnas en una tabla (tabla
hija o referendo) que se refiere a una columna o grupo de columnas en otra tabla
(tabla maestra o referenciada, puediendo ser la misma tabla).
> Una clave foránea es una limitación referencial entre dos tablas.
> Clave utilizada para vincular o relacionar dos tablas
Una tabla puede contener múltiples claves foráneas referenciando tablas diferentes.
La base de datos hace cumplir las restricciones de referencia.
> Se debe garantizar la integridad de los datos, sus respectivas tablas,
> y filas referenciadas al actualizar o eliminar.
### Integridad de Datos
El sistema de gestión de base de datos relacionales, preserva la integridad
de los datos almacenados (a medida de lo posible).
### Restricciones de integridad
**Integridad de dominio**:
Validación de las restricciones requeridas para una determinada columna de la tabla
ej.
- **Datos Requeridos:** valor de columna ***Not Null***
- **Validacion:** Asegura que los ***tipos de datos*** insertados correspondan al especificado
**Integridad de entidad**:
Comprobación de ***unicidad*** del valor de la ***clave primaria***, al insertar/modificar un registro
**Integridad referencial**
Asegura la integridad entre las llaves ***foránea*** y ***primaria***. (tabla refereciada/tabla referendo)
> Actualizaciones de BDs que pueden corromper la integridad referencial
>
> - Incongruencia entre clave foránea con clave primaria al insertar una fila en la tabla referendo.
>
> - Incongruencia entre clave foránea con clave primaria al actualizar la clave foránea,
> a un valor inexsitente de clave primaria en la tabla refenciada.
>
> - Borrando una fila referenciada. Las filas de la tabla referendo quedan huérfanas.
>
> - Actualización de clave primaria en la tabla referenciada, cuando existe(n) referencia(s)
> a la fila modificada. Las filas de la tabla referendo quedan huérfanas.
### Especificación SQL año 2003
Asegurando la integridad referencial
En caso de actualizar o eliminar registros de una tabla rederenciada (padre),
se definen 5 acciónes referenciales diferentes:
- **CASCADE**
Cuando se elminen/actualizen las filas de una tabla padre, se eliminan/actualizan
las respectivas filas de la tabla hija. **eliminacion** o **actualización en cascada**.
- **RESTRICT**
Impide la modificación del valor de una columna de la tabla padre,
cuando está refereciada al valor de una fila en una tabla hija.
- **NO ACTION**
Similar a RESTRICT. Pero la comprobación de integridad se realiza
despues de intentar modificar la tabla (**UPDATE** o **DELETE**)
- **SET DEFAULT**
Cambia a un valor predefinido, el valor de referencia afectado
- **SET NULL**
Cambia a **NULL** el valor de referencia afectado.
No debe estar definido NOT NULL en la columna hija.
### Indices
Estructura de datos que mejora la velocidad de las operaciones, permitiendo un
rápido acceso a los registros de una tabla, de una BD.
Pueden ser definidos como ***únicos***, actuando como restricción,
previniendo filas idénticas en el índice.
Pueden ser creados a partir de una o más columnas.
El indice ocupa espacio en disco, por lo que puediera ralentizar un **INSERT**,
**UPDATE** o **DELETE**.
*Detalle de implementación:* Los índices son construidos generalmente sobre árboles
[B](https://es.wikipedia.org/wiki/%C3%81rbol-B),
[B+](https://es.wikipedia.org/wiki/%C3%81rbol_B%2B) o
[B\*](https://es.wikipedia.org/wiki/%C3%81rbol-B*)
----
## Structured Query Language
Diseñado para administrar y consultar sistemas de gestión de bases
de datos relacionales.
Maneja álgebra y cálculo relacional para realizar consultas.
**SQL** es un lenguaje de definición, manipulación y contról de datos.
Permite:
* Inserción `INSERT`
* Consulta `SELECT`
* Modificación `UPDATE`
* Borrar `DELETE`
Además de la Creación `CREATE` y Modificación `UPDATE`
de esquemas (schemas) y control de acceso a los datos.
#### Tipos de datos básicos SQL
|Variable Tipo|Tipo de dato|
|-|-|
|`VARCHAR`| Cadena de caracteres |
|`INTEGER`| Enteros con o sin signo|
|`DATE`|Fecha (año, mes y día)|
|`TIME`|Hora (horas, minutos y segundos)|
```sql
-- Creación de una tabla
CREATE TABLE nombre_tabla (
nombre_columna1 tipo_de_dato,
nombre_columna2 tipo_de_dato,
nombre_columna3 tipo_de_dato,
....
);
```
#### Restricciones SQL
|Restricción|Instrucción|
|-|-|
|`NOT NULL`| Impide que el dato sea nulo.
|`UNIQUE`| Impide que los datos se repitan.
|`PRIMARY KEY`| Identifica cada registro de una tabla de forma única.
|`FOREIGN KEY`| Identifica un registro de otra tabla de forma única.
|`CHECK`| Asegura el cumplimiento de una condición para todos los valores en una columna.
|`DEFAULT`| Valor insertado por defecto, al no especificar ninguno.
|`INDEX`| Utilizados para crear y devolver datos de forma eficiente.
#### Creacion de tabla
ej.
```sql
CREATE TABLE categoria_libro (
CODIGO INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
NOMBRE TEXT NOT NULL
);
-- Creación de una tabla a partir de otra
--CREATE TABLE nueva_tabla AS
-- SELECT columna1, columna2, ...
-- WHERE ...;
-- ej.
CREATE TABLE copia_libro AS
SELECT ISBN, TITULO FROM libro
```
#### Modificando el esquema de una tabla
**ALTER TABLE**
**Agregar, eliminar o modificar**, columnas en una tabla:
`ALTER TABLE nombre_tabla nombre_columna tipo_de_dato`
**Agregar o eliminar**, resticciones de columnas:
`ALTER TABLE nombre_tabla ALTER COLUMN`
```sql
-- Agregar una columna
-- ALTER TABLE nombre_tabla ADD nombre_columna tipo_de_dato;
--ej.
ALTER TABLE libro
ADD categoria_id INTEGER REFERENCES categoria_libro(ID);
ALTER TABLE libro
ADD precio INTEGER;
-- Borrar una columna
--ALTER TABLE nombre_tabla DROP COLUMN nombre_columna;
--ej.
ALTER TABLE copia_libro
DROP COLUMN TITULO;
-- SQlite no ofrece esta funcionalidad
-- Modificar tipo de dato de columna
--ALTER TABLE nombre_tabla ALTER COLUMN nombre_columna tipo_de_dato;
--ej.
ALTER TABLE libro
ALTER COLUMN precio REAL;
-- SQlite no ofrece esta funcionalidad
-- Agregar una restricción
ALTER TABLE libro
ADD CONSTRAINT chk_price CHECK (precio>=0);
-- SQlite no ofrece esta funcionalidad
-- Borrar tabla
--DROP TABLE nombre_tabla;
DROP TABLE copia_libro;
-- Borrar datos de una tabla
--TRUNCATE TABLE table_name;
-- SQlite no ofrece esta funcionalidad
```
### Sentencias SQL
`SELECT` **Selecciona** datos desde una *db*, estos son guardados en una tabla,
llamada conjunto de resultado. `WHERE`, filtra los registros según condición dada.
```sql
--SELECT columna1, columna2, ...
--FROM nombre_tabla;
--ej.
SELECT * FROM libro;
-- * = ALL
```
`SELECT DISTINCT` solo selecciona los valores diferentes.
```sql
--SELECT DISTINCT columna1, columna2, ...
--FROM nombre_tabla;
--ej.
SELECT DISTINCT categoria_id FROM libro;
```
`WHERE`
```sql
--SELECT columna1, columna2, ...
--FROM nombre_tabla
--WHERE condición;
--ej.
SELECT * FROM libro WHERE precio < 100;
```
`AND` , `OR` , `NOT`
```sql
--SELECT columna1, columna2, ...
--FROM nombre_tabla
--WHERE condición1 AND condición2 OR condición3 ...;
--ej.
SELECT * FROM libro where precio < 90 OR precio > 10 AND NOT precio = 150;
```
`ORDER BY`
```sql
--SELECT columna1, columna2, ...
--FROM nombre_tabla
--ORDER BY column2, column1, ... ASC|DESC;
--ej.
SELECT * FROM libro ORDER BY precio;
SELECT * FROM libro ORDER BY precio DESC;
```
`LIMIT`
```sql
SELECT * FROM libro LIMIT 1;
```
`INSERT`
```sql
--INSERT INTO nombre_tabla (columna1, columna2, columna3, ...)
-- VALUES (valor1, valor2, valor3, ...);
--ej.
INSERT INTO categoria_libro (NOMBRE) VALUES ('SCI');
INSERT INTO categoria_libro VALUES (4, 'Terror');
```
`UPDATE`
```sql
--UPDATE nombre_taba
--SET comumna1 = valor1, columna2 = valor2, ...
-- WHERE concición;
--ej.
UPDATE categoria_libro SET NOMBRE = 'Acción' WHERE CODIGO = 4;
```
`DELETE`
```sql
--DELETE FROM nombre_tabla WHERE condición;
--ej.
DELETE FROM categoria_libro WHERE CODIGO = 4;
--BORRAR REGISTROS DE LA TABLA
--DELETE FROM nombre_tabla
```
### Join
`LEFT JOIN` Obtener datos de varias tablas relacionadas.
```sql
--SELECT nombre_columna(s)
--FROM tabla1
--LEFT JOIN tabla2
--ON tabla1.nombre_columna = tabla2.nombre_columna;
--ej
SELECT * FROM libro LIB LEFT JOIN categoria_libro COD ON LIB.categoria_id = COD.CODIGO
```
`INNER JOIN` Selecciona resgistros con valores coincidentes en ambas tablas.
```sql
--SELECT nombre_columna(s)
--FROM tabla1
--INNER JOIN tabla2
--ON tabla1.nombre_columna = tabla2.nombre_columna;
--ej.
SELECT * FROM libro L INNER JOIN categoria_libro C ON L.categoria_id = C.CODIGO;
```
Diferencias `INNER JOIN` & `LEFT JOIN`
- **LEFT JOIN**
Une la tabla de la izq. con los valores filtrados de la otra tabla.
- **INNER JOIN**
Filta los registros según condición, para AMBAS tablas.
```sql
--ej. agregado libro sin categoría.
INSERT INTO libro (ISBN, TITULO, DESCRIPCION, AUTOR, precio)
VALUES ('1111', 'Prueba', 'Prueba', 1, 50);
SELECT * FROM libro L LEFT JOIN categoria_libro C ON L.categoria_id = C.CODIGO;
```
![Sql Joins](https://gitea.kickto.net/devfzn/Apuntes_Python/src/branch/master/01_curso/Modulo_4/4-2d_SQL_Joins.png)
![Sql Joins](./4-2d_SQL_Joins.png)
----
## Manejo de Bases de Datos en Python
**Librería SQLite3** (parte de la biblioteca estandar de Python)
Hecha en **C**, provee una BD liviana basada en un archivo, se guarda en el disco,
y no requiere de un proceso aparte, permite el acceso a la BD usando una variante
no standard de lenguaje SQL.
También permite trabjabar con una base de datos en memoria. Por ejemplo.
```python
import sqlite3
# Conexión a base de datos
conn = sqlite3.connect(':memory:')
# Cursor
cursor = conn.cursor()
# Crear tabla
cursor.execute("""CREATE TABLE moneda
(ID integer primary key, nombre text, simbolo text)""")
# Insertar datos de monedas
cursor.execute("INSERT INTO moneda VALUES (1, 'Peso (CLP)', '$')")
cursor.execute("INSERT INTO moneda VALUES (2, 'Dólar (USD)', 'U$')")
# Guardar cambios
conn.commit()
# Consultar monedas
query = "SELECT * FROM moneda"
# Obtener la respuesta (filas)
monedas = cursor.execute(query).fetchall()
print(monedas)
# Cerrar conexión a la base de datos
conn.close()
```
Tipos de Datos Sql-Python
|Tipo Python | Tipo SQLite|
|-|-|
|None|Null|
|int|INTEGER|
|float|REAL|
|str|TEXT|
|bytes|BLOB|
SQLite puede almacenar tipos de datos adicionales usando adaptadores.
SQLite3 convierte tipos de SQLite a diferentes tipos de Python por medio de conversores.
#### Ejemplo adaptador
```python
import sqlite3
class Point:
def __init__(self, x, y):
self.x, self.y = x,y
def __conform__(self, protocol):
if protocol is sqlite3.PrepareProtocol:
return "%f,%f" % (self.x, self.y)
con = sqlite3.connect(":memory:")
cur = con.cursor()
p = Point(5.2, -3.5)
cur.execute("select ?", (p,))
print(cur.fetchone()[0])
# Conversores y adaptadores...
```
Apunte migración
```sql
--Crear BD
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS 'moneda' (
'codigo' TEXT,
'nombre' text,
'symbol' text,
PRIMARY KEY('codigo')
);
COMMIT;
--Agregar campo 'factor'
BEGIN TRANSACTION;
ALTER TABLE 'moneda'
ADD 'factor' REAL;
COMMIT;
```
----
### Conexiones a base de datos
```python
import sqlite3
import hashlib
# Conexión a base de datos
conn = sqlite3.connect(':memory:')
"Cursor: Objeto cursor"
cursor = conn.cursor()
# Crear tabla
cursor.execute("""CREATE TABLE moneda
(ID integer primary key, nombre text, simbolo text)""")
"commit(): hace un commit de la transancción"
conn.commit()
```
`excecute()` : Atajo que crea un cursor y ejecuta la consulta (metodo **excecute** del cursor,
devuelve el cursor), **execute many**, lo mismo pero con múliples consultas.
```python
# Insertar datos de monedas
cursor.execute("INSERT INTO moneda VALUES (1, 'Peso (CLP)', '$')")
cursor.execute("INSERT INTO moneda VALUES (2, 'Dólar (USD)', 'U$')")
"rollback(): revertir cambios, al último commit"
conn.rollback()
# Consultar monedas
query = "SELECT * FROM moneda"
# Obtener la respuesta (filas)
monedas = cursor.execute(query).fetchall()
print(monedas)
# Cerrar conexión a la base de datos
conn.close()
```
#### Create Function
Permite crear una función personalizada, para ser usada dentro las sentencias SQL,
usando el nombre de la función.
Puede devolver cualquier valor soportado por SQLite:
`BYTE`, `STRING`, `INT`, `FLOAT`, `NONE`.
#### Create Agregate
Permite crear una ***función de agregación***, para esto de debe **definir una clase**
que implemente los **metodos step y finalize**.
**finalize**, devuelve el resultado de la agregación, puede devolver cualquier tipo
de dato soportado por SQLite.
#### Ejemplo crear función
```python
def md5sum(t):
return hashlib.md5(t).hexdigest()
conn = sqlite3.connect(':memory:')
# create_function()
conn.create_function("md5", 1, md5sum)
cursor = conn.cursor()
# (?) es reemplazado por "a md5 hash"
cursor.execute("select md5(?)", (b"a md5 hash",))
print(cursor.fetchone()[0])
conn.close()
```
#### Ejemplo crear Agregación
```python
class MiSum:
def __init__(self):
self.cont = 0
def step(self, valor):
self.cont += valor
def finalize(self):
return self.cont
conn = sqlite3.connect(':memory:')
conn.create_aggregate("misum", 1, MiSum)
cursor = conn.cursor()
cursor.execute("CREATE TABLE test(i)")
cursor.execute("INSERT INTO test(i) VALUES (1)")
cursor.execute("INSERT INTO test(i) VALUES (2)")
cursor.execute("SELECT MiSum(i) FROM test")
print(cursor.fetchone()[0])
conn.close()
```
----
### Cursores
Estructura de contról, usada para recorrer los registros (y potencial el procesamiento)
de los registros del resultado de una consulta.
#### Metodos pricipales
- **execute**:
Ejecuta una consulta SQL, puede estar parametrizada.
Soporta todos los tipods de 'playholders' para reemplazo
por variables. Parametros (?) y playjolders con nombre.
- **execute many**:
Ejecuta un conjunto de parametros personalizados.
- **execute script**:
Ejecuta múltiples consultas. Ejecuta un commit antes.
- **fetchone**:
Extrae la siguiente fila del conjunto resultado de la consulta
Devuelve una secuencia, o none si no quedan datos.
- **fetchmany**:
Extrae el sgte. conjunto de filas de la respuesta, devolviendo
una lista, si no hay datos, devuelve una lista vacía.
Se especifica el nro de filas a extraer o por defecto toma el
valor de la variable 'array size' del cursor.
- **fetchall**:
Extrae todas las filas que quedan "sin consumir", en el conjunto
resultado de la consulta. Devuelve una lista.
- **close**:
Cierra el cursor, queda inutilizado, Se levanta una excepción
***ProgrammingError*** si se intenta usar.
- **atributo Connection**:
Solo lectura, provee la conexión a la base de datos utilizada por el cursor.
ej.
```python
import sqlite3
# Conexión a la base de datos
conn = sqlite3.connect(":memory:")
# Cursor
cursor = conn.cursor()
# Crear tabla
cursor.execute("""CREATE TABLE moneda
(ID integer primary key, nombre text, simbolo text)""")
# Insertar datos de monedas
cursor.execute("INSERT INTO moneda VALUES (1, 'Peso (CLP)', '$')")
cursor.execute("INSERT INTO moneda VALUES (2, 'Dólar (USD)', 'U$')")
# Guardar cambios
conn.commit()
# Consultar monedas
query = "SELECT * FROM moneda"
print("Fetch one:")
# Obtener la respuesta (filas)
monedas = cursor.execute(query).fetchone()
print(monedas)
print(cursor.fetchone())
print(cursor.fetchone())
print("Fetch all:")
monedas = cursor.execute(query).fetchall()
print(monedas)
conn.close()
```
----
### Guardar objeto en base de datos
Base de datos [*4-3d_prueba.db*](https://gitea.kickto.net/devfzn/IntroPython/raw/branch/master/01_curso/Modulo_4/4-3d_prueba.db)
```python
import sqlite3
DB_PATH = './4-3d_prueba.db'
class AdminMoneda(object):
def __init__(self, database=None):
if not database:
database = ':memory:'
self.conn = sqlite3.connect(database)
self.cursor = self.conn.cursor()
def insert(self, obj):
query = 'INSERT INTO moneda VALUES ("{}", "{}", "{}")'.format(obj.codigo, obj.nombre, obj.simbolo)
self.cursor.execute(query)
self.conn.commit()
class Moneda(object):
"""
Modelo Moneda
"""
objetos = AdminMoneda(DB_PATH)
def __init__(self, codigo, nombre, simbolo):
self.codigo = codigo
self.nombre = nombre
self.simbolo = simbolo
def __repr__(self):
return u'{}'.format(self.nombre)
clp = Moneda(codigo='CLP', nombre='Pesos', simbolo='$')
usd = Moneda(codigo='USD', nombre='Dolar', simbolo='US$')
eur = Moneda(codigo='USD', nombre='Dolar', simbolo='€')
Moneda.objetos.insert(clp)
Moneda.objetos.insert(usd)
Moneda.objetos.insert(eur)
```
----
### Consultar Objetos
De una base de datos, desde Python
```python
import sqlite3
DB_PATH = './4-3d_prueba.db'
class MonedaNoExiste(Exception):
pass
class AdminMoneda(object):
def __init__(self, database=None):
if not database:
database = ':memory:'
self.conn = sqlite3.connect(database)
self.cursor = self.conn.cursor()
def insert(self, obj):
query = 'INSERT INTO moneda VALUES ("{}", "{}", "{}")'.format(obj.codigo, obj.nombre, obj.simbolo)
self.cursor.execute(query)
self.conn.commit()
def get(self, codigo):
query = 'SELECT * FROM moneda WHERE codigo="{}"'.format(codigo)
self.cursor.execute(query)
datos = self.cursor.fetchone()
if not datos:
raise MonedaNoExiste("No existe la moneda código []".format(codigo))
return Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])
def filtro(self, **kwargs):
codigo = kwargs.get('codigo')
nombre = kwargs.get('nombre')
simbolo = kwargs.get('simbolo')
condicion = ' WHERE '
agregar_y = False
agregar_condicion = False
if codigo:
condicion += 'codigo="{}"'.format(codigo)
agregar_condicion = True
agregar_y = True
if nombre:
if agregar_y:
condicion += ' AND '
condicion += 'nombre="{}"'.format(nombre)
agregar_condicion = True
agregar_y = True
if simbolo:
if agregar_y:
condicion += ' AND '
condicion += 'simbolo="{}"'.format(simbolo)
agregar_condicion = True
query = 'SELECT * FROM moneda'
if agregar_condicion:
query += condicion
self.cursor.execute(query)
resultado = self.cursor.fetchall()
monedas = []
for datos in resultado:
moneda = Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])
monedas.append(moneda)
return monedas
class Moneda(object):
"""
Modelo Moneda
"""
objetos = AdminMoneda(DB_PATH)
def __init__(self, codigo, nombre, simbolo):
self.codigo = codigo
self.nombre = nombre
self.simbolo = simbolo
def __repr__(self):
return u'{}'.format(self.nombre)
```
ej. 4-3d
```python
# clp = Moneda(codigo='CLP', nombre='Pesos', simbolo='$')
# usd = Moneda(codigo='USD', nombre='Dolar', simbolo='US$')
# eur = Moneda(codigo='EUR', nombre='Euro', simbolo='€')
#
# Moneda.objetos.insert(clp)
# Moneda.objetos.insert(usd)
# Moneda.objetos.insert(eur)
print(Moneda.objetos.get(codigo='CLP'))
# Moneda.objetos.get(codigo='ART')
print(Moneda.objetos.filtro(codigo='EUR'))
print(Moneda.objetos.filtro(nombre='Dolar'))
print(Moneda.objetos.filtro(simbolo='€'))
print(Moneda.objetos.filtro())
```
----
### Actualizar Objetos
De una base de datos, desde Python
```python
import sqlite3
DB_PATH = './4-3d_prueba.db'
class MonedaNoExiste(Exception):
pass
class AdminMoneda(object):
def __init__(self, database=None):
if not database:
database = ':memory:'
self.conn = sqlite3.connect(database)
self.cursor = self.conn.cursor()
def insert(self, obj):
query = 'INSERT INTO moneda VALUES ("{}", "{}", "{}")'.format(obj.codigo, obj.nombre, obj.simbolo)
self.cursor.execute(query)
self.conn.commit()
def get(self, codigo):
query = 'SELECT * FROM moneda WHERE codigo="{}"'.format(codigo)
self.cursor.execute(query)
datos = self.cursor.fetchone()
if not datos:
raise MonedaNoExiste("No existe la moneda código []".format(codigo))
return Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])
def filtro(self, **kwargs):
codigo = kwargs.get('codigo')
nombre = kwargs.get('nombre')
simbolo = kwargs.get('simbolo')
condicion = ' WHERE '
agregar_y = False
agregar_condicion = False
if codigo:
condicion += 'codigo="{}"'.format(codigo)
agregar_condicion = True
agregar_y = True
if nombre:
if agregar_y:
condicion += ' AND '
condicion += 'nombre="{}"'.format(nombre)
agregar_condicion = True
agregar_y = True
if simbolo:
if agregar_y:
condicion += ' AND '
condicion += 'simbolo="{}"'.format(simbolo)
agregar_condicion = True
query = 'SELECT * FROM moneda'
if agregar_condicion:
query += condicion
self.cursor.execute(query)
resultado = self.cursor.fetchall()
monedas = []
for datos in resultado:
moneda = Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])
monedas.append(moneda)
return monedas
def update(self, old_obj, obj):
updated = False
add_comma = False
query = 'UPDATE moneda SET '
if old_obj.nombre != obj.nombre:
query += 'nombre="{}"'.format(obj.nombre)
updated = True
add_comma = True
if old_obj.simbolo != obj.simbolo:
if add_comma:
query += ', '
query += 'simobolo="{}"'.format(obj.simbolo)
if updated:
query += ' WHERE codigo="{}"'.format(obj.codigo)
self.cursor.execute(query)
self.conn.commit()
def save(self, obj):
try:
old_obj = self.get(codigo=obj.codigo)
except MonedaNoExiste:
self.insert(obj)
else:
self.update(old_obj, obj)
class Moneda(object):
"""
Modelo Moneda
"""
objetos = AdminMoneda(DB_PATH)
def __init__(self, codigo, nombre, simbolo):
self.codigo = codigo
self.nombre = nombre
self.simbolo = simbolo
def __repr__(self):
return u'{}'.format(self.nombre)
```
ej. 4-3d
```python
# clp = Moneda(codigo='CLP', nombre='Pesos', simbolo='$')
# usd = Moneda(codigo='USD', nombre='Dolar', simbolo='US$')
# eur = Moneda(codigo='EUR', nombre='Euro', simbolo='€')
#
# Moneda.objetos.insert(clp)
# Moneda.objetos.insert(usd)
# Moneda.objetos.insert(eur)
```
ej. 4-3e
```python
# print(Moneda.objetos.get(codigo='CLP'))
#
# Moneda.objetos.get(codigo='ART')
# print(Moneda.objetos.filtro(codigo='EUR'))
# print(Moneda.objetos.filtro(nombre='Dolar'))
# print(Moneda.objetos.filtro(simbolo='€'))
#
# print(Moneda.objetos.filtro())
peso_cl = Moneda.objetos.get(codigo='CLP')
Moneda.objetos.save(peso_cl)
print(peso_cl.codigo)
print(peso_cl.nombre)
print(peso_cl.simbolo)
peso_cl.nombre = 'Pesos (CL)'
Moneda.objetos.save(peso_cl)
peso_cl = Moneda.objetos.get(codigo='CLP')
print(peso_cl.codigo)
print(peso_cl.nombre)
print(peso_cl.simbolo)
peso_uru = Moneda(codigo='UYU', nombre='Pesos Uruguayos', simbolo='$')
Moneda.objetos.save(peso_uru)
pesos_uru = Moneda.objetos.get(codigo='UYU')
print(peso_uru.codigo)
print(peso_uru.nombre)
print(peso_uru.simbolo)
```
----
### Borrar Objetos
De una base de datos, desde Python
```python
import sqlite3
DB_PATH = './4-3d_prueba.db'
class MonedaNoExiste(Exception):
pass
class AdminMoneda(object):
def __init__(self, database=None):
if not database:
database = ':memory:'
self.conn = sqlite3.connect(database)
self.cursor = self.conn.cursor()
def insert(self, obj):
query = 'INSERT INTO moneda VALUES ("{}", "{}", "{}")'.format(obj.codigo, obj.nombre, obj.simbolo)
self.cursor.execute(query)
self.conn.commit()
def get(self, codigo):
query = 'SELECT * FROM moneda WHERE codigo="{}"'.format(codigo)
self.cursor.execute(query)
datos = self.cursor.fetchone()
if not datos:
raise MonedaNoExiste("No existe la moneda código []".format(codigo))
return Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])
def filtro(self, **kwargs):
codigo = kwargs.get('codigo')
nombre = kwargs.get('nombre')
simbolo = kwargs.get('simbolo')
condicion = ' WHERE '
agregar_y = False
agregar_condicion = False
if codigo:
condicion += 'codigo="{}"'.format(codigo)
agregar_condicion = True
agregar_y = True
if nombre:
if agregar_y:
condicion += ' AND '
condicion += 'nombre="{}"'.format(nombre)
agregar_condicion = True
agregar_y = True
if simbolo:
if agregar_y:
condicion += ' AND '
condicion += 'simbolo="{}"'.format(simbolo)
agregar_condicion = True
query = 'SELECT * FROM moneda'
if agregar_condicion:
query += condicion
self.cursor.execute(query)
resultado = self.cursor.fetchall()
monedas = []
for datos in resultado:
moneda = Moneda(codigo=datos[0], nombre=datos[1], simbolo=datos[2])
monedas.append(moneda)
return monedas
def update(self, old_obj, obj):
updated = False
add_comma = False
query = 'UPDATE moneda SET '
if old_obj.nombre != obj.nombre:
query += 'nombre="{}"'.format(obj.nombre)
updated = True
add_comma = True
if old_obj.simbolo != obj.simbolo:
if add_comma:
query += ', '
query += 'simobolo="{}"'.format(obj.simbolo)
if updated:
query += ' WHERE codigo="{}"'.format(obj.codigo)
self.cursor.execute(query)
self.conn.commit()
def save(self, obj):
try:
old_obj = self.get(codigo=obj.codigo)
except MonedaNoExiste:
self.insert(obj)
else:
self.update(old_obj, obj)
def delete(self, obj):
query = 'DELETE FROM moneda WHERE codigo="{}"'.format(obj.codigo)
self.cursor.execute(query)
self.conn.commit()
class Moneda(object):
"""
Modelo Moneda
"""
objetos = AdminMoneda(DB_PATH)
def __init__(self, codigo, nombre, simbolo):
self.codigo = codigo
self.nombre = nombre
self.simbolo = simbolo
def __repr__(self):
return u'{}'.format(self.nombre)
```
ej. 4-3d
```python
# clp = Moneda(codigo='CLP', nombre='Pesos', simbolo='$')
# usd = Moneda(codigo='USD', nombre='Dolar', simbolo='US$')
# eur = Moneda(codigo='EUR', nombre='Euro', simbolo='€')
#
# Moneda.objetos.insert(clp)
# Moneda.objetos.insert(usd)
# Moneda.objetos.insert(eur)
```
ej. 4-3e
```python
# print(Moneda.objetos.get(codigo='CLP'))
#
# Moneda.objetos.get(codigo='ART')
# print(Moneda.objetos.filtro(codigo='EUR'))
# print(Moneda.objetos.filtro(nombre='Dolar'))
# print(Moneda.objetos.filtro(simbolo='€'))
#
# print(Moneda.objetos.filtro())
```
ej. 4-3f
```python
# peso_cl = Moneda.objetos.get(codigo='CLP')
# Moneda.objetos.save(peso_cl)
#
# print(peso_cl.codigo)
# print(peso_cl.nombre)
# print(peso_cl.simbolo)
#
# peso_cl.nombre = 'Pesos (CL)'
# Moneda.objetos.save(peso_cl)
# peso_cl = Moneda.objetos.get(codigo='CLP')
# print(peso_cl.codigo)
# print(peso_cl.nombre)
# print(peso_cl.simbolo)
#
# peso_uru = Moneda(codigo='UYU', nombre='Pesos Uruguayos', simbolo='$')
# Moneda.objetos.save(peso_uru)
#
# pesos_uru = Moneda.objetos.get(codigo='UYU')
# print(peso_uru.codigo)
# print(peso_uru.nombre)
# print(peso_uru.simbolo)
pesos_uru = Moneda.objetos.get(codigo='UYU')
Moneda.objetos.delete(pesos_uru)
# pesos_uru = Moneda.objetos.get(codigo='UYU')
```
----
## ORM
***Object Relational Mapping***
Mapeo Relacional de Objetos
Técnica de programación para convertir tipos de datos utilizados entre un lenguaje
de POO y una base de datos relacional como motor de persistencia.
Hay librerías que desarrollan el mapeo relacional de objetos, SQLAlchemy es uno de
los ORM más utilizados en Python.
Tambíen es posible crear propias herramientas ORM.
ORM permite cambiar de motor de DB mapeando automáticamente, ademas de armar
las consultas SQL, evitando así el uso del lenguaje SQL.
**Instalación**: `$ pip install SQLAlchemy`
### Mapeo de los Modelos
Usando el ORM de SQLAlchemy.
```python
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Sequence
engine = create_engine('sqlite:///:memory:')
Base = declarative_base()
class Author(Base):
__tablename__ = 'author'
id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
firstname = Column(String)
lastname = Column(String)
def __repr__(self):
return "{} {}".format(self.firstname, self.lastname)
print(Author.__table__, end='\n'*2)
# Creacción del esquema
Base.metadata.create_all(engine)
author = Author(firstname="Juanita", lastname='Leon')
print(author.firstname)
print(author.lastname)
print(author.id)
```
`$ python -i`
```python
Python 3.8.2 (default, Jul 16 2020, 14:00:26)
[GCC 9.3.0] on linux
>>> runfile('./4-3h_ORM_mapeo_relacional_de_objetos.py', wdir='./')
Author.__table__
Table('author', MetaData(bind=None), Column('id', Integer(), table=<author>,
primary_key=True, nullable=False, default=Sequence('author_id_seq',
metadata=MetaData(bind=None))), Column('firstname', String(), table=<author>),
Column('lastname', String(), table=<author>), schema=None)
author.firstname
'Juanita'
author.lastname
'Leon'
author.id
```
----
## Guardar Objetos
En la base de datos con ORM de SQLAlchemy
#### Mapeo de los Modelos
Con la base de datos, usando el **ORM** de **SQLAlchemy**
```python
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Sequence
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class Author(Base):
__tablename__ = 'author'
id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
firstname = Column(String)
lastname = Column(String)
def __repr__(self):
return "{} {}".format(self.firstname, self.lastname)
```
Creacción del esquema **CREATE_ALL**
```python
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
```
Agregando objetos a DB, flush automatico al hacer una consulta
posterior a **SESSION.ADD**
```python
author = Author(firstname="Juanita", lastname='Leon')
session.add(author)
our_author = session.query(Author).filter_by(firstname='Juanita').first()
# son la misma instancia
print(author is our_author, '\n')
```
Agregar lista de objetos a la DB, **SESION.ADD_ALL**
```python
session.add_all([Author(firstname='Joél Ez', lastname='Silva'),
Author(firstname='Jorge', lastname='Olivares')])
```
**SESSION.NEW**
Aun no estan guardados en la base, pero están en la sesión.
```python
print(session.new)
# Modificando objeto en sesión, ver con SESSION.DIRTY
author.firstname = 'Anita'
print(session.dirty, '\n')
```
**SESSION.COMMIT**
Enviar todos los cambios a la BD. Se devuelve la conexión al pull de conexiones.
Una vez insertados. Todos identificadores y valores por defecto generados por
la BD están disponibles en instancia.
```python
session.commit()
print(author.id, '\n')
```
**ROLLBACK**
Podemos hacer rollback de todos los cambios no incluidos en un commit.
```python
author.firstname = 'Ruben'
another_author = Author(firstname='Uriel', lastname='Alelias')
session.add(another_author)
res = session.query(Author).filter(Author.firstname.in_(['Ruben', 'Uriel'])).all()
print(res, '\n')
session.rollback()
print(author.firstname)
# another_author no está en sesión, producto del rollback
print(another_author in session)
```
----
### Consultar objetos SQLAlchemy
De la base de datos, con ORM de SQLAlchemy
```python
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Sequence
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import aliased
engine = create_engine('sqlite:///:memory:')
Base = declarative_base()
class Author(Base):
__tablename__ = 'author'
id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
firstname = Column(String)
lastname = Column(String)
def __repr__(self):
return "{} {}".format(self.firstname, self.lastname)
Base.metadata.create_all(engine)
author = Author(firstname="Juanita", lastname='Leon')
Session = sessionmaker(bind=engine)
session = Session()
session.add(author)
session.add_all([Author(firstname='Joél Ez', lastname='Silva'),
Author(firstname='Jorge', lastname='Olivares')])
session.commit()
```
### Querys SQLAlchemy
Query #1: Devuelve los Autores ordenados por ID.
```python
for instance in session.query(Author).order_by(Author.id):
print(instance.firstname, instance.lastname)
# Juanita Leon
# Joél Ez Silva
# Jorge Olivares
```
Query #2: Devuelve el Nombre y Apellido de cada Autor.
```python
for firstname, lastname in session.query(Author.firstname, Author.lastname):
print(firstname, lastname)
# Juanita Leon
# Joél Ez Silva
# Jorge Olivares
```
Query #3: Devuelve el Autor, y su primer nombre.
```python
for row in session.query(Author, Author.firstname).all():
print(row.Author, row.firstname)
# Juanita Leon Juanita
# Joél Ez Silva Joél Ez
# Jorge Olivares Jorge
```
Query #4: Devuelve los Autores, asignando una etiqueta al campo firstname.
```python
for row in session.query(Author.firstname.label('firstname_label')).all():
print(row.firstname_label)
# Juanita
# Joél Ez
# Jorge
```
Query #5: Devuelve el Autor y su primer nombre, definiendo un alias de la tabla.
```python
author_alias = aliased(Author, name='author_alias')
for row in session.query(author_alias, author_alias.firstname).all():
print(row.author_alias)
# Juanita Leon
# Joél Ez Silva
# Jorge Olivares
```
Query #6: Busqueda de todos los Autores, filtrados por slices, del 2 al 3.
```python
for an_author in session.query(Author).order_by(Author.id)[1:3]:
print(an_author)
# Joél Ez Silva
# Jorge Olivares
```
Query #7: Filtrado por autores con nombre 'Joél Ez'
```python
for name, in session.query(Author.firstname).filter_by(firstname='Joél Ez'):
print(name)
# Joél Ez
```
Query #8 Filtrado por apellido del Autor.
```python
for name, in session.query(Author.firstname).filter(Author.lastname == 'Silva'):
print(name)
# Joél Ez
```
Query #9: Filtrado por nombre de autor y apellido.
```python
for an_author in session.query(Author).\
filter(Author.firstname == 'Jorge').\
filter(Author.lastname == 'Olivares'):
print(an_author)
# Jorge Olivares
```
Query #10: Cantidad de autores con el nombre 'Juanita'.
```python
print(session.query(Author).filter(Author.firstname == 'Juanita').count())
# 1
```
### Construyendo relación entre objetos
- Crear columna clave foránea hacia la tabla del modelo a relacionar.
- Crear relación con el modelo utilizando el constructor de relaciones llamado relationship.
- La relación es bidireccional.
Ej. ***relación uno a muchos*** (un autor muchos libros)
```python
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
Base = declarative_base()
class Author(Base):
__tablename__ = 'author'
id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
firstname = Column(String)
lastname = Column(String)
# books = relationship("Book", order_by="Book.id", back_populates="author")
# CASCADE
books = relationship("Book", order_by="Book.id", back_populates="author",
cascade="all, delete, delete-orphan")
def __repr__(self):
return "{} {}".format(self.firstname, self.lastname)
class Book(Base):
__tablename__ = 'book'
id = Column(Integer, Sequence('book_id_seq'), primary_key=True)
isbn = Column(String)
title = Column(String)
description = Column(String)
author_id = Column(Integer, ForeignKey('author.id'))
author = relationship("Author", back_populates="books")
def __repr__(self):
return "{}".format(self.title)
```
Trabajando con ***Objetos Relacionados*** se puede solicitar ***libros por autor*** o ***al autor por sus libros***.
```python
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
# crea un autor
j_rowling = Author(firstname='Joanne', lastname='Rowling')
j_rowling.books = [Book(isbn='9788498387087',
title='Harry Potter y la Piedra Filosofal',
description='La vida de Harry Potter cambia para siempre el ...'),
Book(isbn='9788498382679',
title='Harry Potter y la camara secreta',
description='Tras derrotar una vez mas a lord Voldemort, ...')]
print(j_rowling.books[1])
print(j_rowling.books[1].title)
session.add(j_rowling)
session.commit()
j_rowling = session.query(Author).filter_by(firstname='Joanne').one()
print(j_rowling.books)
```
### Consultando Objetos Relacionados
Devuelve el autor y libro para este isbn. Los imprimimos en pantalla.
```python
for an_author, a_book in session.query(Author, Book).filter(Author.id==Book.author_id).\
filter(Book.isbn=='9788498387087').\
all():
print(an_author)
print(a_book)
```
Devuelve el autor del libro con este isbn
```python
print(session.query(Author).join(Book).filter(Book.isbn=='9788498387087').all())
```
Devuelve los autores de los libros poniendo la condición explícitamente.
```python
print(session.query(Author).join(Book, Author.id==Book.author_id).all())
```
Devuelve los autores de los libros especificando la relación de izquierda a derecha.
```python
print(session.query(Author).join(Author.books).all())
```
Devuelve los autores de los libros para una relación específica.
```python
print(session.query(Author).join(Book, Author.books).all())
```
Devuelve los autores de loslibros utilizando un string.
```python
print(session.query(Author).join('books').all())
```
Devuelve el nombre del autor del libro utilizando un filtro **exists**. Es decir,
si existe algún libro del autor.
```python
print(session.query(Author).join('books').all())
stmt = exists().where(Book.author_id == Author.id)
for name,in session.query(Author.firstname).filter(stmt):
print(name)
```
Devuelve el nombre del autor del libro utilizando un filtro **any**. Es decir,
si el autor tiene algún libro.
```python
print(session.query(Author).join('books').all())
for name, in session.query(Author.firstname).filter(Author.books.any()):
print(name)
```
Devuelve el nombre del autor del libro utilizando un filtro **like**, parecido a ***any*** solo
que se le puede definir una condición, en este caso que el apellido del autor contenga
la subcadena de texto *'Row'*.
```python
for name, in session.query(Author.firstname).filter(Author.books.any(Author.lastname.like('%Row%'))):
print(name)
```
Devuelve los libros donde el autor no se llame Joanne.
```python
print(session.query(Book).filter(~Book.author.has(Author.firstname=='Joanne')).all())
```
### Borrando Objetos
Al borrar un autor el/los libros no se borran. Quedando sin autor, objetos huefanos
```python
session.delete(j_rowling)
print(session.query(Author).filter_by(firstname='Joanne').count())
print(session.query(Book).\
filter(Book.isbn.in_(['9788498387087','9788498382679'])).\
count())
session.rollback()
```
Para borrar el objeto padre y los objetos hijos, definir el atributo cascade con
la opción “**all**, **delete**, **delete-orphan**
```python
books = relationship("Book", order_by="Book.id", back_populates="author",
cascade="all, delete, delete-orphan")
```
```python
del j_rowling.books[1]
print(session.query(Book).filter(Book.isbn.in_(['9788498387087',
'9788498382679'])).count())
session.delete(j_rowling)
print(session.query(Author).filter_by(firstname='Joanne').count())
print(session.query(Book).filter(Book.isbn.in_(['9788498387087',
'9788498382679'])).count())
```
### Relaciones entre objetos
#### Crear relaciones entre modelos
Ej. se muestra una relación uno a muchos (un autor muchos libros):
```python
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Sequence, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
Base = declarative_base()
engine = create_engine('sqlite:///:memory:')
class Author(Base):
__tablename__ = 'author'
id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
firstname = Column(String)
lastname = Column(String)
"""
Se crea una relación con el modelo Book, ordenado por book.id y
con referencia hacia atrás "author".
"""
# books = relationship("Book", order_by="Book.id", back_populates="author")
# CASCADE
books = relationship("Book",
order_by="Book.id",
back_populates="author",
cascade="all, delete, delete-orphan")
def __repr__(self):
return "{} {}".format(self.firstname, self.lastname)
class Book(Base):
__tablename__ = 'book'
id = Column(Integer, Sequence('book_id_seq'), primary_key=True)
isbn = Column(String)
title = Column(String)
description = Column(String)
"""
Se crea la columna de tipo entero, ForeignKey de author.id
"""
author_id = Column(Integer, ForeignKey('author.id'))
"""
Se crea la relación con el modelo Author, con una
referencia hacia atrás a "books".
"""
author = relationship("Author", back_populates="books")
def __repr__(self):
return "{}".format(self.title)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
j_rowling = Author(firstname='Joanne', lastname='Rowling')
print(j_rowling.books)
j_rowling.books = [Book(isbn='9788498387087',
title='Harry Potter y la Piedra Filosofal',
description='La vida de Harry Potter cambia para siempre el ...'),
Book(isbn='9788498382679',
title='Harry Potter y la camara secreta',
description='Tras derrotar una vez mas a lord Voldemort, ...')]
print(j_rowling.books[1])
print(j_rowling.books[1].title)
session.add(j_rowling)
session.commit()
j_rowling = session.query(Author).filter_by(firstname='Joanne').one()
print(j_rowling.books)
```
### Querys 2
Devuelve Autor y Libro para el isbn especificado
```python
for an_author, a_book in session.query(Author, Book).\
filter(Author.id == Book.author_id).\
filter(Book.isbn == '9788498387087').\
all():
print(an_author, '\n', a_book)
```
**Query #2**: Devuelve el Autor del libro con isbn 9788498387087.
```python
session.query(Author).join(Book).filter(Book.isbn == '9788498387087').all())
```
**Query #3**: Devuelve los autores de los libros ***condición explicita***.
```python
session.query(Author).join(Book, Author.id == Book.author_id).all())
```
**Query #4**: Devuelve los autores y los libros, especificando la relación de izq. a derecha.
```python
session.query(Author).join(Author.books).all())
```
**Query #5**: Devuelve los Autores de los libros para una relación específica.
```python
session.query(Author).join(Book, Author.books).all())
```
**Query #6**: Busqueda de todos los Autores, usando un string.
```python
session.query(Author).join('books').all())
```
**Query #7**: Filtrado por autores con nombre 'Joél Ez'").
```python
stmt = exists().where(Book.author_id == Author.id)
for name, in session.query(Author.firstname).filter(stmt):
print(name)
stmt = exists().where(Book.author_id == Author.id)
>>> NameError: name 'exists' is not defined
```
**Query #8**: Devuelve el nombre del autor del libro usando un filtro **any**.
```python
for name, in session.query(Author.firstname).filter(Author.books.any()):
print(name)
```
**Query #9**: Devuelve el nombre del autor, usando un filtro **like**, se puede
definir una condición. Por ejm. que el apellido contenga la cadena *'Row'*.
```python
for name, in session.query(Author.firstname).\
filter(Author.books.any(Author.lastname.like('%Row%'))):
print(name)
```
**Query #10**: Devuelve los libros donde el autor **no** se llame *'Joanne'*.
```python
session.query(Book).filter(~Book.author.has(Author.firstname == 'Joanne')).all(),
```
### Borrar objetos de la BD
```python
session.delete(j_rowling)
print("Autores con nombre Joanne :",
session.query(Author).filter_by(firstname='Joanne').count())
print("Libros según isbn 9788498387087 y 9788498382679 :",
session.query(Book).filter(Book.isbn.in_(['9788498387087', '9788498382679'])).count())
```
----
### Relación muchos a muchos
Agregar una tabla para asociar objetos de dos modelos. La tabla de asociación se indica
en el argumento secondary de la relación.
```python
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
association_table = Table('association', Base.metadata,
Column('left_id', Integer, ForeignKey('left.id')),
Column('right_id', Integer, ForeignKey('right.id')))
class Parent(Base):
__tablename__ = 'left'
id = Column(Integer, primary_key=True)
children = relationship("Child", secondary=association_table,
back_populates="parents")
class Child(Base):
__tablename__ = 'right'
id = Column(Integer, primary_key=True)
parents = relationship("Parent", secondary=association_table,
back_populates="children")
```
```python
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, \
Sequence, ForeignKey, Table, Text
from sqlalchemy.orm import sessionmaker, relationship
Base = declarative_base()
# association table
book_categories = Table('book_categories',
Base.metadata,
Column('book_id', ForeignKey('book.id'), primary_key=True),
Column('category_id', ForeignKey('book_category.id'), primary_key=True))
class Author(Base):
__tablename__ = 'author'
id = Column(Integer, Sequence('author_id_seq'), primary_key=True)
firstname = Column(String)
lastname = Column(String)
books = relationship("Book", order_by="Book.id", back_populates="author")
def __repr__(self):
return "{} {}".format(self.firstname, self.lastname)
class BookCategory(Base):
__tablename__ = 'book_category'
id = Column(Integer, Sequence('book_category_id_seq'), primary_key=True)
name = Column(String)
books = relationship('Book', secondary=book_categories,
back_populates='categories')
def __repr__(self):
return "{}".format(self.name)
class Book(Base):
__tablename__ = 'book'
id = Column(Integer, Sequence('book_id_seq'), primary_key=True)
isbn = Column(String)
title = Column(String)
description = Column(String)
author_id = Column(Integer, ForeignKey('author.id'))
author = relationship("Author", back_populates="books")
categories = relationship('BookCategory', secondary=book_categories,
back_populates='books')
def __repr__(self):
return "{}".format(self.title)
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
j_rowling = Author(firstname='Joanne', lastname='Rowling')
session.add(j_rowling)
j_rowling = session.query(Author).filter_by(firstname='Joanne').one()
book = Book(isbn='9788498387087',
title='Harry Potter y la Piedra Filosofal',
description='La vida de Harry Potter cambia para siempre el ...')
book.categories.append(BookCategory(name='Aventura'))
book.categories.append(BookCategory(name='Accion'))
book.author = j_rowling
print(
session.query(Book).filter(Book.categories.any(name='Aventura')).all()
, '\n',
session.query(Book).filter(Book.author == j_rowling).\
filter(Book.categories.any(name='Aventura')).all()
)
# [Harry Potter y la Piedra Filosofal]
# [Harry Potter y la Piedra Filosofal]
```
----
## Actividad sistema escuela
Realizar un sistema para una escuela.
- Este sistema permite registrar nuevos alumnos, profesores y cursos.
- Un alumno es asignado a un curso y un curso puede tener asociado más de un profesor.
- Los profesores tienen un horario que indica cuando están en cada curso.
- El horario asociará un curso y un profesor para un día de la semana (Lunes, Martes,
Miércoles, Jueves, Viernes, Sábado, Domingo), una hora desde, y una hora hasta.
- El sistema permitirá exportar los alumnos que pertenecen a un curso, el horario de
cada profesor y el horario del curso.
```python
import csv
import datetime
from sqlalchemy import create_engine, ForeignKey, Column, \
Integer, String, Time, Sequence
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
Base = declarative_base()
class Alumno(Base):
__tablename__ = 'alumno'
id = Column(Integer, primary_key=True)
nombre = Column(String, nullable=False)
curso_id = Column(Integer, ForeignKey('curso.id'))
curso = relationship('Curso', back_populates='inscrito')
def __repr__(self):
return self.nombre
class Profesor(Base):
__tablename__ = 'profesor'
id = Column(Integer, primary_key=True)
nombre = Column(String, nullable=False)
horario_prof = relationship('Horario', order_by='Horario.hr_ini', back_populates='profesor')
def __repr__(self):
return self.nombre
class Curso(Base):
__tablename__ = 'curso'
id = Column(Integer, primary_key=True)
nombre = Column(String, nullable=False)
inscrito = relationship('Alumno', order_by='Alumno.id', back_populates='curso')
horario_curso = relationship('Horario', order_by='Horario.hr_ini', back_populates='curso')
def __repr__(self):
return self.nombre
class Horario(Base):
__tablename__ = 'horario'
id = Column(Integer, Sequence('horario_id_seq'), primary_key=True)
dia = Column(Integer)
hr_ini = Column(Time)
hr_fin = Column(Time)
curso_id = Column(Integer, ForeignKey('curso.id'))
profesor_id = Column(Integer, ForeignKey('profesor.id'))
curso = relationship('Curso', back_populates='horario_curso')
profesor = relationship('Profesor', back_populates='horario_prof')
class ExportarCSV(object):
def __init__(self, ruta):
self.ruta = ruta
def exprt_horario_prof(self, profesor):
horario = profesor.horario_prof
with open(self.ruta, 'w') as archivo:
writer = csv.writer(archivo)
for horas in horario:
writer.writerow([horas.dia, horas.hr_ini, horas.hr_fin, horas.curso.nombre])
def export_hr_curso(self, curso):
horario = curso.horario_curso
with open(self.ruta, 'w') as archivo:
writer = csv.writer(archivo)
for horas in horario:
writer.writerow([horas.dia, horas.hr_ini, horas.hr_fin, horas.profesor])
def export_alumnos_curso(self, curso):
inscritos = curso.inscrito
with open(self.ruta, 'w') as archivo:
writer = csv.writer(archivo)
for alumno in inscritos:
writer.writerow([str(alumno)])
def main(*args, **kwargs):
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
# Creación de profesores
prof1 = Profesor(nombre='Profesor_1')
prof2 = Profesor(nombre='Profesor_2')
prof3 = Profesor(nombre='Profesor_3')
session.add(prof1)
session.add(prof2)
session.add(prof3)
# Creación de Cursos
curso1 = Curso(nombre='Curso_1')
curso2 = Curso(nombre='Curso_2')
curso3 = Curso(nombre='Curso_3')
session.add(curso1)
session.add(curso2)
session.add(curso3)
# Creación de Alumnos
alumno1 = Alumno(nombre='Alumno_1', curso=curso1)
alumno2 = Alumno(nombre='Alumno_2', curso=curso2)
alumno3 = Alumno(nombre='Alumno_3', curso=curso3)
session.add(alumno1)
session.add(alumno2)
session.add(alumno3)
# Creación de objetos Hora
hora1 = datetime.time(9, 0, 0)
hora2 = datetime.time(12, 0, 0)
hora3 = datetime.time(13, 0, 0)
hora4 = datetime.time(15, 0, 0)
# Creación de horas para iniciio y termino de clases
horario1 = Horario(dia=2, hr_ini=hora1, hr_fin=hora2, curso=curso1, profesor=prof1)
horario2 = Horario(dia=3, hr_ini=hora2, hr_fin=hora3, curso=curso2, profesor=prof2)
horario3 = Horario(dia=4, hr_ini=hora2, hr_fin=hora3, curso=curso3, profesor=prof3)
horario4 = Horario(dia=5, hr_ini=hora3, hr_fin=hora4, curso=curso1, profesor=prof3)
session.add(horario1)
session.add(horario2)
session.add(horario3)
session.add(horario4)
session.commit()
# Exportar Datos
ExportarCSV('Horario_Docente_{}'.format(prof1)).exprt_horario_prof(prof1)
ExportarCSV('Horario_Docente_{}'.format(prof2)).exprt_horario_prof(prof2)
ExportarCSV('Horario_Docente_{}'.format(prof3)).exprt_horario_prof(prof3)
ExportarCSV('Horario_Curso_{}'.format(curso1)).export_hr_curso(curso1)
ExportarCSV('Horario_Curso_{}'.format(curso2)).export_hr_curso(curso2)
ExportarCSV('Horario_Curso_{}'.format(curso3)).export_hr_curso(curso3)
ExportarCSV('Alumnos_Inscritos_{}'.format(curso1)).export_alumnos_curso(curso1)
ExportarCSV('Alumnos_Inscritos_{}'.format(curso2)).export_alumnos_curso(curso2)
ExportarCSV('Alumnos_Inscritos_{}'.format(curso3)).export_alumnos_curso(curso3)
if __name__ == "__main__":
main()
```
```
──────────────────────────────────
File: Horario_Docente_Profesor_1
──────────────────────────────────
2,09:00:00,12:00:00,Curso_1
──────────────────────────────────
──────────────────────────────────
File: Horario_Docente_Profesor_2
──────────────────────────────────
3,12:00:00,13:00:00,Curso_2
──────────────────────────────────
──────────────────────────────────
File: Horario_Docente_Profesor_3
──────────────────────────────────
4,12:00:00,13:00:00,Curso_3
5,13:00:00,15:00:00,Curso_1
──────────────────────────────────
──────────────────────────────────
File: Horario_Curso_Curso_1
──────────────────────────────────
2,09:00:00,12:00:00,Profesor_1
5,13:00:00,15:00:00,Profesor_3
──────────────────────────────────
File: Horario_Curso_Curso_2
──────────────────────────────────
3,12:00:00,13:00:00,Profesor_2
──────────────────────────────────
──────────────────────────────────
File: Horario_Curso_Curso_3
──────────────────────────────────
4,12:00:00,13:00:00,Profesor_3
──────────────────────────────────
──────────────────────────────────
File: Alumnos_Inscritos_Curso_1
──────────────────────────────────
Alumno_1
──────────────────────────────────
──────────────────────────────────
File: Alumnos_Inscritos_Curso_2
──────────────────────────────────
Alumno_2
──────────────────────────────────
──────────────────────────────────
File: Alumnos_Inscritos_Curso_3
──────────────────────────────────
Alumno_3
──────────────────────────────────
```