<jupyter_start><jupyter_text>MongoDB -> NO RELACIONALEste notebook permite entender a nivel básico el funcionamiento de MongoDB y más concretamente, cómo se interacciona con esta base de datos a través de Python.Para poder hacer uso del mismo es necesario:1. Tener instalado MongoDB (https://docs.mongodb.com/manual/installation/)2. Tener activo el servicio de MongoDB  MódulosPara poder trabajar con MongoDB, más allá de haber instalado el motor de base de datos en local (localhost), es necesario cargar el módulo de Python que permite interactuar con él. Sería el equivalente de SQLAlchemy que habíamos utilizado en las sesiones de SQL para MySQL.<jupyter_code># Si no tenemos el módulo de pymongo, lo importamos primero
!pip3 install pymongo
# Importamos las librerías
# Genéricas pandas
import pandas as pd

#Específica de MongoDB MongoClient de pymongo
from pymongo import MongoClient<jupyter_output><empty_output><jupyter_text>Conexión con MongoDBUna vez tenemos importadas las librerías, podemos realizar la conexión a la base de datos. Para ello y, al igual que hicimos con MySQL, tenemos que crear una cadena de conexión. El formato es similiar, aunque en este caso algo más simple. - cadena_conexion "mongodb://localhost:27017"- y la conexión MongoClient(cadena_conexion)<jupyter_code># Creamos la cadena de conexión
cadena_conexion = "mongodb://localhost:27017"
# Instanciamos la conexión
con = MongoClient(cadena_conexion)<jupyter_output><empty_output><jupyter_text>Una vez definida la conexión (no debería darnos error ya que aquí todavía no estamos conectando, simplemente inicializando), ya podemos empezar a "administrar" la base de datos de MongoDB.Para ello, primero debemos instanciar el objeto administrador (client.admin) y después ya podemos hacerle consultas, como por ejemplo, el estado del servicio client.admin.command("serverSatus") y mostrarlo por pantalla.>Nota: si los siguientes comandos tardan en ejecutar, significa que probablemente el servicio de MongoDB en localhost no está activo (net start mongodb). Si el servicio está en marcha, podremos ver el output del estado como un JSON.<jupyter_code># Asignamos la instancia del objeto administrador o funciones de administrador con.admin
client = con.admin
# Podemos consultar diferentes aspectos del servidor de base de datos, como por ejemplo su estado o configuración del servidor de MongoDB client.command("serverStatus").
status = client.command("serverStatus")
print(status)<jupyter_output>9ms': 0, 'file system read latency histogram (bucket 5) - 500-999ms': 0, 'file system read latency histogram (bucket 6) - 1000ms+': 0, 'file system write latency histogram (bucket 1) - 10-49ms': 0, 'file system write latency histogram (bucket 2) - 50-99ms': 0, 'file system write latency histogram (bucket 3) - 100-249ms': 0, 'file system write latency histogram (bucket 4) - 250-499ms': 0, 'file system write latency histogram (bucket 5) - 500-999ms': 0, 'file system write latency histogram (bucket 6) - 1000ms+': 0, 'operation read latency histogram (bucket 1) - 100-249us': 0, 'operation read latency histogram (bucket 2) - 250-499us': 2, 'operation read latency histogram (bucket 3) - 500-999us': 2, 'operation read latency histogram (bucket 4) - 1000-9999us': 0, 'operation read latency histogram (bucket 5) - 10000us+': 0, 'operation write latency histogram (bucket 1) - 100-249us': 0, 'operation write latency histogram (bucket 2) - 250-499us': 1, 'operation write latency histogram (bucket 3[...]<jupyter_text>Listar bases de datosSi queremos ver qué bases de datos tenemos en nuestra instancia de MongoDB, tenemos que utilzar métodos propios de la librearía de pymongo. Es el equivalente a hacer un "SHOW DATABASES" en SQL, pero en vez de ejecutar una sentencia SQL, aquí lo que hacemos es una llamada al método "list_database_names" de la conexión de MongoClient.<jupyter_code># consultamos la lista de bases de datos
con.list_database_names()<jupyter_output><empty_output><jupyter_text>Creación de una base de datosCrear una base de datos con pymongo se hace a través de una única y sencilla instrucción:db = con.[nombre de la bbdd]>Nota: es importante indicar que la base de datos NO se crea, simplemente se instancia. Con pymongo, la base de datos no se creará hasta que no le insertemos como mínimo un documento.<jupyter_code># Creamos una nueva base de datos que se llame NUCLIO y la asignamos a una variable
db = con.NUCLIO

# aun no apare NUCLIO
con.list_database_names()
# tenemos ahora creada la BBDD NUCLIO, pero ahora necesitamos crear "filas" / "registro" (a mano, con nomenclatura JSON)<jupyter_output><empty_output><jupyter_text>Crear documentosRecordemos que un documento en MongoDB es el equivalente a una fila (registro) de una tabla en MySQL. Aquí empezamos a entender la flexibilidad de MongoDB vs los sistemas relacionales que hemos estado viendo, ya que vamos a crear registros (documentos) de diferentes estructuras en una misma tabla (colección).A modo de ejemplo, vamos a crear un documento equivalente a un restaurante, un país y una región, con la siguiente información:Restaurante:- nombre: KFC- barrio: Eixample- rating: 4- anio_inauguracion: 2019País:- nombre: España- poblacion: 47000000Región:- id: Europa- pais: EspañaLos documentos siguen el formato JSON, un formato universal que permite compartir información entre sistemas de cualquier naturaleza. Los JSON son diccionarios de diccionarios, permitiendo incluir todo tipo de formatos, incluso listas.<jupyter_code># Primero creamos el restaurante (que es un REGISTRO DE TABLA (FILA/ITEMS) / DICCIONARIO)
restaurante = {
    "nombre": "KFC",
    "barrio": "Eixample",
    "rating": 4,
    "anio_inauguracion": 2019
}
# Después el país
pais = {
    "nombre": "España",
    "poblacion": 47000000
}
# Y finalmente la región
region = {
    "id": "Europa",
    "pais": "España"
}

display(restaurante)
display(pais)
display(region)<jupyter_output><empty_output><jupyter_text>Para poder insertar los documentos, es necesario hacerlo en una colección. Al igual que antes, crear una colección es muy sencillo en MongoDB y únicamente requiere de una línea. En este caso, vamos a crear la colección a partir de la instancia de la base de datos, a la vez que insertamos, uno a uno, los documentos antes creados. Si no insertamos ningún documento, la colección queda como simple instancia, pero no se crea la colección en sí dentro de MongoDB. Tened en cuenta que estamos utilizando un módulo (pymongo) para poder interaccionar con MongoDB.db.[nombre_coleccion].insert_one(documento)<jupyter_code># insertamos el restaurante en MongoDB dentro de una COLECCION
# BBDD (NUCLIO) -> DCOUMENTOS -> COLECCIONES -> CAMPOS
db.coleccion_prueba.insert_one(restaurante)

# imprime la istancia creada
# insertamos el país
db.coleccion_prueba.insert_one(pais)
# insertamos la región
db.coleccion_prueba.insert_one(region)<jupyter_output><empty_output><jupyter_text>Vamos a buscar la información que hemos insertadoVamos a buscar dentro de la colección. Para ello, utilizamos el método "find", que es el equivalente al select. Por ahora, no vamos a pasar ningún tipo de filtro al find, con lo que debería retornarnos todos los documentos incluidos en dicha colección.>Nota: el resultado del método find, debemos convertirlo en una lista para poderlo visualizar y tratar. En sí, es un tipo Cursor, es decir, un punto que apunta a un elmento de memoria. Es por eso, que pare ver el contenido en sí, debemos transformarlo a lista.db.[nombre_coleccion].find() Si analizamos el resultado anterior, podemos ver:1. Es una lista con un total de 3 elementos [1, 2, 3].2. Cada elemento es un JSON, con la estructura que habíamos definido al realizar el insert.3. Como no hemos definido ninguna clave en nuestros documentos, MongoDB ha creado una para cada uno de ellos (string).<jupyter_code># Hacemos una búsqueda sin filtros (list)
# encontramos documentos que estan dentro de una coleccion
# select de registro dentro de una coleccion
db.coleccion_prueba.find()

# db.coleccion_prueba.find()   ->   instancia un cursor
list(db.coleccion_prueba.find())

# saca 3 DOCUMENTOS de la COLECCION de cualquier tipo posible<jupyter_output><empty_output><jupyter_text>Pongamos en práctica MongoDB a través de un dataset de reviews de restaurantesEn vez de ir creando documentos de forma aleatoria, vamos a utilizar un dataset preexistente, concretamente el que tenemos en la carpeta ficheros_mongoDB/reviews.csv.Para ello, vamos a utilizar un dataframe de puente. Con el dataframe, vamos a importar la información del CSV y a partir de ahí vamos a generar los "JSON" de cada fila del Dataframe para que constituya un documento. El JSON, como habíamos dicho, es un equivalente de un diccionario. Del dataframe, pasaremos a diccionario y de ahí a MongoDB.- De CSV a dataframe- De dataframe a diccionario> https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_dict.html En la documentación podemos encontrar los formatos de generación de diccionarios a partir de un dataframe. Nosotros queremos que el dataframe se convierta en una lista de diccionarios (una lista de documentos), con lo que optaremos por el valor "records" para el parámetro "orient".- De diccionario a MongoDB  Importar e insertar<jupyter_code># Importamos el CSV y mostramos el dataframe dfRatings
dfRatings = pd.read_csv("ficheros_mongoDB/reviews.csv")
dfRatings
# Convertimos el dataframe en una lista de diccionarios y lo mostramos (lRestuarantes)
# Nosotros queremos que el dataframe se convierta en una lista de diccionarios (una lista de documentos), con lo que optaremos por el valor "records" para el parámetro "orient".
lRestaurantes = dfRatings.to_dict(orient="records")
display(type(lRestaurantes))
display(lRestaurantes)
# el resultado es una lista<jupyter_output><empty_output><jupyter_text>Como puede verse en la línea anterior, el método to_dict nos ha generado un listado y dentro de cada elemento (fila) de la lista, aparece un diccionario con los pares clave (columna) -> valor (valor celda).Es cierto que en este caso la estructura de los diferentes documentos es la misma para todos ellos, pero esto no tiene por qué ser así. Ahora ya podemos insertar estos documentos en una colección. Vamos a utilizar una **nueva colección** distinta a la  que hemos utilizado anteriormente. Así mismo, vamos a realizar el insert múltiple de una sola vez. La nueva colección la vamos a denominar "reviews".db.[nombre_coleccion].insert_many(lista_diccionarios)<jupyter_code># Insertamos todos los reviews de los restaurantes a través de un único comando
db.coleccion_prueba.insert_many(lRestaurantes)<jupyter_output><empty_output><jupyter_text>**Pregunta:** ¿Cómo haríamos con insert_one para insertar todos los valores?  Contar y consultar sin filtrosPara poder saber cuántos documentos tiene la colección de reviews, podemos utilizar el método "count_documents" dentro de la colección correspondiente. Importante incluir siempre el filtro como parámetro de "count_documents", aunque no haya ninguno. Los filtros (como veremos más adelante), también siguen el formato JSON. Cuando queremos ningún filtro, debemos indicar simplemente {} (json vacío).Y si queremos ver todos los documentos que existen en la colección, simplemente tenemos que hacer una búsqueda (find) con filtro vacío. Al igual que habíamos hecho con las bases de datos, debemos convertirlo en lista para poder visualizar el contenido de la consulta.db.nombre_coleccion.count_documents({})<jupyter_code># Consultamos el número de documentos incluidos en la colección de coleccionRestaurantes.
# CONTAR con count_documents -> necesita un documento vacio (sin filtros)
db.coleccion_prueba.count_documents({})
# Mostramos todos los documentos contenidos en la colección Reviews db.nombre_coleccion.find({}) como lista
# MOSTRARLO (los 403 documentos)
list(db.coleccion_prueba.find())<jupyter_output><empty_output><jupyter_text>Contar y consultar con filtrosSi queremos aplicar filtros, la forma de trabajar es sencilla, aunque naturalmente puede complicarse todo lo que queramos. En este caso nos vamos a quedar en un nivel básico.Supongamos que queremos limitar el conteo y consulta anteriores a aquellos reviews de restaurantes que se llaman "KFC". Para hacerlo, debemos definir el filtro como un JSON (diccionario) en el que vamos a ir indicando la clave sobre la que queremos filtrar y el valor de filtrado correspondiente.filtro = {"clave": "valor"} --> sería un equivalente a WHERE clave = "valor" en SQL.<jupyter_code># contamos todos los restaurantes que se tienen por nombre KFC en nuestra colección de reviews db.nombre_coleccion.count_documents(filtro)

# BUSQUEDA con FILTRO
# le digo el campo sobre que filtrar y luego el valor (clave <-> valor)
dFiltro = {
    "nombre": "KFC"
}
db.coleccion_prueba.find(dFiltro)
# Mostramos todos los documentos de los restaurantes que se llaman KFC db.nombre_coleccion.find(filtro) como lista
# tmb hemos añadido un OR
dFiltro = {

    "$or": [{"nombre": "KFC", "rating": 3}]

}
list(db.coleccion_prueba.find(dFiltro))

# Si hacemos un len de la lista, deberíamos ver que resulta el mismo número que el count_documents anterior.
len(    list(db.coleccion_prueba.find(dFiltro)) )<jupyter_output><empty_output><jupyter_text>Agrupar y agregar informaciónhttps://docs.mongodb.com/manual/aggregation/https://docs.mongodb.com/manual/reference/method/db.collection.aggregate/Para poder agregar información de documentos de MongoDB, podemos utilizar el método Aggregate. Este método es el equivalente de GROUP BY con un COUNT o SUM, o función de agregación equivalente. Al igual que hemos hecho con el find o el count_documents, este método aplica sobre la colección en cuestión:db.[nombre_coleccion].aggregate(json_condiciones)Este método es el más complicado a nivel de sintaxis, ya que no es muy "user friendly" :-) A este método, se le pasan tres tipos de "parámetros" (lista de diccionarios):1. (dolar)match: es el equivalmente al where de SQL.2. (dolar)group: los campos sobre los que vamos a agrupar (_id será la clave del campo que vamos a agrupar).3. (dolar)sort: los campos por los que vamos a ordenarLo que vamos a hacer es mostrar cuántos restaurantes hay para cada nivel de rating, desde el 1 hasta el 5, utilizando el método **aggregate**.<jupyter_code># Primero, definimos los parámetros:
# pongo en una list todos los parametros que luego le paso al metodo aggregate
lParams = [
    {
        # Agrupación
        "$group": {
            "_id": "$rating", # $_id código interno de MongoDB para indicar en el valor, el nombre del campo sobre el que vamos a agrupar.
            # Como puede verse, se incluye un $ delante del nombre del campo que actúa como Id
            "num_restaurantes": {"$sum": 1} # num_restaurantes es el nombre del campo agregado resultante (como un alias), que en este caso es un COUNT, o lo que es lo mismo un SUM de 1 para cada registro
        }
    }, 
    {
        # Ordenación
        "$sort": {
            "_id": 1 # estamos ordenando de forma ascendente (1) el campo $_id que es el "rating"
        }
    }
]
# Ahora listamos los resultados 
lresultado = list(db.coleccion_prueba.aggregate(lParams))
display(lresultado)
total = 0 
for elemento in lresultado:
    total += elemento["num_restaurantes"]
display(total)

# el resultado abajo es: el rating que tiene 1 -> 77
#                        el rating que tiene 2 -> 84

# el MONGODB es muy flexible, si encuentra: va por delante , si no encuentra: sigue tmb<jupyter_output><empty_output><jupyter_text>Probemos ahora qué pasaría si quisiéramos filtrar por restaurantes Burger King:<jupyter_code># Primero, definimos los parámetros filtrando por Burger King:
lParams = [
    {
        # Where
        "$match": {
            "nombre": "Burger King"
        }
    }, 
    {
        # Agrupación
        "$group": {
            "_id": "$rating", # $_id código interno de MongoDB para indicar en el valor, el nombre del campo sobre el que vamos a agrupar.
            # Como puede verse, se incluye un $ delante del nombre del campo que actúa como Id
            "num_restaurantes": {"$sum": 1} # num_restaurantes es el nombre del campo agregado resultante (como un alias), que en este caso es un COUNT, o lo que es lo mismo un SUM de 1 para cada registro
        }
    }, 
    {
        # Ordenación
        "$sort": {
            "_id": 1 # estamos ordenando de forma ascendente (1) el campo $_id que es el "rating"
        }
    }
]
# Ahora listamos los resultados 
resultado = list(db.coleccion_prueba.aggregate(lParams))
display(resultado)
total = 0 
for elemento in resultado:
    total += elemento["num_restaurantes"]
display(total)<jupyter_output><empty_output><jupyter_text>Actualizar y borrar documentosAl igual que podemos hacer UPDATE en SQL, aquí podemos hacer update_one y update_many (el primero, actualiza sólo el primer valor que encuentra, mientras que el segundo actualiza todos).db.[nombre_coleccion].update_many(dict_campo_condicion, dict_set)<jupyter_code># Actualicemos todos los restaurantes que se llaman "Burger King" y llamémosles "Burger Queen"
# Primero definimos los parámetros dCampo y dSet
dCondition = {"nombre": "Burger King"}
dSet = {"$set": {"nombre": "Burger Queen"}}
# Y después ejecutamos
db.coleccion_prueba.update_many(dCondition, dSet)
# Vamos a buscar el antiguo Burger King.
db.coleccion_prueba.count_documents({"nombre": "Burger King"})
# Vamos a buscar el antiguo Burger Queen.
len(list(db.coleccion_prueba.find({"nombre": "Burger Queen"})))<jupyter_output><empty_output><jupyter_text>Y si ahora queremos **borrar**:db.[nombre_coleccion].delete_many(dict_campo_condicion)<jupyter_code># Borremos todos los registros que sean "Burger Queen"
db.coleccion_prueba.delete_many({"rating": 8000})
# Comprobemos que, efectivamente, se han borrado
list(db.coleccionRestaurantes.find({"rating": 8000}))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07049491676907609, 'fraction_numerical': 0.010948497374595018, 'mean_word_length': 4.9183471074380165, 'pattern_counts': {'":': 34, '<': 65, '<?xml version=': 0, '>': 79, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '16', 'dir': 'jupyter-structured-clean-dedup', 'id': '526859', 'license': 'MIT', 'n_tokens_mistral': 5996, 'n_tokens_neox': 5743, 'n_words': 2258, 'path': 'Clase3_MongoDB_MIO.ipynb', 'repo_name': 'alfredo-bb/Nuclio-proyects', 'size': '17902'}