Introduction

N1QL prononcé "KICKEL" est le language de requêtage couchbase qui manipule des documents json. Il adopte la même syntaxe que le SQL.

Vous pouvez exécuter des requêtes N1QL à partir de la ligne de commande, à l'aide de l'outil cbq ou à partir de Query Workbench dans la console Web Couchbase Server.

Dans cet article, nous allons utiliser N1QL pour manipuler les collections précédement créées avec l'outil de ligne de commande cbq.

Dans Couchbase, les documents sont stockés dans des collections organisées dans des scopes, qui sont à leur tour stockées dans des buckets au sein d'un namespace. Le moteur de requête doit connaître le chemin complet de la collection. Le chemin complet d'une collection a le format namespace:bucket.scope.collection.

Se connecter à cbq

# cbq -u admin -p admin123 -engine=http://127.0.0.1:8091/
 Connected to : http://127.0.0.1:8091/. Type Ctrl-D or \QUIT to exit.
 Path to history file for the shell : /root/.cbq_history 
cbq> 

Mon premier SELECT avec cbq

Maintenant nous savons comment nous connecter à cbq, lançons notre première requête pour afficher tous les étudiants de la collection EDUTIANT.

cbq> SELECT * FROM default:`ecole-bucket`.`ecole-scope`.etudiant;
{
    "requestID": "fe42ad55-84e3-4517-bf9b-294a01634851",
    "errors": [
        {
            "code": 4000,
            "msg": "No index available on keyspace `default`:`ecole-bucket`.`ecole-scope`.`etudiant` that matches your query. Use CREATE PRIMARY INDEX ON `default`:`ecole-bucket`.`ecole-scope`.`etudiant` to create a primary index, or check that your expected index is online."
        }
    ],
    "status": "fatal",
    "metrics": {
        "elapsedTime": "186.651124ms",
        "executionTime": "186.565724ms",
        "resultCount": 0,
        "resultSize": 0,
        "serviceLoad": 12,
        "errorCount": 1
    }
}

Hmmm, un joli message d'erreur indiquant que la collection est dépourvue de l'index. En effet, dans couchbase, toute collection doit avoir au moins un index sur les documents dont on veut requêter.

Créer un index

Un index couchbase peut être de type secondaire qui se porte sur un ou plusieurs champs du document ou primaire pour indexer l'identifiant du document comme ci-dessous.

cbq> create primary index etudiant_idx on `ecole-bucket`.`ecole-scope`.etudiant;
{
    "requestID": "ab21a8ee-a799-495c-9d08-30a08f3c2137",
    "signature": null,
    "results": [
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "2.530386236s",
        "executionTime": "2.53030953s",
        "resultCount": 0,
        "resultSize": 0,
        "serviceLoad": 12
    }
}

Inserer des données

cbq> INSERT INTO `ecole-bucket`.`ecole-scope`.etudiant (KEY, VALUE) VALUES ("etudiant1", { "nom": "Rachid", "email": "rachid@example.com" });
{
    "requestID": "fd80cf56-726d-4549-bfc6-b8edf2a44a0c",
    "signature": null,
    "results": [
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "19.251533ms",
        "executionTime": "19.156702ms",
        "resultCount": 0,
        "resultSize": 0,
        "serviceLoad": 12,
        "mutationCount": 1
    }
}
cbq> INSERT INTO `ecole-bucket`.`ecole-scope`.etudiant (KEY, VALUE) VALUES ("etudiant2", { "nom": "Michel", "email": "michel@example.com" , "adresse": "55 Rue du Faubourg Saint-Honoré, Paris"});
{
    "requestID": "871872d6-4fa1-401a-a762-eae2c35c1b7a",
    "signature": null,
    "results": [
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "92.495986ms",
        "executionTime": "92.419802ms",
        "resultCount": 0,
        "resultSize": 0,
        "serviceLoad": 12,
        "mutationCount": 1
    }
}
cbq> INSERT INTO `ecole-bucket`.`ecole-scope`.etudiant (KEY, VALUE) VALUES ("etudiant3", { "nom": "Sabrina", "email": "sabrina@example.com", "age": 18 });
{
    "requestID": "37578847-5531-4194-bb49-0788185ffc80",
    "signature": null,
    "results": [
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "48.554564ms",
        "executionTime": "48.486839ms",
        "resultCount": 0,
        "resultSize": 0,
        "serviceLoad": 12,
        "mutationCount": 1
    }
}

Lister tous les étudiants

cbq> SELECT * FROM default:`ecole-bucket`.`ecole-scope`.etudiant;
{
    "requestID": "8b03d1d4-519d-4346-805c-f076defea882",
    "signature": {
        "*": "*"
    },
    "results": [
    {
        "etudiant": {
            "email": "rachid@example.com",
            "nom": "Rachid"
        }
    },
    {
        "etudiant": {
            "adresse": "55 Rue du Faubourg Saint-Honoré, Paris",
            "email": "michel@example.com",
            "nom": "Michel"
        }
    },
    {
        "etudiant": {
            "age": 18,
            "email": "sabrina@example.com",
            "nom": "Sabrina"
        }
    }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "7.381213ms",
        "executionTime": "7.314113ms",
        "resultCount": 3,
        "resultSize": 421,
        "serviceLoad": 12
    }
}

Comme vous avez pu le constaté, par défaut, le cqb affiche les statistiques d'exécution de la requête comme le temps d'exécution, nombre de documents retournés,...etc. A noter aussi, les trois documents json retournés sont de format différents avec des champs variés, ce qui n'est pas possible dans le modèle relationnel.

Filtrer les résultats

cbq> SELECT a.* FROM default:`ecole-bucket`.`ecole-scope`.etudiant a where a.nom = "Rachid";
{
    "requestID": "5e3b0408-5390-45a6-b1f2-6a7f5a1214b2",
    "signature": {
        "*": "*"
    },
    "results": [
    {
        "email": "rachid@example.com",
        "nom": "Rachid"
    }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "176.029021ms",
        "executionTime": "175.904439ms",
        "resultCount": 1,
        "resultSize": 70,
        "serviceLoad": 12
    }
}

Limiter les résultats

cbq> SELECT a.* FROM default:`ecole-bucket`.`ecole-scope`.etudiant a LIMIT 2;
{
    "requestID": "7905e83f-2b27-4a59-a8cd-6e791050ce94",
    "signature": {
        "*": "*"
    },
    "results": [
    {
        "email": "rachid@example.com",
        "nom": "Rachid"
    },
    {
        "adresse": "55 Rue du Faubourg Saint-Honoré, Paris",
        "email": "michel@example.com",
        "nom": "Michel"
    }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "2.028518ms",
        "executionTime": "1.949127ms",
        "resultCount": 2,
        "resultSize": 202,
        "serviceLoad": 12
    }
}

Contexte des requêtes

Pour éviter de prefixer la collection avec le chemin complet à chaque requête, nous pouvons définir un contexte d'exécution comme suivant:

cbq> \SET -query_context default:`ecole-bucket`.`ecole-scope`;
cbq> SELECT a.* FROM etudiant a WHERE a.nom = "Rachid";
{
    "requestID": "771d5bda-cb4b-4974-bc71-7c0347544bbe",
    "signature": {
        "*": "*"
    },
    "results": [
    {
        "email": "rachid@example.com",
        "nom": "Rachid"
    }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "4.959393ms",
        "executionTime": "4.737697ms",
        "resultCount": 1,
        "resultSize": 70,
        "serviceLoad": 12
    }
}

Afficher les métadonnées

Si vous regardez le résultat de la requête précédente, la clé ou l'identifiant du document n'est pas affiché parmis les champs returnés! Car l'identifiant ou la clé primaire du document est une métadonnées qui n'est pas affichée par défaut.

Pour inculre les métadonnées dans le résultat de la requête, il faut les demander explicitement. Dans cet exemple, on afficher l'identifiant unique de chaque document.

cbq> SELECT META(a).id, a.* FROM etudiant a WHERE a.nom = "Rachid";
{
    "requestID": "b6befeac-e5a2-4d32-bc04-d2dfb5678151",
    "signature": {
        "*": "*",
        "id": "json"
    },
    "results": [
    {
        "email": "rachid@example.com",
        "id": "etudiant1",
        "nom": "Rachid"
    }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "2.135321ms",
        "executionTime": "2.069432ms",
        "resultCount": 1,
        "resultSize": 97,
        "serviceLoad": 12
    }
}

La requête suivante affiche toutes les métadonnées d'un document.

cbq> SELECT META() FROM etudiant WHERE nom = "Rachid";
{
    "requestID": "46079a17-2a44-4798-95e8-4a8c82b5802a",
    "signature": {
        "$1": "object"
    },
    "results": [
    {
        "$1": {
            "cas": 1672865723335901184,
            "expiration": 0,
            "flags": 0,
            "id": "etudiant1",
            "keyspace": "default:ecole-bucket.ecole-scope.etudiant",
            "type": "json"
        }
    }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "1.606205ms",
        "executionTime": "1.54872ms",
        "resultCount": 1,
        "resultSize": 253,
        "serviceLoad": 12
    }
}

Supprimer des documents

cbq> DELETE FROM etudiant;
{
    "requestID": "6113a90a-750d-4a8c-8ce4-eb069b653379",
    "signature": null,
    "results": [
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "67.815454ms",
        "executionTime": "67.73153ms",
        "resultCount": 0,
        "resultSize": 0,
        "serviceLoad": 12,
        "mutationCount": 3
    }
}

Les jointures

Avant d'utiliser les jointures, nous allons inserer des données dans les deux collection ETUDIANT et CLASSE.

INSERT INTO `ecole-bucket`.`ecole-scope`.etudiant (KEY, VALUE) VALUES ("etudiant1", { "nom": "Rachid", "email": "rachid@example.com", "classe": "Math" });
INSERT INTO `ecole-bucket`.`ecole-scope`.etudiant (KEY, VALUE) VALUES ("etudiant2", { "nom": "Michel", "email": "michel@example.com", "classe": "Math" });
INSERT INTO `ecole-bucket`.`ecole-scope`.etudiant (KEY, VALUE) VALUES ("etudiant3", { "nom": "Sabrina", "email": "michel@example.com", "classe": "Physique" });
INSERT INTO `ecole-bucket`.`ecole-scope`.etudiant (KEY, VALUE) VALUES ("etudiant4", { "nom": "Sebastien", "email": "sebastien@example.com", "classe": "Informatique" });
INSERT INTO `ecole-bucket`.`ecole-scope`.etudiant (KEY, VALUE) VALUES ("etudiant5", { "nom": "Arthur", "email": "arthur@example.com", "classe": "Informatique" });


INSERT INTO `ecole-bucket`.`ecole-scope`.classe (KEY, VALUE) VALUES ("Math", { "description": "Sciences exactes" });
INSERT INTO `ecole-bucket`.`ecole-scope`.classe (KEY, VALUE) VALUES ("Physique", { "description": "Physique quantique" });
INSERT INTO `ecole-bucket`.`ecole-scope`.classe (KEY, VALUE) VALUES ("Informatique", { "description": "Intelligence artificielle" });

Créons l'index sur la collection classe.

cbq> create primary index classe_idx on `ecole-bucket`.`ecole-scope`.classe;
{
    "requestID": "2e3f9f6e-af0d-4fe5-904a-1389a1b35a19",
    "signature": null,
    "results": [
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "2.722520382s",
        "executionTime": "2.722429989s",
        "resultCount": 0,
        "resultSize": 0,
        "serviceLoad": 12
    }
}

Affichons tous les étudiants de la classe Informatique.

cbq> \SET -query_context default:`ecole-bucket`.`ecole-scope`;
cbq> SELECT a.nom, a.email, b.description FROM etudiant a INNER JOIN classe b ON a.classe = META(b).id WHERE a.classe = "Informatique";
{
    "requestID": "ed82f89b-cc12-4744-9345-9a7ab30a9c0a",
    "signature": {
        "description": "json",
        "email": "json",
        "nom": "json"
    },
    "results": [
    {
        "description": "Intelligence artificielle",
        "email": "sebastien@example.com",
        "nom": "Sebastien"
    },
    {
        "description": "Intelligence artificielle",
        "email": "arthur@example.com",
        "nom": "Arthur"
    }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "2.817724ms",
        "executionTime": "2.748417ms",
        "resultCount": 2,
        "resultSize": 250,
        "serviceLoad": 12
    }
}