Sauter au contenu

Cours 08 - Query & Relations

Les types de relations entre les tables et l’utilisation des jointures ont déjà été couvertes.

Cette méthodes pour récupérer des données en joignant plusieurs tables demeurant la plus efficace et la meilleure pour perfectionner sa talents en SQL. Il existe cependant une autre façon de croiser des données en exploitant les relations entre les tables.

Il faut premièrement s’assurer que Drizzle retrouve bien les différents schémas déclarés dans l’application. Une façon de faire est de créer un fichier de type barrel. Si ce n’est pas déjà fait, on crée un fichier index.js à l’intérieur du dossier /src/db/schema. La fonction de ce dernier sera de centraliser tous les schémas en les exportant depuis cet endroit:

/src/db/schema/index.js
// l'extension .js est nécessaire ici
// (ce fichier est aussi utilisé pour la migration sans passer par Nextjs)
export * from './schema.js';
export * from './un-autre-fichier-de-schemas.js';
export * from './un-troisieme-fichier-de-schemas.js'

Dans un fichier /src/db/relations.js, on explicitera chacune des relations propres à chaque table. Nous compléterons ces relations dans la rubrique suivante.

/src/db/relations.js
import { defineRelations } from "drizzle-orm";
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({}));

Finalement, dans le fichier /src/db/index.js, on fournira les relations à Drizzle:

/src/db/index.js
import "dotenv/config"; // pour lire les variables d'environnement
import { drizzle } from "drizzle-orm/libsql";
import { relations } from "./relations";
// correspond au nom de la variable dans .env
export const db = drizzle({
// logger: true,
connection: { url: process.env.DATABASE_URL },
relations,
});

Une fois les schémas retrouvés, il sera possible de faire des requêtes à la bd en utilisant des queries (db.query.nomDeLaTable({})) plutôt que select.

// retourne tous les posts qui correspondent
export const getAllPosts = async () => {
// retourne un tableau de résultats
return db.query.postsTable.findMany({
columns: {
id: false // <- ne sélectionne pas les id dans la requête par exemple
},
orderBy: {
id: 'asc',
title: 'desc'
},
limit: 5
})
};
// ne retourne qu'un seul post
export const getPostById = async (postId) => {
return db.query.postsTable.findFirst({
where: eq(postsTable.id, postId)
})
};

La rubrique Relations de la documentation de Drizzle est très complète. Ces relations permettent d’établir des liens explicites de type “un à un”, “un à plusieurs” et “plusieurs à plusiers”.

Une fois les schémas rédigés, il est nécesaire de reprendre chacune des tables pour déclarer leurs relations. Toutes les relations seront centralisées dans le fichier /src/db/relations.js.

Pour reprendre l’exemple de la documentation du site, si on explore le liens entre un utilisateur et ses publications:

export const usersTable = sqliteTable('users', {
id: integer().primaryKey(),
name: text(),
});
export const postsTable = sqliteTable('posts', {
id: integer().primaryKey(),
content: text(),
authorId: integer('author_id').reference(() => usersTable.id, {onDelete: 'cascade'}),
});
  1. Un utilisateur possède plusieurs publications.
  2. Une publication appartient à un utilisateur.

Pour chaque table, on déclarera les relations en leur donnant un nom dans l’objet relations. Voir la déclaration par étape:

/src/db/relations.js
import { defineRelations } from "drizzle-orm";
import * as schema from "./schema";
// On crée une propriété correspondant au nom des tables (voir les tables déclarées ci-haut)
export const relations = defineRelations(schema, (r) => ({
usersTable: {},
postsTable: {}
}));

On peut déclarer plusieurs relations pour une même table. Ainsi, un utilisateur pourrait posséder plusieurs publications, mais n’appartenir qu’à un seul rôle:

export const relations = defineRelations(schema, (r) => ({
usersTable: {
posts: r.many.postsTable({
from: r.usersTable.id,
to: r.postsTable.authorId
}),
role: r.one.rolesTable({
from: r.usersTable.roleId,
to: r.rolesTable.id
})
},
// ... extrait
}));

Les relations plusieurs à plusiers sont finalement une combinaison de relations “un à plusieurs” en les établissant pour chacune des trois tables impliquées:

export const postsTable = sqliteTable('posts', {
id: integer().primaryKey(),
content: text(),
authorId: integer('author_id').reference(() => usersTable.id, {onDelete: 'cascade'}),
});
export const tagsTable = sqliteTable('tags', {
id: integer().primaryKey(),
title: text().notNull()
});
export const tagsToPostTable = sqliteTable('tags_posts', {
postId: integer('post_id').reference(() => postsTable.id, {onDelete: 'cascade'}),
tagId: integer('tag_id').reference(() => tagsTable.id, {onDelete: 'cascade'}),
}, (t) => [
primaryKey({columns: [t.postId, t.tagId]}) // clé primaire composite (combinant les deux champs, doit donc rester unique)
]);

Une fois les relations explicitement déclarées, il sera possible d’inclure les tables liées lors des requêtes de type db.query:

export const getAllPosts = async () => {
// retourne un tableau de résultats
return db.query.postsTable.findMany({
columns: {
id: false // <- ne sélectionne pas les id dans la requête par exemple
},
with: {
author: true, // author est le nom de la relation déclarée ci-haut
},
orderBy: (posts, {asc}) => [asc(posts.id)],
limit: 5
});
};