Introdução
O suporte do N1QL para ANSI JOIN foi introduzido pela primeira vez na versão 5.5. Atualmente, há uma restrição no ANSI JOIN (versão 5.5 e 6.0) em que o lado direito de um ANSI JOIN deve ser um espaço-chave. Essa restrição será eliminada na versão 6.5.
O suporte do N1QL para a instrução MERGE atualmente usa a cláusula ON KEYS, semelhante à junção de pesquisa. A sintaxe ANSI MERGE será suportada na versão 6.5.
Aprimoramentos do ANSI JOIN
Na versão 6.5 do Couchbase Server, o N1QL adiciona suporte para expressão e subconsulta como lado direito de um ANSI JOIN. Anteriormente, nas versões 5.5 e 6.0, o lado direito de um ANSI JOIN deve ser um espaço-chave. Um erro será retornado se o lado direito de um ANSI JOIN não for um espaço-chave.
A restrição para o espaço-chave só existe nas versões 5.5 e 6.0, e só se aplica ao lado direito de um ANSI JOIN. O lado esquerdo de um ANSI JOIN pode ser um espaço-chave, uma expressão, uma subconsulta ou outra operação join/nest/unnest, desde que o ANSI JOIN não seja misturado com uma operação join/nest não ANSI.
Expressão como o lado direito de um ANSI JOIN
Uma expressão agora pode ser usada no lado direito do ANSI JOIN. Por exemplo:
|
1 2 3 4 5 6 |
SELECT DISTINCT route.destinationairport FROM `travel-sample` airport JOIN [ {"destinationairport": "KEF", "sourceairport": "SFO", "type": "route"}, {"destinationairport": "KEF", "sourceairport": "LHR", "type": "route"} ] AS route ON airport.faa = route.sourceairport AND route.type = "route" WHERE airport.type = "airport" AND airport.city = "San Francisco"; |
Neste exemplo, uma matriz explícita de objetos é especificada como uma expressão no lado direito de um ANSI JOIN. Observe que quando uma expressão é usada no lado direito de um ANSI JOIN, a expressão deve ter um alias ("route" no exemplo acima, embora a palavra-chave AS seja opcional).
Quando uma expressão é usada no lado direito de um ANSI JOIN, um operador ExpressionScan é usado para iterar pelo conjunto de resultados da expressão no plano de explicação:
|
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": "Sequence", "~children": [ { "#operator": "IndexScan3", "as": "airport", "index": "ix_airport_city", "index_id": "eee67e7e615a1b49", "index_projection": { "primary_key": true }, "keyspace": "travel-sample", "namespace": "default", "spans": [ { "exact": true, "range": [ { "high": "\"San Francisco\"", "inclusion": 3, "low": "\"San Francisco\"" } ] } ], "using": "gsi" }, { "#operator": "Fetch", "as": "airport", "keyspace": "travel-sample", "namespace": "default" }, { "#operator": "HashJoin", "build_aliases": [ "route" ], "build_exprs": [ "(`route`.`sourceairport`)" ], "on_clause": "(((`airport`.`faa`) = (`route`.`sourceairport`)) and ((`route`.`type`) = \"route\"))", "probe_exprs": [ "(`airport`.`faa`)" ], "~child": { "#operator": "Sequence", "~children": [ { "#operator": "ExpressionScan", "alias": "route", "expr": "[{\"destinationairport\": \"KEF\", \"sourceairport\": \"SFO\", \"type\": \"route\"}, {\"destinationairport\": \"KEF\", \"sourceairport\": \"LHR\", \"type\": \"route\"}]", "uncorrelated": true } ] } }, { "#operator": "Parallel", "~child": { "#operator": "Sequence", "~children": [ { "#operator": "Filter", "condition": "(((`airport`.`type`) = \"airport\") and ((`airport`.`city`) = \"San Francisco\"))" }, { "#operator": "InitialProject", "distinct": true, "result_terms": [ { "expr": "(`route`.`destinationairport`)" } ] }, { "#operator": "Distinct" } ] } }, { "#operator": "Distinct" } ] }, |
Subconsulta como lado direito de um ANSI JOIN
Uma subconsulta também pode ser usada no lado direito do ANSI JOIN. Por exemplo:
|
1 2 3 4 5 6 |
SELECT DISTINCT route.destinationairport FROM `travel-sample` airport JOIN ( SELECT destinationairport, sourceairport FROM `travel-sample` WHERE type = "route" ) AS route ON airport.faa = route.sourceairport WHERE airport.type = "airport" AND airport.city = "San Francisco"; |
Neste exemplo, uma subconsulta é usada no lado direito de um ANSI JOIN. Semelhante a uma expressão, quando uma subconsulta é usada no lado direito de um ANSI JOIN, a subconsulta também deve ter um alias ("route" no exemplo acima, embora a palavra-chave AS seja opcional).
Quando uma subconsulta é usada no lado direito de um ANSI JOIN, a explicação mostra o plano da própria 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": "Sequence", "~children": [ { "#operator": "IndexScan3", "as": "airport", "index": "ix_airport_city", "index_id": "eee67e7e615a1b49", "index_projection": { "primary_key": true }, "keyspace": "travel-sample", "namespace": "default", "spans": [ { "exact": true, "range": [ { "high": "\"San Francisco\"", "inclusion": 3, "low": "\"San Francisco\"" } ] } ], "using": "gsi" }, { "#operator": "Fetch", "as": "airport", "keyspace": "travel-sample", "namespace": "default" }, { "#operator": "HashJoin", "build_aliases": [ "route" ], "build_exprs": [ "(`route`.`sourceairport`)" ], "on_clause": "((`airport`.`faa`) = (`route`.`sourceairport`))", "probe_exprs": [ "(`airport`.`faa`)" ], "~child": { "#operator": "Sequence", "~children": [ { "#operator": "Sequence", "~children": [ { "#operator": "IndexScan3", "index": "ix_type", "index_id": "d925e49b3a11ae3d", "index_projection": { "primary_key": true }, "keyspace": "travel-sample", "namespace": "default", "spans": [ { "exact": true, "range": [ { "high": "\"route\"", "inclusion": 3, "low": "\"route\"" } ] } ], "using": "gsi" }, { "#operator": "Fetch", "keyspace": "travel-sample", "namespace": "default" }, { "#operator": "Parallel", "~child": { "#operator": "Sequence", "~children": [ { "#operator": "Filter", "condition": "((`travel-sample`.`type`) = \"route\")" }, { "#operator": "InitialProject", "result_terms": [ { "expr": "(`travel-sample`.`destinationairport`)" }, { "expr": "(`travel-sample`.`sourceairport`)" } ] } ] } } ] }, { "#operator": "Alias", "as": "route" } ] } }, { "#operator": "Parallel", "~child": { "#operator": "Sequence", "~children": [ { "#operator": "Filter", "condition": "(((`airport`.`type`) = \"airport\") and ((`airport`.`city`) = \"San Francisco\"))" }, { "#operator": "InitialProject", "distinct": true, "result_terms": [ { "expr": "(`route`.`destinationairport`)" } ] }, { "#operator": "Distinct" } ] } }, { "#operator": "Distinct" } ] }, |
Quando uma subconsulta é usada como lado direito de um ANSI JOIN, ela não pode ser uma subconsulta correlacionada. Um erro será retornado se uma subconsulta correlacionada for usada no lado direito de um ANSI JOIN.
Junção de hash usada quando uma expressão ou uma subconsulta está no lado direito de um ANSI JOIN
Quando um espaço-chave é usado no lado direito de um ANSI JOIN, a junção de loop aninhado é o método de junção padrão e a junção de hash é considerada quando a dica USE HASH é especificada no espaço-chave do lado direito. Além disso, um índice secundário apropriado no espaço-chave é necessário quando a junção de loop aninhado é usada. Por outro lado, quando uma expressão ou uma subconsulta é usada no lado direito de um ANSI JOIN, como não há índice em uma expressão ou subconsulta, é mais eficiente usar uma junção de hash para realizar a junção. Uma junção de loop aninhado nesse caso é efetivamente uma junção cartesiana, já que o conjunto de resultados da expressão ou subconsulta precisa ser repetidamente verificado para cada documento externo. Como resultado, quando uma expressão ou subconsulta está no lado direito de um ANSI JOIN, a junção de hash é o método de junção padrão, mesmo sem a dica USE HASH especificada. Se a junção de hash não puder ser usada, por exemplo, se não houver predicado de junção de igualdade, se a dica USE NL for usada ou se a edição comunitária estiver sendo usada (a junção de hash só está disponível na edição empresarial), a junção de loop aninhado será considerada. Observando os arquivos de explicação acima para os casos de expressão e subconsulta como lado direito de um ANSI JOIN, você pode encontrar um operador HashJoin usado em ambos os arquivos de explicação.
Quando um espaço de chave é usado no lado direito de um ANSI JOIN, uma dica USE INDEX ou USE KEYS pode ser especificada no espaço de chave. Quando uma expressão ou uma subconsulta é usada no lado direito de um ANSI JOIN, não é possível especificar uma dica USE INDEX ou USE KEYS na expressão ou subconsulta, por motivos óbvios. A única dica disponível nesse caso é uma dica de união (USE HASH ou USE NL).
Suporte para ANSI NEST
O mesmo aprimoramento também se aplica ao ANSI NEST, ou seja, agora você pode usar uma expressão ou uma subconsulta como o lado direito de um ANSI NEST.
ANSI MERGE
Uma instrução MERGE permite que os documentos da "fonte" sejam mesclados no "destino", ou seja, quando uma correspondência é encontrada, o documento de destino pode ser atualizado; quando uma correspondência não é encontrada, um documento pode ser inserido no destino.
O conceito de "correspondência" é determinado pela condição de correspondência. Nas versões anteriores do Couchbase, o N1QL suporta apenas uma "correspondência" baseada na chave do documento, ou seja, a origem deve produzir uma chave de documento para o destino, para determinar se uma correspondência é encontrada (se o documento com essa chave de documento já existe no destino). Isso é obtido pela cláusula ON KEYS na sintaxe da instrução MERGE.
O comando MERGE com a cláusula ON KEYS é chamado de look-up merge. Isso é semelhante à sintaxe de união antes do suporte a ANSI JOIN, ou seja, look-up join, que também requer união na chave do documento e usa a cláusula ON KEYS. Portanto, uma extensão natural da instrução MERGE é usar a sintaxe ANSI MERGE, ou seja, usar a cláusula ON para especificar a condição de mesclagem, de modo que uma expressão arbitrária possa ser usada para determinar se existe ou não uma correspondência. O ANSI MERGE é compatível com o N1QL na versão 6.5 do servidor Couchbase.
Aqui está um exemplo de uma instrução ANSI MERGE:
|
1 2 3 4 5 6 7 |
MERGE INTO `travel-sample` AS route USING `travel-sample` AS airport ON route.sourceairport = airport.faa AND airport.type = "airport" AND route.type = "route" WHEN MATCHED THEN UPDATE SET route.old_equipment = route.equipment, route.equipment = "797", route.updated = true WHERE route.airline = "BA" AND airport.country = "France" AND CONTAINS(route.equipment, "319"); |
Neste exemplo, a condição de mesclagem é especificada usando uma cláusula ON. Isso é muito semelhante à cláusula ON de um ANSI JOIN. De fato, internamente, a instrução ANSI MERGE usa ANSI JOIN com a mesma cláusula ON para determinar se existe uma correspondência para qualquer documento de origem.
A explicação para a declaração de mesclagem acima:
|
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": "Sequence", "~children": [ { "#operator": "IndexScan3", "as": "airport", "index": "ix_type", "index_id": "d925e49b3a11ae3d", "index_projection": { "primary_key": true }, "keyspace": "travel-sample", "namespace": "default", "spans": [ { "exact": true, "range": [ { "high": "\"airport\"", "inclusion": 3, "low": "\"airport\"" } ] } ], "using": "gsi" }, { "#operator": "Fetch", "as": "airport", "keyspace": "travel-sample", "namespace": "default" }, { "#operator": "Parallel", "~child": { "#operator": "Sequence", "~children": [ { "#operator": "NestedLoopJoin", "alias": "route", "on_clause": "((((`route`.`sourceairport`) = (`airport`.`faa`)) and ((`airport`.`type`) = \"airport\")) and ((`route`.`type`) = \"route\"))", "~child": { "#operator": "Sequence", "~children": [ { "#operator": "IndexScan3", "as": "route", "index": "ix_type", "index_id": "d925e49b3a11ae3d", "index_projection": { "primary_key": true }, "keyspace": "travel-sample", "namespace": "default", "nested_loop": true, "spans": [ { "exact": true, "range": [ { "high": "\"route\"", "inclusion": 3, "low": "\"route\"" } ] } ], "using": "gsi" }, { "#operator": "Fetch", "as": "route", "keyspace": "travel-sample", "namespace": "default", "nested_loop": true } ] } }, { "#operator": "Merge", "as": "route", "keyspace": "travel-sample", "namespace": "default", "update": { "#operator": "Sequence", "~children": [ { "#operator": "Filter", "condition": "((((`route`.`airline`) = \"BA\") and ((`airport`.`country`) = \"France\")) and contains((`route`.`equipment`), \"319\"))" }, { "#operator": "Clone" }, { "#operator": "Set", "set_terms": [ { "path": "(`route`.`old_equipment`)", "value": "(`route`.`equipment`)" }, { "path": "(`route`.`equipment`)", "value": "\"797\"" }, { "path": "(`route`.`updated`)", "value": "true" } ] }, { "#operator": "SendUpdate", "alias": "route", "keyspace": "travel-sample", "namespace": "default" } ] } } ] } }, { "#operator": "Discard" } ] }, |
Na explicação, você pode ver um operador NestedLoopJoin que é usado para a operação ANSI JOIN (transformada internamente) entre a origem e o destino.
A dica de união pode ser especificada na fonte:
|
1 2 3 4 5 6 7 |
MERGE INTO `travel-sample` AS route USING `travel-sample` AS airport USE HASH(build) ON route.sourceairport = airport.faa AND airport.type = "airport" AND route.type = "route" WHEN MATCHED THEN UPDATE SET route.old_equipment = route.equipment, route.equipment = "797", route.updated = true WHERE route.airline = "BA" AND airport.country = "France" AND CONTAINS(route.equipment, "319"); |
Neste exemplo, a dica USE HASH é especificada no aeroporto.
Tratamento de várias correspondências em ANSI MERGE
Na mesclagem de pesquisa, como a condição de mesclagem está na chave do documento, há apenas uma única correspondência de documento de destino potencial para cada documento de origem. No ANSI MERGE, como a condição de mesclagem agora é uma expressão arbitrária, pode haver várias correspondências de documentos de destino para cada documento de origem. Para cada ação de mesclagem especificada, a ação de mesclagem só pode ser executada no máximo uma vez para cada documento de origem. No caso de várias correspondências, se uma ação de mesclagem for tentada uma segunda vez para o mesmo documento de origem, um erro será retornado (código de erro 5320 ou 5330). Se esse erro for encontrado, a condição de mesclagem (cláusula ON) precisará ser refinada para evitar várias correspondências para o mesmo documento de origem.
Ação INSERT para ANSI MERGE
Na mesclagem de pesquisa, se uma ação INSERT for especificada, somente o documento que está sendo inserido será fornecido. Como a mesclagem de pesquisa exige uma correspondência na chave do documento e a ação INSERT só é acionada quando nenhuma correspondência é encontrada (ou seja, nenhum documento com essa chave de documento existe no destino), o documento recém-inserido simplesmente usará a mesma chave de documento. Para ANSI MERGE, isso não é mais verdade, pois não é mais necessário que uma chave de documento seja a condição de correspondência. Como resultado, quando uma ação INSERT é especificada, uma chave de documento também deve ser especificada como parte da ação INSERT. Por exemplo:
|
1 2 3 4 5 |
MERGE INTO `travel-sample` AS route USING `travel-sample` AS airport ON route.sourceairport = airport.faa AND airport.type = "airport" AND route.type = "route" WHEN NOT MATCHED THEN INSERT (KEY UUID(), VALUE {"sourceairport": airport.faa, "targetairport": "SFO", "type": "route"}) WHERE airport.country = "France"; |
Em comparação, aqui está a sintaxe da ação INSERT para mesclagem de pesquisa:
|
1 |
WHEN NOT MATCHED THEN INSERT <expr> |
em que é o documento a ser inserido.
Aqui está a sintaxe da ação INSERT para ANSI MERGE:
|
1 2 |
WHEN NOT MATCHED THEN INSERT (<key_expr>, <value_expr>) WHEN NOT MATCHED THEN INSERT (KEY <key_expr>, VALUE <value_expr>) |
em que especifica a chave do documento para o documento recém-inserido e especifica o novo documento. Os dois são separados por uma vírgula.
Há duas formas que podem ser usadas, as palavras-chave KEY e VALUE são opcionais. Um novo par de parênteses também é necessário.
As outras ações de mesclagem (ação UPDATE, ação DELETE) permanecem as mesmas entre a mesclagem de pesquisa e a ANSI MERGE.
Resumo
Os aprimoramentos do ANSI JOIN e o suporte ao ANSI MERGE no Couchbase 6.5 aumentam a conformidade do N1QL com o ANSI e tornam o N1QL mais fácil de usar, especialmente para a migração de um banco de dados relacional.