Comment bien utiliser Google Sheets en entreprise ?

Comment bien utiliser Google Sheets en entreprise ?

Structurer les onglets, modéliser les données comme un mini-entrepôt, automatiser avec Apps Script et garder une copie froide dans BigQuery : c’est la différence entre un tableur partagé et un système d’information qui tient trois ans. Voici la méthode, le code et les limites chiffrées.

Un Google Sheets devient un système d’information dès qu’une équipe s’appuie dessus pour décider, facturer ou piloter. À ce moment-là, il hérite des contraintes d’une base de données sans en avoir les garde-fous : pas de clé primaire, pas de typage, pas de transaction, pas d’historique.

La bonne approche n’est pas de fuir le tableur. C’est de l’utiliser pour ce qu’il fait mieux que tout le reste — la saisie guidée et la restitution — et de sortir les données dès qu’elles deviennent un actif. Trois couches : la feuille pour capturer, Apps Script pour automatiser, BigQuery pour conserver et interroger. C’est le socle des applications métier qu’on installe en PME, et la raison pour laquelle Apps Script mérite d’être appris sérieusement.

Pourquoi Google Sheets casse quand l’entreprise grandit ?

Parce qu’un tableur n’impose aucune contrainte. La limite technique n’est presque jamais le problème : Google a porté le plafond à 20 millions de cellules par classeur en septembre 2026, contre 10 millions depuis 2022. Ce qui casse, c’est la structure — ou son absence.

Les vrais plafonds, ceux qu’on rencontre en production :

ContrainteValeurCe qui se passe au-delà
Cellules par classeur20 M (sept. 2026)Le fichier ne s’ouvre plus
Taille du fichier100 MoImport Excel tronqué
Durée d’une exécution Apps Script6 minLe script est tué, à mi-parcours
Temps total des déclencheurs6 h/jour (Workspace)Les traitements de nuit sautent
Exécutions simultanées30 par utilisateurDeux scripts se marchent dessus
Extrait Connected Sheets500 000 lignes ou 5 M cellulesLa vue est partielle, silencieusement
Tableau croisé Connected Sheets200 000 lignesTCD incomplet

J’ai repris il y a deux ans un fichier de suivi de production : onze onglets, quatre personnes qui saisissaient dans le même tableau, aucune colonne d’identifiant. Résultat, trois mois de commandes en doublon et une réconciliation manuelle de deux semaines. Le problème n’était pas Sheets. Le problème, c’était d’avoir laissé un tableur jouer le rôle d’une base sans lui donner les règles d’une base.

Formez-vous à Google Sheets !

Nos formations Google Sheets vous accompagnent pour passer d’une utilisation basique à une vraie expertise en manipulation de données. Vous apprendrez à structurer, analyser et automatiser vos feuilles de calcul pour en faire des outils puissants et adaptés à vos besoins métiers. Fonctions avancées, requêtes dynamiques, visualisation pertinente.

Comment bien utiliser Google Sheets en entreprise ?
Un classeur, trois zones : la saisie ne lit jamais les calculs.

Comment structurer un Google Sheets fiable ?

Quatre règles, non négociables, qui font 80 % du travail. Elles coûtent une demi-journée à poser et épargnent des mois de nettoyage.

Une table par onglet, une seule ligne d’en-tête

L’en-tête va en ligne 1. Pas de titre fusionné au-dessus, pas de logo, pas de « Semaine 42 » : tout ce qui précède l’en-tête casse getValues(), les tableaux structurés, les filtres et les connecteurs BigQuery.

Même logique en bas : aucune ligne de total dans le tableau. Un total est une vue, pas un enregistrement. Dès qu’il est dans la plage, il est compté comme une donnée par tous les outils qui lisent la feuille.

Pas de cellules fusionnées, pas de lignes vides de séparation, pas de commentaire dans une colonne métier.

Nommez les onglets avec un préfixe de zone : SAI_ pour la saisie, CALC_ pour les calculs, DIF_ pour la diffusion, REF_ pour les référentiels. Le préfixe dit qui a le droit d’écrire.

Séparez saisie, calculs et diffusion

  • SAI_COMMANDES : colonnes brutes, saisie humaine, aucune formule. C’est la couche de capture. Elle doit rester sale mais valide.
  • CALC_MARGE : formules uniquement, jamais éditée à la main, protégée en écriture.
  • DIF_ : vues filtrées et agrégées, ou — mieux — un onglet Connected Sheets branché sur BigQuery. Le modèle est le même que pour un tableau de bord analytics dans Sheets : une source, une granularité, aucune saisie.

La règle qui évite 90 % des bugs : aucune formule ne remonte de la couche calcul vers la couche saisie. Un tableur se lit dans un sens.

Typez et verrouillez la zone de saisie

Une colonne, un type. Les statuts passent par une validation de données (liste fermée), les dates par un format date réel — pas du texte « 12/03 ». Un 12/03/2026 stocké en texte est invisible pour SAFE.PARSE_DATE et pour tous les tris.

Verrouillez ce qui ne doit pas bouger : Données > Protéger les feuilles et les plages. Les onglets CALC_, DIF_ et REF_ en écriture réservée à une seule personne. SAI_ reste ouvert, mais la structure — en-têtes, colonnes — est protégée.

Ajoutez trois colonnes techniques à chaque table de saisie : id (UUID généré à la création, jamais réutilisé), cree_le, cree_par. Elles coûtent trois colonnes et sauvent les jointures.

Nommez, documentez, restreignez l’accès

Créez des plages nommées pour tout ce qui est référentiel : REF_TARIFS, REF_CLIENTS. Les formules pointent sur ces noms, jamais sur 'Feuille 3'!$A$2:$B$47.

Un dossier Drive par domaine, des droits par groupe et non par personne, et un onglet README en première position qui dit : à quoi sert le fichier, qui écrit où, quelle est la granularité d’une ligne, et où sont les données historiques. Ce dernier point compte plus qu’il n’y paraît : sans lui, personne ne saura dans six mois que la vérité est dans BigQuery.

Comment modéliser les données internes dans un tableur ?

Comme un mini-entrepôt en étoile : des tables de faits qui enregistrent des événements, des tables de dimensions qui décrivent les objets. Ce n’est pas de la théorie de consultant, c’est ce qui rend les formules simples et les exports vers BigQuery triviaux.

Une ligne = un événement, pas un état

La question à se poser avant de créer la feuille : que représente une ligne ? Une commande ? Une ligne de commande ? Un appel ? Si vous ne savez pas répondre en une phrase, vous ne pourrez pas agréger.

Une commande avec trois articles, c’est une ligne dans FAI_COMMANDES et trois lignes dans FAI_LIGNES_COMMANDES. Le jour où le commercial ajoute un quatrième article, vous n’avez pas à ajouter une colonne article_4.

Le corollaire : jamais de tableau à double entrée (clients en lignes, mois en colonnes). C’est le format qui rend tout calcul impossible et tout export illisible.

Les clés avant les formules

id_client, id_produit, id_commande : des identifiants stables, en texte, jamais dépendants d’un libellé. Un client qui change de nom ne doit pas casser l’historique.

RECHERCHEV/XLOOKUP est une jointure déguisée, et une jointure silencieuse : quand la clé manque, elle renvoie #N/A et personne ne le voit. Utilisez-la pour l’affichage, pas pour la vérité. Dès qu’il y a plus de deux tables à joindre, la jointure se fait dans BigQuery, pas dans la feuille.

Le référentiel vit à un seul endroit

Un tarif, un taux de TVA, une grille de remise : une seule feuille REF_, protégée, alimentée par le métier. Toutes les autres formules la lisent par plage nommée.

La variante que je vois le plus souvent : trois copies du même tarif dans trois onglets, et une quatrième dans la boîte mail du contrôle de gestion. Le jour du changement de grille, deux copies sont mises à jour.

La couche calcul reste mince

Les colonnes calculées acceptent les opérations ligne à ligne : montant HT, marge, écart, délai. Elles n’acceptent pas les agrégations lourdes, ni les ARRAYFORMULA sur colonne entière, ni les QUERY() sur 50 000 lignes. Ces formules recalculent à chaque frappe et transforment un fichier de 4 000 lignes en fichier inutilisable sur mobile.

Tout ce qui ressemble à « combien par mois et par agence » appartient à BigQuery, ou au pire à un TCD figé.

Un dernier point, souvent oublié : une feuille est un fichier, et un fichier se partage par lien. Dès qu’elle contient des e-mails, des téléphones ou des IBAN, elle relève d’une politique d’accès aussi stricte qu’une base de données : droits par groupe et non par lien public, colonnes minimisées, et une durée de conservation écrite quelque part.

Comment automatiser une feuille avec Apps Script ?

En écrivant par lots, en verrouillant les écritures et en découpant le travail pour rester sous les six minutes. Apps Script n’est pas lent : c’est l’accès cellule par cellule qui est lent. Chaque appel à l’API Sheets coûte des dizaines de millisecondes ; mille appels font une minute.

Lisez et écrivez des tableaux, jamais cellule par cellule

Voici le squelette que j’utilise pour tout recalcul de masse. Une lecture, un calcul en mémoire, une écriture.

/**
 * Recalcule le montant net et la marge des commandes.
 * 1 lecture API, 1 écriture API, quel que soit le nombre de lignes.
 */
function recalculerMarge() {
  const ss = SpreadsheetApp.openById('ID_DU_CLASSEUR');
  const src = ss.getSheetByName('SAI_COMMANDES');
  const dst = ss.getSheetByName('CALC_MARGE');

  const derniereLigne = src.getLastRow();
  if (derniereLigne < 2) return;                       // en-tête seulement

  // getValues() renvoie un tableau 2D : une requête, tout en mémoire
  const donnees = src.getRange(2, 1, derniereLigne - 1, 8).getValues();

  const sortie = donnees.map((ligne) => {
    const [id, , , ca, cout, statut] = ligne;           // colonnes utiles
    const net = statut === 'ANNULEE' ? 0 : Number(ca) - Number(cout);
    const marge = net / (Number(ca) || 1);
    return [id, net, marge];
  });

  // setValues() en une fois, dimensionné exactement
  dst.getRange(2, 1, sortie.length, 3).setValues(sortie);
}

Ce qui tue les performances, dans l’ordre : appendRow() en boucle, getRange(i, 1).getValue() dans une boucle for, et setValue() par cellule. Les trois apparaissent systématiquement dans les scripts hérités.

Verrouillez pour éviter les collisions

Deux personnes qui cliquent sur le bouton d’export en même temps, c’est deux scripts qui écrivent dans la même plage. Apps Script n’a aucun mécanisme de transaction : la dernière écriture gagne, et vous récupérez un mélange.

function syncProtege() {
  const lock = LockService.getScriptLock();
  if (!lock.tryLock(30000)) {                    // 30 s d'attente maximum
    console.warn('Une exécution est déjà en cours.');
    return;
  }
  try {
    recalculerMarge();
  } finally {
    lock.releaseLock();                          // toujours, même en erreur
  }
}

Le finally n’est pas décoratif : un lock.releaseLock() oublié bloque tous les traitements suivants jusqu’à expiration du verrou.

Découpez le travail sous la limite des six minutes

Une exécution meurt à six minutes, sans préavis. Au-delà, elle est tuée au milieu d’une écriture — d’où les feuilles à moitié remplies.

La parade : un curseur stocké dans PropertiesService, des lots de quelques milliers de lignes, et un déclencheur qui reprend là où le précédent s’est arrêté.

const TAILLE_LOT = 2000;

/**
 * Traite la saisie par tranches.
 * Déclencheur horaire : Extensions > Apps Script > Déclencheurs.
 */
function traiterParLots() {
  const lock = LockService.getScriptLock();
  if (!lock.tryLock(10000)) return;

  try {
    const props = PropertiesService.getScriptProperties();
    const offset = Number(props.getProperty('offset') || 0);

    const feuille = SpreadsheetApp.openById('ID_DU_CLASSEUR')
      .getSheetByName('SAI_COMMANDES');
    const lignes = feuille.getDataRange().getValues().slice(1); // sans en-tête

    if (offset >= lignes.length) {
      props.deleteProperty('offset');            // tour terminé, on remet à zéro
      return;
    }

    const lot = lignes.slice(offset, offset + TAILLE_LOT);
    envoyerVersBigQuery(lot);                    // chargement en staging, MERGE ensuite

    props.setProperty('offset', String(offset + lot.length));
  } catch (e) {
    console.error(e);                            // visible dans « Exécutions »
    MailApp.sendEmail('data@exemple.fr', 'Échec traiterParLots', e.message);
  } finally {
    lock.releaseLock();
  }
}

Deux règles pour que ça tienne dans le temps : le traitement doit être idempotent — rejouer un lot ne doit pas créer de doublon, donc on charge dans une table de staging et on fusionne (MERGE) sur l’id — et l’erreur doit alerter quelqu’un. Un script qui échoue en silence pendant trois semaines, c’est une sauvegarde qui n’existe pas.

Comment bien utiliser Google Sheets en entreprise ?
De la feuille de saisie au snapshot BigQuery : cinq étapes, un seul sens.

Comment sauvegarder les données dans BigQuery ?

Trois mécanismes pour la sauvegarde, et un quatrième pour la restitution. Les confondre est l’erreur classique : on branche une table externe sur une feuille de 200 000 lignes et on s’étonne que chaque requête coûte cher et prenne dix secondes.

La table externe : BigQuery lit la feuille, sans copie

C’est le mécanisme le plus simple et le plus sous-utilisé. BigQuery sait lire un Google Sheets comme source externe, avec un format dédié et une plage explicite.

CREATE OR REPLACE EXTERNAL TABLE `mon-projet.raw.sheets_referentiel_tarifs`
OPTIONS (
  format = 'GOOGLE_SHEETS',
  uris = ['https://docs.google.com/spreadsheets/d/ID_DU_CLASSEUR/edit#gid=0'],
  sheet_range = 'REF_TARIFS!A1:F',
  skip_leading_rows = 1
);

Ce que ça donne : zéro duplication, une donnée toujours à jour, une jointure possible avec vos tables BigQuery. Ce que ça ne donne pas : de l’historique, de la performance, ni de la robustesse. La lecture passe par l’API Drive, donc elle est lente et dépend des droits du compte de service sur le fichier.

Mon usage : les référentiels qui changent souvent et les tables de correspondance de moins de quelques milliers de lignes. Jamais une table de faits.

Le chargement depuis Apps Script : la copie chaude

Pour les volumes qui grossissent, on pousse les lignes dans une table native. Le service avancé BigQuery expose l’API directement depuis Apps Script.

const PROJECT_ID = 'mon-projet';

function envoyerVersBigQuery(lignes) {
  // INSERT par paquets de 500 : au-delà, la requête devient énorme
  for (let i = 0; i < lignes.length; i += 500) {
    const paquet = lignes.slice(i, i + 500);
    const sql = `
      INSERT INTO `${PROJECT_ID}.staging.commandes_ingest`
        (id, date_commande, client_id, montant_ht, statut, charge_le)
      VALUES ${paquet.map(enTuple).join(',n')}`;

    const res = BigQuery.Jobs.query({ query: sql, useLegacySql: false }, PROJECT_ID);
    if (res.errors) throw new Error(JSON.stringify(res.errors));
  }
}

// Typage et échappement explicites : BigQuery ne devine rien
function enTuple(ligne) {
  const [id, date, clientId, montant, statut] = ligne;
  const d = Utilities.formatDate(new Date(date), 'Europe/Paris', 'yyyy-MM-dd');
  return `(${lit(id)}, DATE '${d}', ${lit(clientId)}, ${Number(montant)}, ${lit(statut)}, CURRENT_TIMESTAMP())`;
}

function lit(v) {
  if (v === null || v === '') return 'NULL';
  return `'${String(v).replace(/\/g, '\\').replace(/'/g, "\'")}'`;
}

Trois points qui font échouer ce code chez tout le monde : le service avancé doit être activé et rattaché à un projet Cloud (pas au projet par défaut), les apostrophes doivent être échappées sinon la requête casse sur « L’Atelier », et le type doit être forcé — une colonne montant qui contient une fois 1 250,00 en texte fait échouer tout le lot.

Et la limite honnête : Apps Script n’est pas un orchestrateur. Il n’a pas de reprise sur erreur, pas de parallélisme utile, pas de monitoring. Au-delà de quelques dizaines de milliers de lignes par jour, vous exportez en CSV vers Cloud Storage et vous lancez un load job — ou vous laissez un vrai outil faire le travail : un workflow n8n qui pilote BigQuery couvre la planification et les reprises, ce qu’Apps Script ne sait pas faire.

Le snapshot quotidien : l’historique que Sheets ne garde pas

Une feuille n’a pas de versioning exploitable. BigQuery, si. Une requête planifiée suffit à conserver une photo par jour.

-- Une fois : table d'historique partitionnée par jour
CREATE TABLE IF NOT EXISTS `mon-projet.raw.commandes_history`
PARTITION BY DATE(_snapshot_at)
AS
SELECT CURRENT_TIMESTAMP() AS _snapshot_at, *
FROM `mon-projet.staging.commandes_clean`
WHERE FALSE;

-- Puis, chaque nuit, en requête planifiée
INSERT INTO `mon-projet.raw.commandes_history`
SELECT CURRENT_TIMESTAMP() AS _snapshot_at, *
FROM `mon-projet.staging.commandes_clean`;

La vue de staging au milieu n’est pas un luxe. C’est elle qui absorbe les horreurs du tableur : dates en texte, montants en français, espaces insécables, lignes vides. Et elle reste courte — un peu de discipline d’écriture SQL suffit à la maintenir.

CREATE OR REPLACE VIEW `mon-projet.staging.commandes_clean` AS
SELECT
  TRIM(CAST(id AS STRING))                               AS id,
  SAFE.PARSE_DATE('%d/%m/%Y', TRIM(date_commande))       AS date_commande,
  TRIM(client_id)                                        AS client_id,
  SAFE_CAST(REPLACE(REPLACE(montant_ht, ' ', ''), ',', '.') AS NUMERIC) AS montant_ht,
  UPPER(TRIM(statut))                                    AS statut,
  charge_le
FROM `mon-projet.staging.commandes_ingest`
WHERE TRIM(CAST(id AS STRING)) != ''
QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY charge_le DESC) = 1;

Le SAFE_CAST renvoie NULL au lieu d’échouer : votre pipeline continue, et vous requêtez WHERE montant_ht IS NULL pour trouver les lignes sales. C’est exactement ce qu’on veut — une erreur de saisie ne doit pas arrêter la chaîne, elle doit être visible.

Le QUALIFY ROW_NUMBER() fait le travail que le tableur ne sait pas faire : une seule ligne par id, la dernière chargée gagne. Un lot rejoué après incident devient inoffensif.

La restitution : Connected Sheets, avec ses plafonds

Pour relire les données dans Sheets sans écrire une ligne de code, Connected Sheets connecte un onglet directement à BigQuery. C’est puissant et c’est borné : 500 000 lignes ou 5 millions de cellules par extrait, 200 000 lignes par tableau croisé dynamique. Au-delà, la vue est tronquée sans erreur. Le contrôle de gestion qui compare deux chiffres sans savoir que son extrait est partiel, c’est le scénario à éviter.

Règle simple : les extraits servent à lire, jamais à décider sur un total.

Faut-il garder Sheets ou passer à une vraie base ?

Gardez Sheets. Mais seulement pour ce qu’il fait mieux que tout le reste : la saisie guidée par des humains, et la restitution visuelle pour des humains. Tout le reste appartient à BigQuery. Et ce déplacement n’est pas qu’un choix technique : c’est aussi une compétence qui devient un critère de recrutement, comme le montre ce qui change en 2027 pour les ingénieurs data.

Le critère de bascule tient en trois lignes. Sortez les données du tableur dès qu’elles dépassent environ 100 000 lignes, dès qu’elles servent à produire un chiffre officiel, ou dès que deux services se mettent à en dépendre. En dessous, un Sheets bien structuré reste l’outil le plus rapide à mettre en place et le moins cher à maintenir.

La formule que j’applique : Sheets est une interface, pas un stockage. Une feuille propre, un Apps Script qui écrit par lots et qui alerte, une table BigQuery partitionnée derrière. Vous gardez le confort du tableur et vous obtenez la seule chose qu’il ne sait pas donner : un historique fiable.


À propos de l’auteur — Franck Scandolera, data engineer et développeur d’applications métier. Je conçois des architectures de données et des automatisations pour des PME et des agences : ingestion, modélisation, entrepôt BigQuery, workflows scriptés. Ce type de montage, je l’installe en quelques jours dans des organisations qui n’ont ni data team ni budget plateforme.

Sources

  1. Google Workspace Updates, 2026, Doubled cell limits in Google Sheets now generally available — https://workspaceupdates.googleblog.com/2026/09/doubled-cell-limits-in-google-sheets-now-generally-available.html
  2. Google Drive Help, 2026, Files you can store in Google Drive — https://support.google.com/drive/answer/37603
  3. Google for Developers, 2026, Quotas for Google Services — https://developers.google.com/apps-script/guides/services/quotas
  4. Google for Developers, 2026, BigQuery Service (Apps Script) — https://developers.google.com/apps-script/advanced/bigquery
  5. Google Cloud, 2026, Create Google Drive external tables — https://docs.cloud.google.com/bigquery/docs/external-data-drive
  6. Google Cloud, 2026, Use Connected Sheets — https://docs.cloud.google.com/bigquery/docs/connected-sheets
  7. Google Docs Editors Help, 2026, Analyze & refresh BigQuery data in Google Sheets using Connected Sheets — https://support.google.com/docs/answer/9703214

FAQ

  • Peut-on utiliser Google Sheets comme base de données ?
    Non, pas au-delà d’un usage de saisie. Un tableur n’a ni clé primaire, ni typage, ni transaction, ni historique fiable. Il peut servir de couche de capture devant une vraie base ; il ne peut pas être la base.
  • Combien de lignes maximum dans Google Sheets ?
    La limite officielle est de 20 millions de cellules par classeur depuis septembre 2026, pour un fichier de 100 Mo maximum. En pratique, la lisibilité et les formules s’effondrent bien avant : au-delà de 100 000 lignes, il faut sortir les données vers BigQuery.
  • Apps Script peut-il remplacer un ETL ?
    Pour de petits volumes, oui : lecture par lots, écriture dans BigQuery, requête planifiée pour l’historique. Au-delà de quelques dizaines de milliers de lignes par jour, non — pas de reprise sur erreur, pas de monitoring, et une limite de six minutes par exécution.
  • Comment sauvegarder automatiquement un Google Sheets dans BigQuery ?
    Deux options. Une table externe BigQuery pointant sur l’URL de la feuille, pour une lecture directe sans copie. Ou un Apps Script qui pousse les lignes par lots dans une table native, complété par une requête planifiée qui conserve un snapshot quotidien.
  • Faut-il choisir Connected Sheets ou un export complet ?
    Connected Sheets pour lire et filtrer sans code, en gardant en tête les plafonds : 500 000 lignes ou 5 millions de cellules par extrait, 200 000 lignes par tableau croisé. Pour un calcul officiel ou un historique, l’export vers une table BigQuery reste obligatoire.
Défiler vers le haut
Formations Analytics