Requêtes Firestore pour SQL Minds

Créez rapidement une application avec Firebase, même si vous êtes intimidé par les bases de données NoSQL : la traduction de requêtes en SQL est plus facile que vous ne le pensez.
Les bases de données SQL sont assez différentes des bases de données NoSQL, vous devez donc penser un peu différemment lors de la modélisation de vos données. Cependant, la plupart des traductions du code SQL vers NoSQL sont identiques ou plus simples. Ici, nous utilisons l’API Firebase Modular, mais le firebase-admin
L’API sera la même.
TL;DR
Firestore est une base de données de documents qui n’autorise pas les jointures. Vous pouvez les simuler en dupliquant vos données. Toutefois, la plupart des requêtes régulières fonctionnent comme prévu. L’absence de schéma permet de se lever et d’avancer extrêmement vite. Vous devez apprendre les modèles de modélisation des données.
Introduction
Firestore est une base de données de documents NoSQL.
Tableaux
- Une table est une collection ou une sous-collection. Une ligne est un document.
Schéma
- Vous devez déclarer un schéma en SQL ; vous n’en avez pas besoin en NoSQL.
- Chaque document peut contenir différents types de données, même si cela ne serait pas utile.
Créer
Vous créez un tableau lorsque vous créez un document. Un tableau n’existe pas sans document.
Modifier
Il n’y a rien à modifier.
Baisse
Pour supprimer un tableau, vous devez supprimer tous les documents d’une collection. Cela peut également être fait dans le Console Firebase.
Contraintes
Il n’y a pas de schéma, mais vous pouvez déclarer des contraintes en utilisant Règles Firebase.
Conventions de dénomination
Vous verrez généralement les noms de champs en cas de chameau au lieu du cas en cas de serpent.
Clé primaire
Chaque document a un chemin unique. Le documentId()
du document est la clé primaire. Dans la collection posts/2xelskel
le 2xelskel
est l’identifiant du document. Vous pouvez utiliser n’importe quelle chaîne alphanumérique, ainsi que _
personnages. Sinon, il sera généré automatiquement.
Insérer
Vous pouvez insérer des données dans SQL en déclarant chaque champ et valeur.
SQL
INSERT INTO posts (title, content, created_by, created_at)
VALUES (
'My First Post',
'This is the content of my first post.',
1,
NOW()
);
Firestore
addDoc(
collection(db, 'posts'),
{
title: 'My First Post',
content: 'This is the content of my first post.',
createdBy: 1,
createdAt: serverTimestamp()
}
});
📝 Notez le serverTimestamp()
fonction, qui utilisera la date sur le serveur. Étant donné que Firestore peut être exécuté en toute sécurité sur le client, cela serait sécurisé dans les règles Firestore.
Mise à jour
La mise à jour est fondamentalement la même.
SQL
UPDATE posts
SET title = 'Updated Post Title',
content = 'This is the updated content of the post.',
updated_at = NOW()
WHERE post_id = 1;
Firestore
updateDoc(
collection(db, 'posts', '1'),
{
title = 'Updated Post Title',
content = 'This is the updated content of the post.',
updatedAt: serverTimestamp()
}
});
📝 Notez que l’ID du document est 1
dans ce cas. L’ID sera probablement une chaîne générée de manière unique dans une application réelle.
Bouleversé
Vous pouvez insérer un document avec setDoc
.
SQL
INSERT INTO posts (id, title, content, created_by, created_at, updated_at)
VALUES (1, 'Upserted Post', 'This is the upserted content.', 1, NOW(), NOW())
ON CONFLICT (id)
DO UPDATE SET
title = EXCLUDED.title,
content = EXCLUDED.content,
author_id = EXCLUDED.created_by,
updated_at = NOW();
📝 Cela varie selon les langages SQL.
setDoc(
doc(db, 'posts', '1'),
{
title: 'Upserted Post',
content: 'This is the upserted content.',
createdBy: 1,
createdAt: serverTimestamp(),
updatedAt: serverTimestamp()
}, { merge: true }
});
📝 setDoc
se comporte comme addDoc
sans le { merge: true }
paramètre.
Sélectionner
Dans Firestore, vous ne pouvez sélectionner que tous les champs d’un document, sauf si vous utilisez l’API REST avec votre propre code.
SQL
SELECT * FROM posts;
Sélectionnez tous les messages.
Firestore
query(
collection(db, 'posts')
);
Existe
En SQL, vous SELECT
d’abord.
SQL
SELECT EXISTS (
SELECT 1
FROM posts
WHERE id = 1
);
Firestore
const postSnap = await getDoc(doc(db, 'posts', '1'));
const exists = postSnap.exists();
Dans Firestore, vous devez lire le document pour vérifier son existence.
Limite
Bien entendu, nous limitons notre sélection à un ou plusieurs cas d’utilisation réels. Nous ne voulons pas que tous les documents d’une collection ou toutes les lignes d’un tableau.
SQL
SELECT * FROM posts LIMIT 1;
Firestore
query(
collection(db, 'posts'),
limit(1)
);
Il n’y a rien de surprenant ici.
Où
Utilisation des requêtes Firestore AND
par défaut, mais vous pouvez également ajouter OR
requêtes.
SQL
SELECT * FROM posts
WHERE
(a = 1 AND c = 3) OR
(a = 1 AND d = 4) OR
(b = 2 AND c = 3) OR
(b = 2 AND d = 4);
Firestore
query(
collection(db, 'posts'),
or(
and(where('a', '==', 1), where('c', '==', 3)),
and(where('a', '==', 1), where('d', '==', 4)),
and(where('b', '==', 2), where('c', '==', 3)),
and(where('b', '==', 2), where('d', '==', 4))
)
);
⚠️ Contrairement à SQL, chaque requête a une limite stricte de 30 disjonctions.
Dans
Vous pouvez rechercher IN
ainsi que dans Firestore.
SQL
SELECT *
FROM posts
WHERE created_by IN (1, 2, 3);
Firestore
query(
collection(db, 'posts'),
where('created_by', 'in', [1, 2, 3])
)
Gardez à l’esprit qu’il s’agit d’une simplification de :
query(
collection(db, 'posts'),
or(
where('created_by', '==', 1),
where('created_by', '==', 2),
where('created_by', '==', 3)
)
);
Il en va de même pour SQL. IN
c’est juste plusieurs OR
clauses sur le même domaine, condensées.
Commander par
Vous pouvez trier vos données de la même manière que SQL.
SQL
SELECT * FROM posts
ORDER BY created_by ASC, created_at DESC;
Firestore
query(
collection(db, 'posts'),
orderBy('createdBy'),
orderBy('createdAt', 'desc')
);
📝 L’ordre de tri par défaut est asc
.
📝Un orderBy
trie également les données, le champ doit donc exister. Sinon, il est filtré.
⚠️ Changer orderBy
field nécessite un index composite pour chaque requête. Si vous modifiez le champ, vous avez besoin d’un autre index.
Inégalités
Vous pouvez avoir des inégalités sur plusieurs domaines.
SQL
SELECT * FROM cities
WHERE population > 1000000
AND density < 10000;
Firestore
query(
collection(db, 'cities'),
where('population', '>', 1000000),
where('density', '<', 10000),
);
📝 Triez les champs d’index par égalités suivis de la plage la plus sélective ou du champ d’inégalité.
⚠️ Triez les champs par ordre décroissant de sélectivité des contraintes de requête.
⚠️ Requêtes avec filtres de plage ou d’inégalité sur les champs du document et uniquement contraintes d’égalité sur la clé du document (__name__)
ne sont pas pris en charge.
⚠️ Cloud Firestore limite le nombre de champs de plage ou d’inégalité à 10. Cela évite que les requêtes ne deviennent trop coûteuses à exécuter.
PAS égal
Vous ne pouvez avoir qu’un !=
sur un champ dans Firestore par requête.
SQL
SELECT * FROM cities
WHERE capital != false;
Firestore
query(
collection(db, 'cities'),
where("capital", "!=", false)
);
⚠️Un !=
La clause de requête peut correspondre à de nombreux documents d’une collection. Pour contrôler le nombre de résultats renvoyés, utilisez une clause limite.
📝 Les requêtes non égales excluent les documents pour lesquels le champ donné n’existe pas.
📝 Pour plusieurs non égaux sur le même champ, utilisez not-in
.
Incrémentation
Vous n’avez pas besoin d’incrémenter les colonnes dans SQL jusqu’à ce que votre base de données devienne grande. Quoi qu’il en soit, le code est simple.
SQL
UPDATE posts
SET views = views + 1
WHERE id = 1;
Firestore
updateDoc(
doc(db, 'posts', '1'),
{
views: increment(1)
}
});
📝 Ceci est plus utile dans les bases de données NoSQL. Vous pouvez également utiliser increment(-1)
pour décrémenter.
Compter
Le comptage peut devenir complexe dans Firestore, mais en général, vous souhaitez compter sur le serveur.
SQL
SELECT COUNT(*) AS post_count
FROM posts
WHERE created_by = 1;
Firestore
const countSnap = await getCountFromServer(
query(
collection(db, 'posts')
)
);
const postCount = countSnap.data().count;
📝 Vous avez besoin de deux appels distincts pour compter une requête et renvoyer les résultats de la requête.
Autres agrégations de comptes
Vous pouvez également faire sum
et average
agrégations.
📝 min
et max
ne sont pas pris en charge.
Voir Requêtes d’agrégation.
Tri par nombre
Vous ne pouvez pas trier directement par nombre, vous devez donc d’abord compter dans Firestore.
SQL
Disons que nous voulons que tous les utilisateurs et eux post_count
ou le nombre de messages dont ils disposent.
SELECT users.*, COUNT(posts.id) AS post_count
FROM users
JOIN posts ON users.id = posts.author_id
GROUP BY users.id
ORDER BY post_count DESC;
Firestore
Dans Firestore, il faut créer le post_count
avec chaque publication créée dans le document de l’utilisateur. Nous pouvons le faire dans un batch
transaction.
const userRef = doc(db, 'users/1');
const postRef = doc(collection(db, 'posts'));
const batch = writeBatch(db);
batch.set(userRef, {
postCount: increment(1)
}, { merge: true });
batch.set(postRef, {
createdBy: auth.currentUser,
createdAt: serverTimestamp(),
title: 'Some post',
content: 'post content here'
});
batch.commit();
}
Et enfin, nous pourrons trier les documents plus tard.
query(
collection(db, 'users'),
orderBy('postCount', 'desc')
);
Vous pouvez sécuriser cela dans les règles Firestore ou créer une fonction de déclenchement Firebase sur onCreate
qui incrémente et décrémente automatiquement le document utilisateur.
Les compteurs deviennent extrêmement compliqués dans Firestore. Voir Quatre façons de compter dans Firestore pour les cas de niche.
Unique et distinctif
La seule façon de conserver quelque chose d’unique est de le définir comme ID de document dans Firestore. Si vous souhaitez un champ unique, vous devez le mettre à deux endroits dans une transaction.
Noms d’utilisateur uniques
Pour plus d’unicité, nous pouvons enregistrer un nom d’utilisateur dans le users
collection sur le document utilisateur et dans un usernames
collecte en une seule transaction. Nous pouvons utiliser les règles Firestore pour sécuriser cela.
SQL
SELECT DISTINCT username
FROM users
ORDER BY username ASC;
Firestore
const batch = writeBatch();
batch.set(
doc(`usernames/${username}`),
{
uid,
username
}
);
batch.set(
doc(`users/${uid}`),
{
username
},
{ merge: true }
);
await batch.commit();
Si vous mettez à jour le nom d’utilisateur, envisagez également de supprimer l’ancien nom d’utilisateur dans le usernames
collecte en premier.
query(
collection(db, 'usernames'),
orderBy('usernames')
);
Pays distincts
Si vous souhaitez rechercher tous les pays auxquels appartiennent les utilisateurs, vous devez également stocker un identifiant unique. countries
collection.
SQL
SELECT country, COUNT(*) as user_count
FROM users
GROUP BY country
ORDER BY user_count DESC;
Firestore
const batch = writeBatch();
batch.set(
doc(`countries/${country}`),
{
uid,
country
userCount: increment(1)
}
);
batch.set(
doc(`users/${uid}`),
{
country
},
{ merge: true }
);
await batch.commit();
Vous pouvez également créer les pays à l’aide d’une fonction de déclenchement Firebase. Vous devriez également envisager de compter le nombre d’utilisateurs dans chaque pays. Cela peut s’avérer utile si vous souhaitez rechercher le pays le plus populaire auquel appartiennent vos utilisateurs.
query(
collection(db, 'countries'),
orderBy('userCount', 'desc')
);
🖋️ Le thème commun que vous commencez à voir est que Firestore vous oblige à créer votre base de données pour les requêtes au lieu de l’inverse.
Comme
Il n’y a pas LIKE
équivalent dans Firestore.
SQL
SELECT country
FROM users
WHERE country LIKE 'Uni%';
Cependant, il existe un startsWith
hack que vous pouvez utiliser.
Firestore
export const startsWith = (
fieldName: string,
term: string
) => {
return [
orderBy(fieldName),
startAt(term),
endAt(term + '~')
];
};
Cette fonction peut être utilisée pour rechercher une chaîne commençant par un terme. Cela ne fonctionne que dans les objets parents et ne fonctionne pas avec des tableaux ou des objets.
query(
collection(db, 'users'),
...startsWith('country', 'Uni')
);
Utilisez une base de données externe ou stockez votre index de recherche dans Firestore si vous le souhaitez Recherche floue capacités. Vous pouvez le faire avec Vecteurs ou une fonction personnalisée comme Soundex.
Jointures internes
Vous pouvez facilement effectuer une jointure interne dans Firestore en interrogeant deux collections différentes, mais cela vous coûtera une lecture de document supplémentaire et pourrait potentiellement ralentir votre application.
Plusieurs-à-Un
L’exemple de classe consiste à ajouter des informations utilisateur à une publication.
SQL
SELECT
p.id AS post_id,
p.title AS post_title,
p.content AS post_content,
jsonb_build_object(
'id', u.id,
'name', u.name,
'email', u.email
) AS created_by
FROM posts p
JOIN users u ON p.created_by = u.id;
Vous pourriez facilement le faire sans JSON, mais je voulais un objet équivalent.
Firestore
addDoc(
doc(db, 'posts', 'post-id'),
{
title: 'some title',
content: 'some content',
createdBy: {
uid: auth.currentUser.uid,
displayName: 'auth.currentUser.displayName,
username: 'get username from user document or custom claim'
}
}
});
Cela pourrait être fait de plusieurs manières, mais la question est simple.
getDoc(
doc(db, 'posts', 'post-id-here')
);
Encore une fois, vous devez tenir compte du moment où un utilisateur met à jour son username
, displayName
ou d’autres informations de profil. Vous devez créer une fonction de déclenchement Firebase pour le gérer.
Ici, nous avons messages et tags. Chaque publication peut avoir plusieurs balises, mais vous n’aurez pas un nombre infini de balises sur une publication. Firestore vous permet de stocker des données sous forme de tableaux et d’effectuer une recherche en fonction du tableau. Un tableau fonctionne parfaitement dans ce cas.
SQL
En SQL, nous pouvons avoir une table de balises avec le user_id
.
SELECT p.id AS post_id, p.title, p.content, t.tag
FROM posts p
LEFT JOIN tags t ON p.id = t.post_id
ORDER BY p.id;
Ou bien, nous pouvons avoir une table de jonction post_tag
.
SELECT p.id AS post_id, p.title, p.content, t.name AS tag_name
FROM posts p
JOIN post_tag pt ON p.id = pt.post_id
JOIN tags t ON pt.tag_id = t.id
ORDER BY p.id;
Firestore
Dans Firestore, nous devons créer les balises à deux endroits différents, similaires à l’index unique.
const batch = writeBatch(db);
const tags = ['tag1', 'tag2'];
// add tags array when adding post
batch.set(
doc(db, 'posts', 'some-post-id'), {
tags,
title: 'some title',
content: 'some content',
createdBy: auth.currentUser.uid,
createdAt: serverTimestamp()
});
// create a tag document
tags.forEach(tag => {
batch.set(doc(db, 'tags', tag), {
postCount: increment(1)
}, { merge: true });
});
await batch.commit();
🖋️ Il faut aussi penser à décrémenter le postCount
sur les balises lorsqu’une publication est modifiée ou supprimée. Cela devrait être fait avec les fonctions Firebase, car les règles Firestore deviendraient trop complexes pour plusieurs balises. Les règles Firestore n’ont pas non plus de boucles.
query(
collection(db, 'posts')
);
Les balises seront déjà attachées au document de publication.
Requêtes complexes
Firestore ne gère pas les requêtes complexes, mais il existe des solutions de contournement pour la plupart des cas d’utilisation. Cet article ne fait qu’effleurer la surface.
Documents vs tableaux
Une base de données NoSQL est très différente d’une base de données SQL. Vous devez réfléchir à vos requêtes avant même de créer un document. Une fois que vous avez appris les modèles, vous pouvez créer n’importe quelle structure.
🌐 Je reste fermement convaincu que toutes les données sont relationnelles !
Mais Firebase vous permet de créer une application plus rapidement que n’importe quelle plate-forme de base de données que j’ai utilisée. Firestore est incroyable à cet égard.
Source link