Utilice N1QL cuando se encuentre en un aprieto JSON. - Confucio
Para el modelo de datos JSON, el consejo es pensar en las colecciones como tablas, el documento JSON como filas desnormalizadas y los nombres de campo como columnas - a grandes rasgos. Todo esto es válido en bases de datos como Couchbase y MongoDB cuando se siguen estrictamente las recomendaciones. Hay muchas razones por las que los usuarios no se limitan a seguir este modelo de par clave-valor todo el tiempo. He aquí las principales razones.
-
- JSON es demasiado verboso.
- Desea convertir una estructura de datos map/hashmap en la que las claves son dinámicas.
- Datos de series temporales cuando los nombres de los campos suelen ser marcas de tiempo codificadas.
- Codificación basada en diccionarios
- Los formatos y normas existentes no permiten el rediseño.
Si su base de datos y su lenguaje de consulta el lenguaje de consulta no se ocupan de la situación, tendrá que pasar por un elaborado rediseño. Además de simplemente acceder a la información, cómo hacer que las consultas sobre JSON sean eficientes cuando ¿ni siquiera sabes el nombre del campo que tienes que indexar? Afortunadamente, Couchbase N1QL tiene una variedad de características de consulta e índice para tratar también con metadatos flexibles.
Consideremos estos casos de uso.
Caso práctico 1: Transformación del valor.
He aquí un ejemplo de documento JSON.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ "cnombre": "Jane Smith", "dob" : "1990-01-30", "teléfonos" : [ "+1 510-523-3529", "+1 650-392-4923" ], "facturación": [ { "tipo": "visado", "cardnum": "5827-2842-2847-3909", "caducidad": "2019-03" }, { "tipo": "maestro", "cardnum": "6274-2542-5847-3949", "caducidad": "2018-12" } ] } |
El modelo de datos JSON se describe simplemente como un conjunto de pares clave-valor. Cada clave es una cadena, única en su nivel jerárquico, y los valores pueden ser escalares, objetos o matrices. Una definición rigurosa puede leerse aquí. JSON también es autodescriptivo y eso lo hace flexible para un modelo de documento de base de datos. No todos los clientes tienen que tener un número fijo de números de teléfono o coches o cualquier otro tipo de atributos.
La misma información anterior se puede reorganizar como el JSON a continuación sin pérdida de información, pero algunos sche implícita
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "Jane Smith": "1990-01-30", "home": "+1 510-523-3529", "oficina": "+1 650-392-4923", "Facturación": [ { "visado": "5827-2842-2847-3909", "caducidad": "2019-03" }, { "maestro": "6274-2542-5847-3949", "caducidad": "2018-12" } ] } |
Esto está muy bien si simplemente estás poniendo y fijando el documento. No importa lo que la estructura de JSON. Simplemente schlep es de ida y vuelta.
Veamos ahora cómo afecta esto a las consultas.
Q1: SELECCIONE * DESDE clientes DONDE cxnombre = "Jane Smith";
Con el nuevo modelo JSON, no existe un nombre de campo llamado cxnombre aquí.
1 2 3 4 |
Q2: SELECCIONE p DESDE gente p DONDE CUALQUIER o EN nombres_objeto(p) SATISFACE o = "Jane Smith" FIN |
Cuál es la magia de pares_objetos() ¿función? Transforma los pares JSON {"key": "value"} en una matriz de pares nombre-valor. He aquí un ejemplo.
1 2 3 4 5 6 7 8 9 |
SELECCIONE OBJECT_NAMES({"Jane Smith": "1990-01-30", "home": "+1 510-523-3529"}) "$1": [ "Jane Smith", "home" ] } |
La función OBJECT_NAMES() extrae la clave (en este caso "Jane Smith") y la devuelve como valor, que puede ser indexado. Dado que la función no devuelve un único valor, sino una matriz de "nombres de clave" como valores, es necesario crear un índice de matriz. Las consultas Q1 y Q2 hacen el mismo trabajo para el modelo de datos respectivo. Pero necesitamos que cada una de esas consultas se ejecute en milisegundos.
Para Q1, simplemente creamos el índice sobre cxname.
CREAR ÍNDICE ix_cxname EN clientes(cxnombre)
Para la Q2,
CREAR ÍNDICE ix_people EN personas(DISTINTO OBJECT_NAMES(self))
Con este índice, para Q2, obtendrá un plan que utiliza el índice.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
{ "#operator": "DistinctScan", "escanear": { "#operator": "IndexScan3", "como": "p", "cardinalidad": 1, "coste": 0.273, "índice": "ix_people", "index_id": "4a2df8dd85543aa4", "proyección_índice": { "clave_primaria": verdadero }, "espacio clave": "gente", "espacio de nombres": "por defecto", "vanos": [ { "exacto": verdadero, "rango": [ { "alto": "Jane Smith", "inclusión": 3, "bajo": "Jane Smith" } ] } ], |
Caso de uso 2: Nombres de claves dinámicas.
Este caso de uso se ha extraído del Couchbase puesto del foro.
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "id": "05a9b954-bdee-4d7f-9715-8e9e08f8cb75", "tipo": "artículo", "traducciones": { "es": "Hola", "de": "Hallo", "fr": "Bonjour", "es": "Hola" } } |
Pregunta: ¿Cuál sería la mejor manera de indexar los valores dentro de traducciones dinámicamente? Es decir, un índice genérico que indexe todas las claves dentro del traducciones objeto.
Si la necesidad es simplemente consultar documentos en inglés todo el tiempo, para consultar todos los documentos que tienen translations.es = "Hola".
Si siempre está buscando traducciones al inglés, puede crear simplemente el índice en transactions.es.
1 2 3 4 |
CREAR ÍNDICE ix_tren EN info(traducciones.es); SELECCIONE * DESDE información DONDE traducción.es = "Hola"; |
Si las claves son dinámicas, no sabes qué idioma concreto va a haber en los datos y cuáles pueden ser objeto de consulta, tienes que hacer que ambas sean dinámicas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* Consulta */ SELECCIONE * DESDE información DONDE CUALQUIER v EN OBJECT_PAIRS(traducciones) SATISFACE [v.name,v.val] = ["es", "Hola"] FIN /* Índice */ CREAR ÍNDICE ix_infoname EN información ( DISTINTO ARRAY [v.name, v.val ] PARA v EN OBJECT_PAIRS(traducciones) FIN ) |
Aquí está la explicación para verificar que el índice es efectivamente recogido y los predicados son empujados hacia abajo a la exploración del índice.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
{ "#operator": "DistinctScan", "escanear": { "#operator": "IndexScan3", "cardinalidad": 0.5, "coste": 0.1665, "índice": "ix_infoname", "index_id": "bebbfd22a022fb75", "proyección_índice": { "clave_primaria": verdadero }, "espacio clave": "info", "espacio de nombres": "por defecto", "vanos": [ { "exacto": verdadero, "rango": [ { "alto": "[\"en\", \"Hello\"]", "inclusión": 3, "bajo": "[\"en\", \"Hello\"]" } ] } ], "usando": "gsi" } }, |
No se preocupe si la definición del índice le parece un poco más complicada de lo normal. El Index Advisor lo tiene todo cubierto.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
CONSEJO SELECCIONE * DESDE información DONDE CUALQUIER v EN OBJECT_PAIRS(traducciones) SATISFACE [v.name,v.val] = ["es", "Hola"] FIN { "declaración_índice": "CREATE INDEX adv_DISTINCT_object_pairs_translations_name_val ON `info`(DISTINCT ARRAY [`v`.`name`, `v`.`val`] FOR v in object_pairs((`translations`)) END)", "keyspace_alias": "info", "recomendando_regla": "Las claves de índice siguen el orden de los tipos de predicado: 2. igualdad/nulo/falta". } |
Incluso puedes añadir expresiones encima de cada expresión que estés evaluando.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
CONSEJO SELECCIONE * DESDE información DONDE CUALQUIER v EN OBJECT_PAIRS(traducciones) SATISFACE [BAJO(v.name),BAJO(v.val)] = ["es", "Hola"] FIN { "declaración_índice": "CREATE INDEX adv_DISTINCT_object_pairs_translations_lower_name_lower_val ON `info`(DISTINCT ARRAY [lower((`v`.`name`)), lower((`v`.`val`))] FOR v in object_pairs((`translations`)) END)", "keyspace_alias": "info", "recomendando_regla": "Las claves de índice siguen el orden de los tipos de predicado: 2. igualdad/nulo/falta". } |
Más funciones de objeto
N1QL dispone de objeto y funciones de datos anidados para ayudar con modelos de datos complejos. Echa un vistazo al conjunto completo de funciones de objeto y a la sección funciones simbólicas.
Referencias:
- Funciones de objeto Couchbae N1QL Documentación
- Couchbase Indexación de matrices
- Couchbase índice blog