Introducción
El soporte de N1QL para ANSI JOIN se introdujo por primera vez en la versión 5.5. Actualmente existe una restricción en ANSI JOIN (versiones 5.5 y 6.0) en el sentido de que el lado derecho de un ANSI JOIN debe ser un espacio de claves. Esta restricción se suprimirá en la versión 6.5.
El soporte N1QL para la sentencia MERGE utiliza actualmente la cláusula ON KEYS, similar a lookup join. La versión 6.5 soportará la sintaxis MERGE ANSI.
Mejoras de ANSI JOIN
En la versión 6.5 de Couchbase Server, N1QL añade soporte para expresión y subconsulta como lado derecho de un ANSI JOIN. Anteriormente en las versiones 5.5 y 6.0, el lado derecho de un ANSI JOIN debía ser un espacio de claves. Se devuelve un error si el lado derecho de un ANSI JOIN no es un espacio de claves.
La restricción para el espacio clave sólo existe en las versiones 5.5 y 6.0, y sólo se aplica al lado derecho de un ANSI JOIN. El lado izquierdo de un ANSI JOIN puede ser un espacio clave, una expresión, una subconsulta u otra operación join/nest/unnest, siempre que ANSI JOIN no se mezcle con una operación join/nest que no sea ANSI.
Expresión como lado derecho de un ANSI JOIN
Ahora se puede utilizar una expresión en el lado derecho de ANSI JOIN. Por ejemplo:
|
1 2 3 4 5 6 |
SELECCIONE DISTINTO ruta.destinoaeropuerto DESDE `viaje-muestra` aeropuerto ÚNASE A [ {"destinoaeropuerto": "KEF", "sourceairport": "OFS", "tipo": "ruta"}, {"destinoaeropuerto": "KEF", "sourceairport": "LHR", "tipo": "ruta"} ] AS ruta EN aeropuerto.faa = ruta.fuenteaeropuerto Y ruta.tipo = "ruta" DONDE aeropuerto.tipo = "aeropuerto" Y aeropuerto.ciudad = "San Francisco"; |
En este ejemplo, se especifica una matriz explícita de objetos como una expresión en el lado derecho de un ANSI JOIN. Tenga en cuenta que cuando se utiliza una expresión en el lado derecho de un ANSI JOIN, la expresión debe tener un alias ("ruta" en el ejemplo anterior, aunque la palabra clave AS es opcional).
Cuando se utiliza una expresión en el lado derecho de un ANSI JOIN, se utiliza un operador ExpressionScan para iterar a través del conjunto de resultados de la expresión en el plan explain:
|
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
"plan": { "#operator": "Secuencia", "~niños": [ { "#operator": "IndexScan3", "como": "aeropuerto", "índice": "ix_ciudad_aeropuerto", "index_id": "eee67e7e615a1b49", "proyección_índice": { "clave_primaria": verdadero }, "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto", "vanos": [ { "exacto": verdadero, "rango": [ { "alto": "San Francisco", "inclusión": 3, "bajo": "San Francisco" } ] } ], "usando": "gsi" }, { "#operator": "Fetch", "como": "aeropuerto", "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto" }, { "#operator": "HashJoin", "build_aliases": [ "ruta" ], "build_exprs": [ "(`ruta`.`aeropuerto_de_origen`)" ], "on_clause": "(((`airport`.`faa`) = (`route`.`sourceairport`)) and ((`route`.`type`) = \"route\"))", "probe_exprs": [ "(`airport`.`faa`)" ], "~niño": { "#operator": "Secuencia", "~niños": [ { "#operator": "ExpressionScan", "alias": "ruta", "expr": "[{\"destinationairport\": \"KEF", "sourceairport": \"SFO", "type": \"ruta", "aeropuerto de destino": \"KEF", "sourceairport": \"LHR", "tipo": \"ruta"]", "no correlacionado": verdadero } ] } }, { "#operator": "Paralelo", "~niño": { "#operator": "Secuencia", "~niños": [ { "#operator": "Filtro", "condición": "(((`airport`.`type`) = \ "airport\") and ((`airport`.`city`) = \ "San Francisco\"))" }, { "#operator": "ProyectoInicial", "distinto": verdadero, "result_terms": [ { "expr": "(`ruta`.`destinoaeropuerto`)" } ] }, { "#operator": "Distinto" } ] } }, { "#operator": "Distinto" } ] }, |
Subconsulta como lado derecho de un ANSI JOIN
También se puede utilizar una subconsulta en el lado derecho de ANSI JOIN. Por ejemplo:
|
1 2 3 4 5 6 |
SELECCIONE DISTINTO ruta.destinoaeropuerto DESDE `viaje-muestra` aeropuerto ÚNASE A ( SELECCIONE destinoaeropuerto, fuenteaeropuerto DESDE `viaje-muestra` DONDE tipo = "ruta" ) AS ruta EN aeropuerto.faa = ruta.fuenteaeropuerto DONDE aeropuerto.tipo = "aeropuerto" Y aeropuerto.ciudad = "San Francisco"; |
En este ejemplo, se utiliza una subconsulta en el lado derecho de un ANSI JOIN. Al igual que una expresión, cuando se utiliza una subconsulta en el lado derecho de un ANSI JOIN, la subconsulta también debe tener un alias ("ruta" en el ejemplo anterior, aunque la palabra clave AS es opcional).
Cuando se utiliza una subconsulta en el lado derecho de un ANSI JOIN, la explicación muestra el plan de la propia subconsulta:
|
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
"plan": { "#operator": "Secuencia", "~niños": [ { "#operator": "IndexScan3", "como": "aeropuerto", "índice": "ix_ciudad_aeropuerto", "index_id": "eee67e7e615a1b49", "proyección_índice": { "clave_primaria": verdadero }, "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto", "vanos": [ { "exacto": verdadero, "rango": [ { "alto": "San Francisco", "inclusión": 3, "bajo": "San Francisco" } ] } ], "usando": "gsi" }, { "#operator": "Fetch", "como": "aeropuerto", "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto" }, { "#operator": "HashJoin", "build_aliases": [ "ruta" ], "build_exprs": [ "(`ruta`.`aeropuerto_de_origen`)" ], "on_clause": "((`airport`.`faa`) = (`route`.`sourceairport`))", "probe_exprs": [ "(`airport`.`faa`)" ], "~niño": { "#operator": "Secuencia", "~niños": [ { "#operator": "Secuencia", "~niños": [ { "#operator": "IndexScan3", "índice": "ix_type", "index_id": "d925e49b3a11ae3d", "proyección_índice": { "clave_primaria": verdadero }, "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto", "vanos": [ { "exacto": verdadero, "rango": [ { "alto": "\"route\"", "inclusión": 3, "bajo": "\"route\"" } ] } ], "usando": "gsi" }, { "#operator": "Fetch", "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto" }, { "#operator": "Paralelo", "~niño": { "#operator": "Secuencia", "~niños": [ { "#operator": "Filtro", "condición": "((`viaje-muestra`.`tipo`) = \ "ruta\")" }, { "#operator": "ProyectoInicial", "result_terms": [ { "expr": "(`viaje-muestra`.`destino-aeropuerto`)" }, { "expr": "(`viaje-muestra`.`aeropuerto-de-fuente`)" } ] } ] } } ] }, { "#operator": "Alias", "como": "ruta" } ] } }, { "#operator": "Paralelo", "~niño": { "#operator": "Secuencia", "~niños": [ { "#operator": "Filtro", "condición": "(((`airport`.`type`) = \ "airport\") and ((`airport`.`city`) = \ "San Francisco\"))" }, { "#operator": "ProyectoInicial", "distinto": verdadero, "result_terms": [ { "expr": "(`ruta`.`destinoaeropuerto`)" } ] }, { "#operator": "Distinto" } ] } }, { "#operator": "Distinto" } ] }, |
Cuando se utiliza una subconsulta como lado derecho de un ANSI JOIN, no puede ser una subconsulta correlacionada. Se devolverá un error si se utiliza una subconsulta correlacionada en el lado derecho de un ANSI JOIN.
Hash join utilizado cuando una expresión o una subconsulta está en el lado derecho de un ANSI JOIN
Cuando se utiliza un espacio de claves en el lado derecho de un ANSI JOIN, la unión en bucle anidado es el método de unión por defecto, y se considera la unión hash cuando se especifica la sugerencia USE HASH en el espacio de claves del lado derecho. Además, se requiere un índice secundario apropiado en el espacio clave cuando se utiliza la unión de bucle anidado. Por el contrario, cuando se utiliza una expresión o una subconsulta en el lado derecho de un ANSI JOIN, como no hay índice en una expresión o una subconsulta, es más eficiente utilizar una unión hash para realizar la unión. En este caso, una unión de bucle anidado es una unión cartesiana, ya que el conjunto de resultados de la expresión o subconsulta debe explorarse repetidamente para cada documento externo. Como resultado, cuando una expresión o subconsulta se encuentra en el lado derecho de un ANSI JOIN, la unión hash es el método de unión por defecto, incluso sin especificar la sugerencia USE HASH. Si no se puede utilizar la unión hash, por ejemplo, si no hay un predicado de unión de igualdad, o cuando se utiliza la sugerencia USE NL, o cuando se utiliza la edición community (la unión hash sólo está disponible en la edición enterprise), entonces se considera la unión de bucle anidado. Si se observan los archivos de explicación anteriores para los casos de expresión y subconsulta como lado derecho de un ANSI JOIN, se puede encontrar un operador HashJoin utilizado en ambos archivos de explicación.
Cuando se utiliza un espacio clave en el lado derecho de un ANSI JOIN, se puede especificar una sugerencia USE INDEX o USE KEYS en el espacio clave. Cuando se utiliza una expresión o una subconsulta en el lado derecho de un ANSI JOIN, no se puede especificar una sugerencia USE INDEX o USE KEYS en la expresión o subconsulta, por razones obvias. La única pista disponible en este caso es una pista join (USE HASH o USE NL).
Compatibilidad con ANSI NEST
La misma mejora se aplica también a ANSI NEST, es decir, ahora puede utilizar una expresión o una subconsulta como lado derecho de un ANSI NEST.
FUSIÓN ANSI
Una sentencia MERGE permite fusionar documentos del "origen" en el "destino", es decir, cuando se encuentra una coincidencia, se puede actualizar el documento destino; cuando no se encuentra ninguna coincidencia, se puede insertar un documento en el destino.
El concepto de "coincidencia" viene determinado por la condición de coincidencia. En versiones anteriores de Couchbase N1QL sólo soporta una "coincidencia" basada en la clave del documento, es decir, la fuente debe producir una clave de documento para el destino, para determinar si se encuentra una coincidencia (si el documento con esa clave de documento ya existe en el destino). Esto se consigue mediante la cláusula ON KEYS de la sintaxis de la sentencia MERGE.
La sentencia MERGE con la cláusula ON KEYS se denomina look-up merge. Es similar a la sintaxis join anterior a la compatibilidad con ANSI JOIN, es decir, look-up join, que también requiere la unión en la clave del documento y utiliza la cláusula ON KEYS. Por lo tanto, una extensión natural de la sentencia MERGE es utilizar la sintaxis ANSI MERGE, es decir, utilizar la cláusula ON para especificar la condición de fusión, de forma que se pueda utilizar una expresión arbitraria para determinar si existe o no una coincidencia. ANSI MERGE está soportado en N1QL en la versión 6.5 del servidor Couchbase.
A continuación se muestra un ejemplo de sentencia MERGE ANSI:
|
1 2 3 4 5 6 7 |
FUSIONAR EN `viaje-muestra` AS ruta USO DE `viaje-muestra` AS aeropuerto EN ruta.fuenteaeropuerto = aeropuerto.faa Y aeropuerto.tipo = "aeropuerto" Y ruta.tipo = "ruta" CUANDO MATCHED ENTONCES ACTUALIZACIÓN SET ruta.equipo_antiguo = ruta.equipo, ruta.equipo = "797", ruta.actualizado = verdadero DONDE ruta.aerolínea = "BA" Y aeropuerto.país = "Francia Y CONTIENE(ruta.equipo, "319"); |
En este ejemplo, la condición de fusión se especifica mediante una cláusula ON. Esto es muy similar a la cláusula ON de un ANSI JOIN. De hecho, internamente la sentencia ANSI MERGE utiliza ANSI JOIN con la misma cláusula ON para determinar si existe una coincidencia para cualquier documento fuente.
La explicación de la declaración de fusión anterior:
|
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
"plan": { "#operator": "Secuencia", "~niños": [ { "#operator": "IndexScan3", "como": "aeropuerto", "índice": "ix_type", "index_id": "d925e49b3a11ae3d", "proyección_índice": { "clave_primaria": verdadero }, "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto", "vanos": [ { "exacto": verdadero, "rango": [ { "alto": "aeropuerto", "inclusión": 3, "bajo": "aeropuerto" } ] } ], "usando": "gsi" }, { "#operator": "Fetch", "como": "aeropuerto", "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto" }, { "#operator": "Paralelo", "~niño": { "#operator": "Secuencia", "~niños": [ { "#operator": "NestedLoopJoin", "alias": "ruta", "on_clause": "((((`route`.`sourceairport`) = (`airport`.`faa`)) and ((`airport`.`type`) = \"airport\")) and ((`route`.`type`) = \"route\"))", "~niño": { "#operator": "Secuencia", "~niños": [ { "#operator": "IndexScan3", "como": "ruta", "índice": "ix_type", "index_id": "d925e49b3a11ae3d", "proyección_índice": { "clave_primaria": verdadero }, "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto", "bucle_anidado": verdadero, "vanos": [ { "exacto": verdadero, "rango": [ { "alto": "\"route\"", "inclusión": 3, "bajo": "\"route\"" } ] } ], "usando": "gsi" }, { "#operator": "Fetch", "como": "ruta", "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto", "bucle_anidado": verdadero } ] } }, { "#operator": "Fusionar", "como": "ruta", "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto", "actualizar": { "#operator": "Secuencia", "~niños": [ { "#operator": "Filtro", "condición": "((((`route`.`airline`) = \"BA\") and ((`airport`.`country`) = \"France\")) and contains((`route`.`equipment`), \"319\"))" }, { "#operator": "Clon" }, { "#operator": "Set", "set_terms": [ { "camino": "(`ruta`.`equipo_antiguo`)", "valor": "(`ruta`.`equipo`)" }, { "camino": "(`ruta`.`equipo`)", "valor": "\"797\"" }, { "camino": "(`ruta`.`actualizada`)", "valor": "true" } ] }, { "#operator": "EnviarActualización", "alias": "ruta", "espacio clave": "viaje-muestra", "espacio de nombres": "por defecto" } ] } } ] } }, { "#operator": "Descartar" } ] }, |
En la explicación puede ver un operador NestedLoopJoin que se utiliza para la operación ANSI JOIN (transformada internamente) entre el origen y el destino.
Se puede especificar una sugerencia de unión en la fuente:
|
1 2 3 4 5 6 7 |
FUSIONAR EN `viaje-muestra` AS ruta USO DE `viaje-muestra` AS aeropuerto UTILICE HASH(construya) EN ruta.fuenteaeropuerto = aeropuerto.faa Y aeropuerto.tipo = "aeropuerto" Y ruta.tipo = "ruta" CUANDO MATCHED ENTONCES ACTUALIZACIÓN SET ruta.equipo_antiguo = ruta.equipo, ruta.equipo = "797", ruta.actualizado = verdadero DONDE ruta.aerolínea = "BA" Y aeropuerto.país = "Francia Y CONTIENE(ruta.equipo, "319"); |
En este ejemplo se especifica la sugerencia USE HASH en el aeropuerto.
Tratamiento de coincidencias múltiples en ANSI MERGE
En la fusión por búsqueda, como la condición de fusión es la clave del documento, sólo hay una coincidencia potencial con el documento de destino para cada documento de origen. En la fusión ANSI, como la condición de fusión es ahora una expresión arbitraria, puede haber múltiples coincidencias de documentos de destino para cada documento de origen. Para cada acción de fusión especificada, la acción de fusión sólo puede ejecutarse como máximo una vez para cada documento de origen. En caso de coincidencias múltiples, si se intenta una acción de fusión por segunda vez para el mismo documento fuente, se devolverá un error (código de error 5320 o 5330). Si se produce un error de este tipo, es necesario refinar la condición de fusión (cláusula ON) para evitar coincidencias múltiples para el mismo documento fuente.
Acción INSERT para ANSI MERGE
En la fusión por consulta, si se especifica una acción INSERT, sólo se proporciona el documento que se está insertando. Dado que la fusión por búsqueda requiere una coincidencia en la clave del documento, y la acción INSERT sólo se activa cuando no se encuentra ninguna coincidencia (es decir, no existe ningún documento con esa clave de documento en el destino), el nuevo documento insertado simplemente utilizará la misma clave de documento. En el caso de ANSI MERGE, esto ya no es cierto, puesto que ya no es necesario que una clave de documento sea la condición de coincidencia. En consecuencia, cuando se especifica una acción INSERT, también debe especificarse una clave de documento como parte de la acción INSERT. Por ejemplo:
|
1 2 3 4 5 |
FUSIONAR EN `viaje-muestra` AS ruta USO DE `viaje-muestra` AS aeropuerto EN ruta.fuenteaeropuerto = aeropuerto.faa Y aeropuerto.tipo = "aeropuerto" Y ruta.tipo = "ruta" CUANDO NO MATCHED ENTONCES INSERTAR (CLAVE UUID(), VALOR {"sourceairport": aeropuerto.faa, "targetairport": "OFS", "tipo": "ruta"}) DONDE aeropuerto.país = "Francia; |
En comparación, ésta es la sintaxis de la acción INSERT para look-up merge:
|
1 |
CUANDO NO MATCHED ENTONCES INSERTAR <expr> |
donde es el documento que se va a insertar.
Esta es la sintaxis de la acción INSERT para ANSI MERGE:
|
1 2 |
CUANDO NO MATCHED ENTONCES INSERTAR (<clave_expr>, <valor_expr>) CUANDO NO MATCHED ENTONCES INSERTAR (CLAVE <clave_expr>, VALOR <valor_expr>) |
donde especifica la clave del documento recién insertado y especifica el nuevo documento. Los dos están separados por una coma.
Se pueden utilizar dos formas, las palabras clave KEY y VALUE son opcionales. También se requiere un nuevo par de paréntesis.
Las demás acciones de fusión (acción UPDATE, acción DELETE) siguen siendo las mismas entre look-up merge y ANSI MERGE.
Resumen
Las mejoras para ANSI JOIN y el soporte para ANSI MERGE en Couchbase 6.5 mejoran el cumplimiento ANSI de N1QL, y hace que N1QL sea más fácil de usar, especialmente para la migración desde una base de datos relacional.