O Couchbase Lite 2.0 suporta a capacidade de realizar JOINS em seus documentos JSON. Isso faz parte da nova interface de consulta baseada em N1QLa linguagem de consulta declarativa do Couchbase que estende o SQL para JSON. Se você estiver familiarizado com SQL, vai se sentir em casa com a semântica da nova API.
Os JOINS permitem que você combine o conteúdo de vários documentos. Nesta publicação, forneceremos exemplos para ilustrar os tipos de JOINS possíveis com o Couchbase Lite. Para cada uma das consultas, forneceremos a consulta SQL equivalente. Este blog pressupõe que você esteja familiarizado com os fundamentos da nova API de consulta, portanto, se ainda não tiver feito isso, não deixe de revisar o artigo postagem anterior primeiro. Se estiver interessado, os links para blogs que discutem outros recursos da interface do Query são fornecidos no final desta publicação.
Você pode fazer o download das compilações mais recentes do Couchbase Mobile 2.0 Pre-Release em nosso downloads página.
Histórico
Se você estava usando as versões 1.x do Couchbase Mobile, provavelmente está familiarizado com Visualizações de mapas para criar índices e consultas. Na versão 2.0, você não precisa mais criar visualizações e funções de mapa! Em vez disso, uma interface simples permite a criação de índices e você pode usar uma interface do Query Builder para construir suas consultas. A nova interface de consulta é mais simples de usar e muito mais poderosa em comparação. Vamos descobrir alguns de seus recursos nesta postagem.
Projeto de amostra
Embora os exemplos discutidos aqui usem o Swift para iOS, a mesma interface de consulta também é compatível com as plataformas Android e Windows.
Portanto, com alguns pequenos ajustes, você poderá reutilizar os exemplos de consulta desta postagem ao trabalhar com outras plataformas.
Siga as instruções abaixo se você estiver interessado em um projeto Swift de amostra
- Clone o iOS Swift Playground do GitHub
|
1 |
$ git clone https://github.com/couchbaselabs/couchbase-lite-ios-api-playground |
- Siga as instruções de instalação no manual correspondente LEIAME para criar e executar o playground.
Modelo de dados de amostra
Usaremos um banco de dados de amostra localizado aqui. Você pode incorporar esse banco de dados pré-criado em seu aplicativo móvel e começar a usá-lo para suas consultas.
O conjunto de dados de amostra é um pouco artificial, mas o objetivo aqui é demonstrar alguns casos de uso típicos de unir-se consultas.
- "Documento do tipo "funcionário
|
1 2 3 4 5 6 7 |
{ "type": "employee", "firstname": "John", "lastname": "Smith", "department": "1000", "location": "101" } |
- "Documento do tipo "departamento
|
1 2 3 4 5 6 7 8 9 10 11 |
{ "type": "department", "name": "Product Management", "code": "2000", "head": { "firstname": "Patricia", "lastname": "Shoji" }, "location":["101","102"] } |
- Documento do tipo "location" (local)
|
1 2 3 4 5 6 |
{ "type": "location", "name": "HQ", "address": "1123 6th St. Melbourne, FL 32904 ", "code": "101" } |
** Consulte o modelo acima para cada um dos exemplos de consulta abaixo. **
O identificador do banco de dados
Nas consultas abaixo, usaremos o Banco de dados API para abrir/criar o banco de dados CouchbaseLite.
|
1 2 |
let options = DatabaseConfiguration() let db = try Database(name: kDBName, config: options) |
Índices
Para acelerar as consultas de leitura, você pode criar índices nas propriedades que serão consultadas. A melhoria no desempenho seria significativa em grandes conjuntos de dados. Obviamente, esteja ciente de que haverá um aumento na necessidade de armazenamento para guardar os índices, e o desempenho das gravações também poderá ser afetado. Portanto, tenha cuidado ao criar muitos índices.
O exemplo a seguir cria um Índice de valor no tipo propriedade de um documento
|
1 |
try db.createIndex(IndexBuilder.valueIndex(items: ValueIndexItem.property("type")),withName: "typeIndex") |
JOIN ou Inner JOIN
Você pode usar uma consulta JOIN ou Inner JOIN simples para buscar propriedades dos documentos participantes se e somente se ambos os documentos atenderem às condições especificadas no ON cláusula.
Por exemplo, considerando o modelo de dados que apresentamos anteriormente, vamos supor que você queira buscar o firstName, lastName de um "funcionário" e o correspondente nome do "departamento" ao qual o funcionário pertencia. Nesse caso, primeiro nome e sobrenome as propriedades são obtidas do documento de tipo "funcionário" e o departamento nome é obtido do documento de tipo "departamento" se e somente se o departamento da propriedade "employee" corresponde à propriedade código propriedade no "departamento"
Isso implica que, se não houver documentos de "departamento" que correspondam ao código no documento "employee", os detalhes desse funcionário não serão incluídos no resultado de saída

Solicitaçã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 |
try db.createIndex(IndexBuilder.valueIndex(items: ValueIndexItem.property("type")),withName: "typeIndex") // set up aliases to represent the data source for "department" type document let departmentDS = DataSource.database(db).as("departmentDS") // Property expression for "department" (in employee documents) let employeeDeptExpr = Expression.property("department").from("employeeDS") // Property expression for "code" (in department documents) let departmentCodeExpr = Expression.property("code").from("departmentDS") // Join clause: Join employee type and department type documents where the // "department" field of employee documents is equal to the department "code"; // of "department" documents let joinExpr = employeeDeptExpr.equalTo(departmentCodeExpr) .and(Expression.property("type").from("employeeDS").equalTo(Expression.string("employee"))) .and(Expression.property("type").from("departmentDS").equalTo(Expression.string("department"))) // Construct inner join expression with ON query. let join = Join.join(departmentDS).on(joinExpr) // return the "firstname", "lastname"; and "department"; name from the documents that are joined based // on the JOIN expression let searchQuery = Query.select( SelectResult.expression(Expression.property("firstname").from("employeeDS")), SelectResult.expression(Expression.property("lastname").from("employeeDS")), SelectResult.expression(Expression.property("name").from("departmentDS"))) .from(employeeDS) .join(join) |
ANSI SQL
A instrução SQL equivalente para a consulta acima seria
|
1 2 3 4 5 6 7 8 9 10 |
SELECT employeeDS.firstname, employeeDS.lastname, departmentDS.name FROM `travel-sample` employeeDS INNER JOIN `travel-sample` departmentDS ON employeeDS.department = departmentDS.code WHERE employeeDS.type = "employee" AND departmentDS.type = "department" |
Left JOIN ou Left Outer JOIN
Você pode usar uma consulta JOIN à esquerda para obter propriedades dos documentos participantes se ambos os documentos atenderem às condições especificadas no ON cláusula. No entanto, diferentemente de um JOIN regular, os resultados também incluirão documentos não correspondentes no lado esquerdo da cláusula ON da expressão JOIN.
Por exemplo, considerando o modelo de dados que apresentamos anteriormente, vamos supor que você queira buscar o firstName, lastName de um "funcionário" e o correspondente nome do "departamento" ao qual o funcionário pertencia.
Além disso, vamos supor que também estejamos interessados nofirstName e lastName de um "funcionário" cujo departamento código faz não corresponde a um departamento válido. Esse poderia ser o caso, por exemplo, se o departamento O código do funcionário foi inserido incorretamente.
Nesse caso, primeiro nome e sobrenome as propriedades são obtidas do documento de tipo "funcionário" e o departamento nome é obtido do documento de tipo "departamento" se o departamento da propriedade "employee" corresponde à propriedade código propriedade no "departamento".
Se não houver um departamento correspondente, somente o primeiro nome e sobrenome do documento "employee" são retornadas.

Solicitaçã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 |
// set up aliases to represent the data source for "employee" type document let employeeDS = DataSource.database(db).as("employeeDS") // set up aliases to represent the data source for "department" type document let departmentDS = DataSource.database(db).as("departmentDS") // Property expression for "department" (in employee documents) let employeeDeptExpr = Expression.property("department").from("employeeDS") // Property expression for "code" (in department documents) let departmentCodeExpr = Expression.property("code").from("departmentDS") // Join clause: Join department type and employee type documents where the // "department" field of employee documents is equal to the department "code" of // "department" documents let joinExpr = employeeDeptExpr.equalTo(departmentCodeExpr) .and(Expression.property("type").from("employeeDS").equalTo(Expression.string("employee"))) .and(Expression.property("type").from("departmentDS").equalTo(Expression.string("department"))) // join expression with ON query let join = Join.leftJoin(departmentDS).on(joinExpr) // return the "firstname", "lastname" and "department" name from the documents that are joined based on the JOIN expression let searchQuery = Query.select( SelectResult.expression(Expression.property("firstname").from("employeeDS")), SelectResult.expression(Expression.property("lastname").from("employeeDS")), SelectResult.expression(Expression.property("name").from("departmentDS"))) .from(employeeDS) .join(join) |
ANSI SQL
A instrução SQL equivalente para a consulta acima seria
|
1 2 3 4 5 6 7 8 9 10 |
SELECT employeeDS.firstname, employeeDS.lastname, departmentDS.name FROM `travel-sample` employeeDS LEFT JOIN `travel-sample` departmentDS ON employeeDS.department = departmentDS.code WHERE employeeDS.type = "employee" AND departmentDS.type = "department" |
Cross JOIN
Você pode usar uma consulta cross JOIN para buscar o produto cartesiano das propriedades dos documentos participantes, que normalmente não estão relacionados entre si. Isso é o equivalente a um inner JOIN sem o ON da expressão de união.
Por exemplo, considerando o modelo de dados que apresentamos anteriormente, vamos supor que você queira buscar o produto cartesiano de todos os documentos de tipo "localização" e tipo "departamento". Em outras palavras, cada "local" tipo seria combinado com cada um dos documentos do "departamento" tipo documentos.
Como não há em especificada na expressão cross JOIN, você precisaria incluir uma cláusula onde para filtrar o subconjunto de documentos a ser considerado com base no documento tipo.

Solicitaçã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 |
// set up aliases to represent the data source for "department" type document let departmentDS = DataSource.database(db).as("departmentDS") // set up aliases to represent the data source for "location" type document let locationDS = DataSource.database(db).as("locationDS") // Property expression for "department" (in employee documents) let employeeDeptExpr = Expression.property("code").from("departmentDS") // Property expression for "department" (in location documents) let departmentCodeExpr = Expression.property("code").from("locationDS") // cross join expression let join = Join.crossJoin(locationDS) // type property expressions let deptTypeExpr = Expression.property("type").from("departmentDS") let locationTypeExpr = Expression.property("type").from("locationDS") // The where clauses filters the set of documents to cross-join on // We alias the "code" properties since it exists in both the department and location docs // NOTE: The where clause is used to filter the documents to be considered as // part of the cartesian join let searchQuery = Query.select( SelectResult.expression(Expression.property("name").from("departmentDS")).as("DeptName"), SelectResult.expression(Expression.property("name").from("locationDS")).as("LocationName"), SelectResult.expression(Expression.property("address").from("locationDS"))) .from(departmentDS) .join(join) .where(deptTypeExpr.equalTo(Expression.string("department")) .and(locationTypeExpr.equalTo(Expression.string("location")))) |
ANSI SQL
A instrução SQL equivalente para a consulta acima seria
|
1 2 3 4 5 6 7 8 9 |
SELECT departmentDS.name AS DeptName, locationDS.name AS LocationName, locationDS.address FROM `travel-sample` departmentDS CROSS JOIN `travel-sample` locationDS WHERE departmentDS.type = "department" |
Encadeamento de JOINs
É possível especificar várias expressões JOIN em seu selecionar para poder unir-se a documentos com base em critérios diferentes.
Por exemplo, considerando o modelo de dados que apresentamos anteriormente, vamos supor que você queira buscar o firstName, lastName de um "funcionário" e o correspondente nome do "departamento" ao qual o funcionário pertencia. Além disso, você também queria identificar o nome do "local" em que o funcionário estava baseado.
Nesse caso, usamos duas expressões JOIN.
A primeira expressão JOIN é usada para unir documentos de tipo "funcionário" com documentos de tipo "department" com base na propriedade "department code". Nesse caso, a propriedade primeiro nome e sobrenome as propriedades são obtidas do documento de tipo "funcionário" e o departamento nome é obtido do documento de tipo "departamento" se e somente se o departamento da propriedade "employee" corresponde à propriedade código propriedade no "departamento".
A segunda expressão JOIN é usada para unir documentos de tipo "funcionário" com documentos de tipo "location" com base na propriedade "location code". Nesse caso, a propriedade primeiro nome e sobrenome as propriedades são obtidas do documento de tipo "funcionário" e o local nome é obtido do documento de tipo "localização" se e somente se o localização da propriedade "employee" corresponde à propriedade código propriedade no "departamento".

Solicitaçã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 |
// set up aliases to represent the data source for "employee" type document let employeeDS = DataSource.database(db).as("employeeDS") // set up aliases to represent the data source for "department" type document let departmentDS = DataSource.database(db).as("departmentDS") // set up aliases to represent the data source for "location" type document let locationDS = DataSource.database(db).as("locationDS") // Property expression for "department" property (in employee documents) let employeeDeptExpr = Expression.property("department").from("employeeDS") // Property expression for "location" property (in employee documents) let employeeLocationExpr = Expression.property("location").from("employeeDS") // Property expression for "code" property (in department documents) let departmentCodeExpr = Expression.property("code").from("departmentDS") // Property expression for "code" property (in location documents) let locationCodeExpr = Expression.property("code").from("locationDS") // Join Criteria 1 // Join where the "department" field of employee documents is equal to the department "code" of "department" documents let joinDeptCodeExpr = employeeDeptExpr.equalTo(departmentCodeExpr) .and(Expression.property("type").from("employeeDS").equalTo(Expression.string("employee"))) .and(Expression.property("type").from("departmentDS").equalTo(Expression.string("department"))) // Join Criteria 2 // Join where the "department" field of employee documents is equal to the department "code" of "department" documents let joinLocationCodeExpr = employeeLocationExpr.equalTo(locationCodeExpr) .and(Expression.property("type").from("employeeDS").equalTo(Expression.string("employee"))) .and(Expression.property("type").from("locationDS").equalTo(Expression.string("location"))) // join expression for department code let joinDeptCode = Join.join(departmentDS).on(joinDeptCodeExpr) // join expression for location code let joinLocationCode = Join.join(locationDS).on(joinLocationCodeExpr) // Multiple join expressions in the join clause let searchQuery = QueryBuilder.select(SelectResult.expression(Expression.property("firstname").from("employeeDS")), SelectResult.expression(Expression.property("lastname").from("employeeDS")), SelectResult.expression(Expression.property("name").from("departmentDS")).as("deptName"), SelectResult.expression(Expression.property("name").from("locationDS")).as("locationName")) .from(employeeDS) .join(joinDeptCode,joinLocationCode) |
ANSI SQL
A instrução SQL equivalente para a consulta acima seria
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
SELECT employeeDS.firstname, employeeDS.lastname, departmentDS.name AS deptName, locationDS.name AS locationName FROM `travel-sample` employeeDS JOIN `travel-sample` departmentDS ON employeeDS.department = departmentDS.code JOIN `travel-sample` locationDS ON employeeDS.location = locationDS.code WHERE departmentDS.type = "department" AND locationDS.type = "location" AND employeeDS.type = "employee" |
JOIN Expressões com funções
Embora todos os exemplos tenham usado o igual a na expressão JOIN, deve-se observar que você pode usar qualquer operador de comparação, como entre, greaterThanOrEqualTo e assim por diante na expressão JOIN. Você também pode incluir qualquer Função expressões. Esse é um recurso poderoso.
Por exemplo, considerando o modelo de dados que apresentamos anteriormente, vamos supor que você queira buscar o departamento nome e correspondente localização nomes do "local" onde o departamento estava baseado. Um departamento pode pertencer a um ou mais locais.
Nesse caso, a expressão JOIN uniria documentos do tipo "department" (departamento) e "location" (local) procurando correspondências em qualquer um dos membros da tabela localização do documento do departamento usando a propriedade de matriz ArrayFunction expressã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 |
// set up aliases to represent the data source for "department" type document let departmentDS = DataSource.database(db).as("departmentDS") // set up aliases to represent the data source for "location" type document let locationDS = DataSource.database(db).as("locationDS") // Property expression for "location" property (in department documents) let departmentLocationExpr = Expression.property("location").from("departmentDS") // Property expression for "code" property (in location documents) let locationCodeExpr = Expression.property("code").from("locationDS") // Join where the "code" field of location documents is contained in // the "location" array of "department" documents let joinDeptCodeExpr = ArrayFunction.contains(departmentLocationExpr, value: locationCodeExpr) .and(Expression.property("type").from("locationDS").equalTo(Expression.string("location")) .and(Expression.property("type").from("departmentDS").equalTo(Expression.string("department")))) // join expression let joinLocationCode = Join.join(departmentDS).on(joinDeptCodeExpr) // Search query with JOIN let searchQuery = QueryBuilder.select( SelectResult.expression(Expression.property("name").from("departmentDS")).as("departmentName"), SelectResult.expression(Expression.property("name").from("locationDS")).as("locationName")) .from(locationDS) .join(joinLocationCode) |
ANSI SQL
As matrizes não são compatíveis com o SQL. Entretanto, o N1QL inclui suporte para matrizes. A instrução do tipo SQL correspondente para a consulta acima seria
|
1 2 3 4 5 6 7 8 9 10 11 12 |
SELECT departmentDS.name AS departmentName, locationDS.name AS locationName FROM `travel-sample` locationDS JOIN `travel-sample` departmentDS ON ANY code IN departmentDS.location SATISFIES code = locationDS.location END WHERE departmentDS.type = "department" AND locationDS.type = "location" |
O que vem a seguir
Esta postagem do blog analisou o poderoso recurso JOIN do Couchbase Mobile 2.0, que permite combinar resultados de vários documentos JSON. Você pode download Couchbase Mobile 2.0 e teste as consultas discutidas nesta publicação. Isso é um começo. Espere ver mais funcionalidades em versões futuras.
Aqui estão algumas outras postagens relacionadas ao Couchbase Mobile Query que podem ser de interesse
- Isso postagem no blog discute os fundamentos
- Isso postagem no blog discute como consultar coleções de matrizes
- Isso postagem no blog discute os recursos de pesquisa de texto completo (FTS).
Se tiver dúvidas ou comentários, deixe um comentário abaixo ou entre em contato comigo pelo Twitter @rajagp ou envie-me um e-mail priya.rajagopal@couchbase.com. O Fóruns do Couchbase são outro bom lugar para entrar em contato com perguntas.