[{"data":1,"prerenderedAt":812},["ShallowReactive",2],{"page-\u002Fsql\u002Fagregats-et-index":3,"pieces-sql":769,"autour-\u002Fsql\u002Fagregats-et-index":794,"prerequis-\u002Fsql\u002Fagregats-et-index":799,"plan-sql":801},{"id":4,"title":5,"body":6,"description":745,"extension":746,"ligne":71,"meta":747,"motsCles":748,"navigation":750,"niveau":751,"path":752,"prerequis":753,"ressources":755,"seo":763,"sources":764,"stem":767,"__hash__":768},"docs\u002Fsql\u002F05.agregats-et-index.md","Agrégats et index",{"type":7,"value":8,"toc":735},"minimark",[9,14,53,57,66,140,143,219,230,234,237,347,359,379,391,395,403,510,513,517,520,559,563,566,572,595,606,613,647,652,656,667,681,690,694,731],[10,11,13],"h2",{"id":12},"ce-que-tu-vas-apprendre","Ce que tu vas apprendre",[15,16,17,40,50],"ul",{},[18,19,20,21,25,26,25,29,25,32,35,36,39],"li",{},"Résumer plusieurs lignes en une valeur avec ",[22,23,24],"code",{},"COUNT",", ",[22,27,28],{},"SUM",[22,30,31],{},"AVG",[22,33,34],{},"MIN"," et ",[22,37,38],{},"MAX",".",[18,41,42,43,46,47,39],{},"Regrouper avec ",[22,44,45],{},"GROUP BY"," et filtrer les groupes avec ",[22,48,49],{},"HAVING",[18,51,52],{},"Savoir ce qu'est un index et quand en créer un.",[10,54,56],{"id":55},"les-fonctions-dagrégat","Les fonctions d'agrégat",[58,59,60,61,65],"p",{},"Une ",[62,63,64],"strong",{},"fonction d'agrégat"," prend plusieurs lignes et renvoie une seule valeur.",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-sql shiki shiki-themes github-light-default github-dark-default github-dark-default","SELECT COUNT(*) AS nb_recettes,\n       AVG(temps_minutes) AS duree_moyenne,\n       MAX(temps_minutes) AS plus_longue\nFROM recettes;\n","sql","",[22,74,75,104,118,131],{"__ignoreMap":72},[76,77,80,84,88,92,95,98,101],"span",{"class":78,"line":79},"line",1,[76,81,83],{"class":82},"sOnC1","SELECT",[76,85,87],{"class":86},"s5iOY"," COUNT",[76,89,91],{"class":90},"scfrF","(",[76,93,94],{"class":82},"*",[76,96,97],{"class":90},") ",[76,99,100],{"class":82},"AS",[76,102,103],{"class":90}," nb_recettes,\n",[76,105,107,110,113,115],{"class":78,"line":106},2,[76,108,109],{"class":86},"       AVG",[76,111,112],{"class":90},"(temps_minutes) ",[76,114,100],{"class":82},[76,116,117],{"class":90}," duree_moyenne,\n",[76,119,121,124,126,128],{"class":78,"line":120},3,[76,122,123],{"class":86},"       MAX",[76,125,112],{"class":90},[76,127,100],{"class":82},[76,129,130],{"class":90}," plus_longue\n",[76,132,134,137],{"class":78,"line":133},4,[76,135,136],{"class":82},"FROM",[76,138,139],{"class":90}," recettes;\n",[58,141,142],{},"Résultat : une seule ligne, avec le nombre de recettes, la durée moyenne et la durée maximale.",[144,145,146,159],"table",{},[147,148,149],"thead",{},[150,151,152,156],"tr",{},[153,154,155],"th",{},"Fonction",[153,157,158],{},"Calcule",[160,161,162,173,186,196,206],"tbody",{},[150,163,164,170],{},[165,166,167],"td",{},[22,168,169],{},"COUNT(*)",[165,171,172],{},"le nombre de lignes",[150,174,175,180],{},[165,176,177],{},[22,178,179],{},"COUNT(colonne)",[165,181,182,183],{},"le nombre de valeurs non ",[22,184,185],{},"NULL",[150,187,188,193],{},[165,189,190],{},[22,191,192],{},"SUM(colonne)",[165,194,195],{},"la somme",[150,197,198,203],{},[165,199,200],{},[22,201,202],{},"AVG(colonne)",[165,204,205],{},"la moyenne",[150,207,208,216],{},[165,209,210,25,213],{},[22,211,212],{},"MIN(colonne)",[22,214,215],{},"MAX(colonne)",[165,217,218],{},"le plus petit, le plus grand",[58,220,221,222,225,226,229],{},"Un ",[22,223,224],{},"WHERE"," s'applique avant le calcul : ",[22,227,228],{},"SELECT COUNT(*) FROM recettes WHERE temps_minutes \u003C 60;"," compte seulement les recettes rapides.",[10,231,233],{"id":232},"group-by-un-résultat-par-groupe","GROUP BY : un résultat par groupe",[58,235,236],{},"Combien de recettes par catégorie ?",[67,238,240],{"className":69,"code":239,"language":71,"meta":72,"style":72},"SELECT c.nom, COUNT(r.id) AS nb_recettes\nFROM categories AS c\nLEFT JOIN recettes AS r ON r.categorie_id = c.id\nGROUP BY c.nom\nORDER BY nb_recettes DESC;\n",[22,241,242,275,287,321,332],{"__ignoreMap":72},[76,243,244,246,249,251,254,256,258,260,263,265,268,270,272],{"class":78,"line":79},[76,245,83],{"class":82},[76,247,248],{"class":86}," c",[76,250,39],{"class":90},[76,252,253],{"class":86},"nom",[76,255,25],{"class":90},[76,257,24],{"class":86},[76,259,91],{"class":90},[76,261,262],{"class":86},"r",[76,264,39],{"class":90},[76,266,267],{"class":86},"id",[76,269,97],{"class":90},[76,271,100],{"class":82},[76,273,274],{"class":90}," nb_recettes\n",[76,276,277,279,282,284],{"class":78,"line":106},[76,278,136],{"class":82},[76,280,281],{"class":90}," categories ",[76,283,100],{"class":82},[76,285,286],{"class":90}," c\n",[76,288,289,292,295,297,300,303,306,308,311,314,316,318],{"class":78,"line":120},[76,290,291],{"class":82},"LEFT JOIN",[76,293,294],{"class":90}," recettes ",[76,296,100],{"class":82},[76,298,299],{"class":90}," r ",[76,301,302],{"class":82},"ON",[76,304,305],{"class":86}," r",[76,307,39],{"class":90},[76,309,310],{"class":86},"categorie_id",[76,312,313],{"class":82}," =",[76,315,248],{"class":86},[76,317,39],{"class":90},[76,319,320],{"class":86},"id\n",[76,322,323,325,327,329],{"class":78,"line":133},[76,324,45],{"class":82},[76,326,248],{"class":86},[76,328,39],{"class":90},[76,330,331],{"class":86},"nom\n",[76,333,335,338,341,344],{"class":78,"line":334},5,[76,336,337],{"class":82},"ORDER BY",[76,339,340],{"class":90}," nb_recettes ",[76,342,343],{"class":82},"DESC",[76,345,346],{"class":90},";\n",[58,348,349,351,352,355,356,358],{},[22,350,45],{}," forme un groupe de lignes par valeur de ",[22,353,354],{},"c.nom",", et l'agrégat se calcule dans chaque groupe. Le ",[22,357,291],{}," garde la catégorie « Boissons », avec un compte de 0.",[58,360,361,362,365,366,368,369,371,372,375,376,378],{},"Remarque le ",[22,363,364],{},"COUNT(r.id)"," plutôt que ",[22,367,169],{},". Pour une catégorie vide, la ligne existe grâce au ",[22,370,291],{},", mais ",[22,373,374],{},"r.id"," vaut ",[22,377,185],{}," et n'est pas compté.",[380,381,382],"note",{},[58,383,384,385,387,388,390],{},"En règle générale, toute colonne du ",[22,386,83],{}," qui n'est pas dans un agrégat doit apparaître dans le ",[22,389,45],{},". Sinon PostgreSQL refuse la requête.",[10,392,394],{"id":393},"having-filtrer-les-groupes","HAVING : filtrer les groupes",[58,396,397,399,400,402],{},[22,398,224],{}," filtre les lignes avant le regroupement. Pour filtrer sur le résultat d'un agrégat, il faut ",[22,401,49],{}," :",[67,404,406],{"className":69,"code":405,"language":71,"meta":72,"style":72},"SELECT c.nom, AVG(r.temps_minutes) AS duree_moyenne\nFROM categories AS c\nJOIN recettes AS r ON r.categorie_id = c.id\nGROUP BY c.nom\nHAVING AVG(r.temps_minutes) > 60;\n",[22,407,408,438,448,475,485],{"__ignoreMap":72},[76,409,410,412,414,416,418,420,422,424,426,428,431,433,435],{"class":78,"line":79},[76,411,83],{"class":82},[76,413,248],{"class":86},[76,415,39],{"class":90},[76,417,253],{"class":86},[76,419,25],{"class":90},[76,421,31],{"class":86},[76,423,91],{"class":90},[76,425,262],{"class":86},[76,427,39],{"class":90},[76,429,430],{"class":86},"temps_minutes",[76,432,97],{"class":90},[76,434,100],{"class":82},[76,436,437],{"class":90}," duree_moyenne\n",[76,439,440,442,444,446],{"class":78,"line":106},[76,441,136],{"class":82},[76,443,281],{"class":90},[76,445,100],{"class":82},[76,447,286],{"class":90},[76,449,450,453,455,457,459,461,463,465,467,469,471,473],{"class":78,"line":120},[76,451,452],{"class":82},"JOIN",[76,454,294],{"class":90},[76,456,100],{"class":82},[76,458,299],{"class":90},[76,460,302],{"class":82},[76,462,305],{"class":86},[76,464,39],{"class":90},[76,466,310],{"class":86},[76,468,313],{"class":82},[76,470,248],{"class":86},[76,472,39],{"class":90},[76,474,320],{"class":86},[76,476,477,479,481,483],{"class":78,"line":133},[76,478,45],{"class":82},[76,480,248],{"class":86},[76,482,39],{"class":90},[76,484,331],{"class":86},[76,486,487,489,492,494,496,498,500,502,505,508],{"class":78,"line":334},[76,488,49],{"class":82},[76,490,491],{"class":86}," AVG",[76,493,91],{"class":90},[76,495,262],{"class":86},[76,497,39],{"class":90},[76,499,430],{"class":86},[76,501,97],{"class":90},[76,503,504],{"class":82},">",[76,506,507],{"class":86}," 60",[76,509,346],{"class":90},[58,511,512],{},"Seules les catégories dont la durée moyenne dépasse 60 minutes restent.",[10,514,516],{"id":515},"lordre-dune-requête","L'ordre d'une requête",[58,518,519],{},"Les clauses s'écrivent toujours dans cet ordre :",[521,522,523,528,535,540,545,550],"ol",{},[18,524,525,527],{},[22,526,83],{}," : les colonnes et calculs ;",[18,529,530,35,532,534],{},[22,531,136],{},[22,533,452],{}," : les tables ;",[18,536,537,539],{},[22,538,224],{}," : le filtre sur les lignes ;",[18,541,542,544],{},[22,543,45],{}," : le regroupement ;",[18,546,547,549],{},[22,548,49],{}," : le filtre sur les groupes ;",[18,551,552,554,555,558],{},[22,553,337],{}," puis ",[22,556,557],{},"LIMIT"," : le tri et la coupe.",[10,560,562],{"id":561},"les-index","Les index",[58,564,565],{},"Pour trouver une recette par son titre, PostgreSQL lit par défaut toute la table, ligne après ligne. Avec 20 recettes, c'est instantané. Avec des millions, c'est lent.",[58,567,221,568,571],{},[62,569,570],{},"index"," est une structure annexe, comme l'index à la fin d'un livre. Elle permet de retrouver les lignes sans tout lire.",[67,573,575],{"className":69,"code":574,"language":71,"meta":72,"style":72},"CREATE INDEX idx_recettes_titre ON recettes (titre);\n",[22,576,577],{"__ignoreMap":72},[76,578,579,582,585,589,592],{"class":78,"line":79},[76,580,581],{"class":82},"CREATE",[76,583,584],{"class":82}," INDEX",[76,586,588],{"class":587},"syCW7"," idx_recettes_titre",[76,590,591],{"class":82}," ON",[76,593,594],{"class":90}," recettes (titre);\n",[58,596,597,598,601,602,605],{},"Cette commande crée un index sur la colonne ",[22,599,600],{},"titre",". Les requêtes avec ",[22,603,604],{},"WHERE titre = '...'"," peuvent alors l'utiliser. C'est PostgreSQL qui décide, au cas par cas, s'il l'emploie.",[58,607,608,609,612],{},"Pour le vérifier, place ",[22,610,611],{},"EXPLAIN"," devant la requête :",[67,614,616],{"className":69,"code":615,"language":71,"meta":72,"style":72},"EXPLAIN SELECT * FROM recettes WHERE titre = 'Gratin dauphinois';\n",[22,617,618],{"__ignoreMap":72},[76,619,620,623,625,628,631,633,635,638,641,645],{"class":78,"line":79},[76,621,622],{"class":90},"EXPLAIN ",[76,624,83],{"class":82},[76,626,627],{"class":82}," *",[76,629,630],{"class":82}," FROM",[76,632,294],{"class":90},[76,634,224],{"class":82},[76,636,637],{"class":90}," titre ",[76,639,640],{"class":82},"=",[76,642,644],{"class":643},"shWOn"," 'Gratin dauphinois'",[76,646,346],{"class":90},[58,648,649,651],{},[22,650,611],{}," affiche le plan d'exécution choisi, sans lancer la requête.",[10,653,655],{"id":654},"quand-créer-un-index","Quand créer un index ?",[58,657,658,659,662,663,666],{},"Une clé primaire ou une contrainte ",[22,660,661],{},"UNIQUE"," crée déjà un index automatiquement. En revanche, une clé étrangère comme ",[22,664,665],{},"recettes.categorie_id"," n'en reçoit pas : si tu joins souvent sur cette colonne, ajoute-le toi-même.",[58,668,669,670,25,673,676,677,680],{},"Un index a un prix : il occupe de la place, et chaque ",[22,671,672],{},"INSERT",[22,674,675],{},"UPDATE"," ou ",[22,678,679],{},"DELETE"," doit aussi le mettre à jour.",[682,683,684],"tip",{},[58,685,686,687,689],{},"N'ajoute pas d'index « au cas où ». Crée-le quand une requête fréquente est lente, et vérifie avec ",[22,688,611],{}," qu'il sert.",[10,691,693],{"id":692},"à-retenir","À retenir",[15,695,696,709,714,722,725],{},[18,697,698,25,700,25,702,25,704,35,706,708],{},[22,699,24],{},[22,701,28],{},[22,703,31],{},[22,705,34],{},[22,707,38],{}," résument plusieurs lignes en une valeur.",[18,710,711,713],{},[22,712,45],{}," calcule un agrégat par groupe ; chaque colonne non agrégée doit y figurer.",[18,715,716,718,719,721],{},[22,717,224],{}," filtre les lignes, ",[22,720,49],{}," filtre les groupes.",[18,723,724],{},"Un index accélère les recherches sur une colonne, au prix d'un peu de place et d'écritures plus lentes.",[18,726,727,728,730],{},"Vérifie l'usage d'un index avec ",[22,729,611],{}," avant de multiplier les index.",[732,733,734],"style",{},"html pre.shiki code .sOnC1, html code.shiki .sOnC1{--shiki-light:#CF222E;--shiki-default:#FF7B72;--shiki-dark:#FF7B72}html pre.shiki code .s5iOY, html code.shiki .s5iOY{--shiki-light:#0550AE;--shiki-default:#79C0FF;--shiki-dark:#79C0FF}html pre.shiki code .scfrF, html code.shiki .scfrF{--shiki-light:#1F2328;--shiki-default:#E6EDF3;--shiki-dark:#E6EDF3}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .syCW7, html code.shiki .syCW7{--shiki-light:#8250DF;--shiki-default:#D2A8FF;--shiki-dark:#D2A8FF}html pre.shiki code .shWOn, html code.shiki .shWOn{--shiki-light:#0A3069;--shiki-default:#A5D6FF;--shiki-dark:#A5D6FF}",{"title":72,"searchDepth":106,"depth":120,"links":736},[737,738,739,740,741,742,743,744],{"id":12,"depth":106,"text":13},{"id":55,"depth":106,"text":56},{"id":232,"depth":106,"text":233},{"id":393,"depth":106,"text":394},{"id":515,"depth":106,"text":516},{"id":561,"depth":106,"text":562},{"id":654,"depth":106,"text":655},{"id":692,"depth":106,"text":693},"Calculer des totaux et des moyennes avec COUNT, SUM, AVG, GROUP BY et HAVING, puis accélérer les recherches avec un index.","md",{},[24,28,31,45,49,570,749,611],"CREATE INDEX",true,"intermediaire","\u002Fsql\u002Fagregats-et-index",[754],"\u002Fsql\u002Fjointures",[756,760],{"nom":757,"url":758,"type":759},"Documentation PostgreSQL en français, « Fonctions d'agrégat »","https:\u002F\u002Fdocs.postgresql.fr\u002Fcurrent\u002Ftutorial-agg.html","doc",{"nom":761,"url":762,"type":759},"Documentation PostgreSQL en français, chapitre « Index »","https:\u002F\u002Fdocs.postgresql.fr\u002Fcurrent\u002Findexes.html",{"title":5,"description":745},[765,766],{"nom":757,"url":758},{"nom":761,"url":762},"sql\u002F05.agregats-et-index","n7kInbkNpTvpgy9zR2mtt_MkLPzjxNFC9vaDbONrwQQ",[770,774,778,782,785,786,790],{"path":771,"title":772,"stem":773},"\u002Fsql\u002Fbase-relationnelle","La base relationnelle et SELECT","sql\u002F01.base-relationnelle",{"path":775,"title":776,"stem":777},"\u002Fsql\u002Fmodifier-les-donnees","Ajouter, modifier et supprimer des lignes","sql\u002F02.modifier-les-donnees",{"path":779,"title":780,"stem":781},"\u002Fsql\u002Fcreer-des-tables","Créer des tables","sql\u002F03.creer-des-tables",{"path":754,"title":783,"stem":784},"Les jointures","sql\u002F04.jointures",{"path":752,"title":5,"stem":767},{"path":787,"title":788,"stem":789},"\u002Fsql\u002Ftransactions-et-injection","Transactions et injection SQL","sql\u002F06.transactions-et-injection",{"path":791,"title":792,"stem":793},"\u002Fsql","SQL","sql\u002Findex",[795,797],{"title":783,"path":754,"stem":784,"description":796,"children":-1},"Combiner plusieurs tables dans une seule requête avec INNER JOIN et LEFT JOIN, et utiliser des alias pour garder des requêtes lisibles.",{"title":788,"path":787,"stem":789,"description":798,"children":-1},"Regrouper des modifications en un bloc tout ou rien avec les transactions, et protéger une application contre l'injection SQL grâce aux requêtes paramétrées.",[800],{"path":754,"title":783,"ligne":71},[802,804,805,806,807,808,810],{"path":771,"title":772,"niveau":803,"stem":773},"debutant",{"path":775,"title":776,"niveau":803,"stem":777},{"path":779,"title":780,"niveau":803,"stem":781},{"path":754,"title":783,"niveau":751,"stem":784},{"path":752,"title":5,"niveau":751,"stem":767},{"path":787,"title":788,"niveau":809,"stem":789},"avance",{"path":791,"title":792,"niveau":811,"stem":793},"memo",1794583463325]