[{"data":1,"prerenderedAt":478},["ShallowReactive",2],{"\u002Fnext\u002Fdodock\u002Fdeveloppeurs\u002Fapi\u002Fquery-builder\u002Fcte-recursives":3,"section:\u002Fnext\u002Fdodock\u002Fdeveloppeurs\u002Fapi\u002Fquery-builder\u002Fcte-recursives":477},{"id":4,"title":5,"body":6,"description":463,"extension":464,"meta":465,"navigation":137,"path":473,"seo":474,"stem":475,"__hash__":476},"docs\u002F2.next\u002F3.dodock\u002F10.developpeurs\u002F1.api\u002F5.query-builder\u002F5.cte-recursives.md","Requêtes récursives avec WITH RECURSIVE",{"type":7,"value":8,"toc":456},"minimark",[9,13,22,46,51,62,72,82,86,90,109,264,268,283,411,415,430,434,452],[10,11,5],"h1",{"id":12},"requêtes-récursives-avec-with-recursive",[14,15,16,17,21],"p",{},"Une CTE récursive (",[18,19,20],"em",{},"Common Table Expression",") parcourt une arborescence en une seule requête SQL. Utilisez-la pour lire tous les niveaux d'une hiérarchie : sous-tâches, nomenclatures à plusieurs niveaux, comptes d'un plan comptable.",[14,23,24,25,29,30,33,34,37,38,41,42,45],{},"Dans la version next de Dodock, ",[26,27,28],"code",{},"frappe.qb.with_()"," accepte l'argument ",[26,31,32],{},"recursive=True",". Dodock produit alors ",[26,35,36],{},"WITH RECURSIVE"," sur MariaDB (10.2 et plus), PostgreSQL et SQLite. Sans cet argument, ",[26,39,40],{},"with_()"," produit un ",[26,43,44],{},"WITH"," simple. Cet argument n'existe pas en v5.",[47,48,50],"h2",{"id":49},"principe","Principe",[14,52,53,54,57,58,61],{},"Une CTE récursive a deux parties, réunies par ",[26,55,56],{},"+"," (",[26,59,60],{},"UNION",") :",[63,64,65,69],"ol",{},[66,67,68],"li",{},"La partie initiale sélectionne les lignes de départ.",[66,70,71],{},"La partie récursive joint la table à la CTE elle-même, pour ajouter le niveau suivant.",[14,73,74,75,78,79,81],{},"La base répète la partie récursive tant qu'elle trouve de nouvelles lignes. Dans la partie récursive, désignez la CTE avec ",[26,76,77],{},"AliasedQuery(\"\u003Cnom>\")",", le même nom que dans ",[26,80,40],{},".",[83,84,85],"mermaid",{},"\ngraph TD\n    A[Partie initiale : lignes de départ] --> B[Partie récursive : enfants du niveau précédent]\n    B --> C[Niveau suivant]\n    C --> D[Arrêt quand aucune nouvelle ligne]\n",[47,87,89],{"id":88},"exemple-toutes-les-sous-tâches-dune-tâche","Exemple : toutes les sous-tâches d'une tâche",[14,91,92,93,96,97,57,101,104,105,108],{},"Le champ ",[26,94,95],{},"parent_task"," de ",[98,99,100],"strong",{},"Tâche",[26,102,103],{},"Task",") pointe vers la tâche parente. Cette requête renvoie la tâche ",[26,106,107],{},"TASK-2026-00001"," et toutes ses sous-tâches, à tous les niveaux. Le nom de la tâche est fictif.",[110,111,116],"pre",{"className":112,"code":113,"language":114,"meta":115,"style":115},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import frappe\nfrom pypika import AliasedQuery\n\nTask = frappe.qb.DocType(\"Task\")\narbre = AliasedQuery(\"arbre\")\n\ndepart = (\n    frappe.qb.from_(Task)\n    .select(Task.name, Task.subject, Task.parent_task)\n    .where(Task.name == \"TASK-2026-00001\")\n)\n\nenfants = (\n    frappe.qb.from_(Task)\n    .join(arbre)\n    .on(Task.parent_task == arbre.name)\n    .select(Task.name, Task.subject, Task.parent_task)\n)\n\ntaches = (\n    frappe.qb.with_(depart + enfants, \"arbre\", recursive=True)\n    .from_(arbre)\n    .select(arbre.name, arbre.subject, arbre.parent_task)\n    .run(as_dict=True)\n)\n","python","",[26,117,118,126,132,139,145,151,156,162,168,174,180,186,191,197,202,208,214,219,224,229,235,241,247,253,259],{"__ignoreMap":115},[119,120,123],"span",{"class":121,"line":122},"line",1,[119,124,125],{},"import frappe\n",[119,127,129],{"class":121,"line":128},2,[119,130,131],{},"from pypika import AliasedQuery\n",[119,133,135],{"class":121,"line":134},3,[119,136,138],{"emptyLinePlaceholder":137},true,"\n",[119,140,142],{"class":121,"line":141},4,[119,143,144],{},"Task = frappe.qb.DocType(\"Task\")\n",[119,146,148],{"class":121,"line":147},5,[119,149,150],{},"arbre = AliasedQuery(\"arbre\")\n",[119,152,154],{"class":121,"line":153},6,[119,155,138],{"emptyLinePlaceholder":137},[119,157,159],{"class":121,"line":158},7,[119,160,161],{},"depart = (\n",[119,163,165],{"class":121,"line":164},8,[119,166,167],{},"    frappe.qb.from_(Task)\n",[119,169,171],{"class":121,"line":170},9,[119,172,173],{},"    .select(Task.name, Task.subject, Task.parent_task)\n",[119,175,177],{"class":121,"line":176},10,[119,178,179],{},"    .where(Task.name == \"TASK-2026-00001\")\n",[119,181,183],{"class":121,"line":182},11,[119,184,185],{},")\n",[119,187,189],{"class":121,"line":188},12,[119,190,138],{"emptyLinePlaceholder":137},[119,192,194],{"class":121,"line":193},13,[119,195,196],{},"enfants = (\n",[119,198,200],{"class":121,"line":199},14,[119,201,167],{},[119,203,205],{"class":121,"line":204},15,[119,206,207],{},"    .join(arbre)\n",[119,209,211],{"class":121,"line":210},16,[119,212,213],{},"    .on(Task.parent_task == arbre.name)\n",[119,215,217],{"class":121,"line":216},17,[119,218,173],{},[119,220,222],{"class":121,"line":221},18,[119,223,185],{},[119,225,227],{"class":121,"line":226},19,[119,228,138],{"emptyLinePlaceholder":137},[119,230,232],{"class":121,"line":231},20,[119,233,234],{},"taches = (\n",[119,236,238],{"class":121,"line":237},21,[119,239,240],{},"    frappe.qb.with_(depart + enfants, \"arbre\", recursive=True)\n",[119,242,244],{"class":121,"line":243},22,[119,245,246],{},"    .from_(arbre)\n",[119,248,250],{"class":121,"line":249},23,[119,251,252],{},"    .select(arbre.name, arbre.subject, arbre.parent_task)\n",[119,254,256],{"class":121,"line":255},24,[119,257,258],{},"    .run(as_dict=True)\n",[119,260,262],{"class":121,"line":261},25,[119,263,185],{},[47,265,267],{"id":266},"exemple-composants-dune-nomenclature-à-plusieurs-niveaux","Exemple : composants d'une nomenclature à plusieurs niveaux",[14,269,270,271,274,275,278,279,282],{},"Une ligne de nomenclature (",[98,272,273],{},"Article de nomenclature",", ",[26,276,277],{},"BOM Item",") peut porter sa propre nomenclature dans le champ ",[26,280,281],{},"bom_no",". Cette requête suit ces liens et renvoie chaque composant avec son niveau. Le nom de la nomenclature est fictif.",[110,284,286],{"className":112,"code":285,"language":114,"meta":115,"style":115},"import frappe\nfrom pypika import AliasedQuery\n\nBOMItem = frappe.qb.DocType(\"BOM Item\")\ncomposants = AliasedQuery(\"composants\")\n\ndepart = (\n    frappe.qb.from_(BOMItem)\n    .select(BOMItem.parent, BOMItem.item_code, BOMItem.qty, BOMItem.bom_no)\n    .where(BOMItem.parent == \"BOM-CHAISE-001\")\n    .where(BOMItem.parenttype == \"BOM\")\n)\n\nsous_composants = (\n    frappe.qb.from_(BOMItem)\n    .join(composants)\n    .on(BOMItem.parent == composants.bom_no)\n    .select(BOMItem.parent, BOMItem.item_code, BOMItem.qty, BOMItem.bom_no)\n    .where(BOMItem.parenttype == \"BOM\")\n)\n\nlignes = (\n    frappe.qb.with_(depart + sous_composants, \"composants\", recursive=True)\n    .from_(composants)\n    .select(composants.parent, composants.item_code, composants.qty)\n    .run(as_dict=True)\n)\n",[26,287,288,292,296,300,305,310,314,318,323,328,333,338,342,346,351,355,360,365,369,373,377,381,386,391,396,401,406],{"__ignoreMap":115},[119,289,290],{"class":121,"line":122},[119,291,125],{},[119,293,294],{"class":121,"line":128},[119,295,131],{},[119,297,298],{"class":121,"line":134},[119,299,138],{"emptyLinePlaceholder":137},[119,301,302],{"class":121,"line":141},[119,303,304],{},"BOMItem = frappe.qb.DocType(\"BOM Item\")\n",[119,306,307],{"class":121,"line":147},[119,308,309],{},"composants = AliasedQuery(\"composants\")\n",[119,311,312],{"class":121,"line":153},[119,313,138],{"emptyLinePlaceholder":137},[119,315,316],{"class":121,"line":158},[119,317,161],{},[119,319,320],{"class":121,"line":164},[119,321,322],{},"    frappe.qb.from_(BOMItem)\n",[119,324,325],{"class":121,"line":170},[119,326,327],{},"    .select(BOMItem.parent, BOMItem.item_code, BOMItem.qty, BOMItem.bom_no)\n",[119,329,330],{"class":121,"line":176},[119,331,332],{},"    .where(BOMItem.parent == \"BOM-CHAISE-001\")\n",[119,334,335],{"class":121,"line":182},[119,336,337],{},"    .where(BOMItem.parenttype == \"BOM\")\n",[119,339,340],{"class":121,"line":188},[119,341,185],{},[119,343,344],{"class":121,"line":193},[119,345,138],{"emptyLinePlaceholder":137},[119,347,348],{"class":121,"line":199},[119,349,350],{},"sous_composants = (\n",[119,352,353],{"class":121,"line":204},[119,354,322],{},[119,356,357],{"class":121,"line":210},[119,358,359],{},"    .join(composants)\n",[119,361,362],{"class":121,"line":216},[119,363,364],{},"    .on(BOMItem.parent == composants.bom_no)\n",[119,366,367],{"class":121,"line":221},[119,368,327],{},[119,370,371],{"class":121,"line":226},[119,372,337],{},[119,374,375],{"class":121,"line":231},[119,376,185],{},[119,378,379],{"class":121,"line":237},[119,380,138],{"emptyLinePlaceholder":137},[119,382,383],{"class":121,"line":243},[119,384,385],{},"lignes = (\n",[119,387,388],{"class":121,"line":249},[119,389,390],{},"    frappe.qb.with_(depart + sous_composants, \"composants\", recursive=True)\n",[119,392,393],{"class":121,"line":255},[119,394,395],{},"    .from_(composants)\n",[119,397,398],{"class":121,"line":261},[119,399,400],{},"    .select(composants.parent, composants.item_code, composants.qty)\n",[119,402,404],{"class":121,"line":403},26,[119,405,258],{},[119,407,409],{"class":121,"line":408},27,[119,410,185],{},[47,412,414],{"id":413},"bonnes-pratiques","Bonnes pratiques",[416,417,418,421,424],"ul",{},[66,419,420],{},"Évitez les boucles. Si les données peuvent former un cycle (A parent de B, B parent de A), la requête ne s'arrête pas. Ajoutez une colonne de profondeur et une condition qui la limite dans la partie récursive.",[66,422,423],{},"Les deux parties doivent renvoyer le même nombre de colonnes, dans le même ordre.",[66,425,426,427,81],{},"Ces requêtes ne contrôlent pas les autorisations de l'utilisateur. Contrôlez-les avant d'exposer le résultat, par exemple avec ",[26,428,429],{},"frappe.has_permission",[47,431,433],{"id":432},"ressources","Ressources",[416,435,436,445],{},[66,437,438],{},[439,440,444],"a",{"href":441,"rel":442},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fqueries-with.html",[443],"nofollow","CTE récursives dans PostgreSQL",[66,446,447],{},[439,448,451],{"href":449,"rel":450},"https:\u002F\u002Fmariadb.com\u002Fkb\u002Fen\u002Frecursive-common-table-expressions-overview\u002F",[443],"CTE récursives dans MariaDB",[453,454,455],"style",{},"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);}",{"title":115,"searchDepth":122,"depth":128,"links":457},[458,459,460,461,462],{"id":49,"depth":128,"text":50},{"id":88,"depth":128,"text":89},{"id":266,"depth":128,"text":267},{"id":413,"depth":128,"text":414},{"id":432,"depth":128,"text":433},"Utiliser les CTE récursives dans frappe.qb pour parcourir des structures arborescentes comme les nomenclatures ou les dépendances de tâches","md",{"icon":466,"reviewed":137,"last_verified":467,"sources":468},"i-mdi-tree-outline","2026-11-03",[469,470,471,472],"dodock@develop:frappe\u002Fquery_builder\u002Fbuilder.py","dodock@develop:frappe\u002Ftests\u002Ftest_query_builder.py","dokos@develop:erpnext\u002Fprojects\u002Fdoctype\u002Ftask\u002Ftask.json","dokos@develop:erpnext\u002Fmanufacturing\u002Fdoctype\u002Fbom_item\u002Fbom_item.json","\u002Fnext\u002Fdodock\u002Fdeveloppeurs\u002Fapi\u002Fquery-builder\u002Fcte-recursives",{"title":5,"description":463},"2.next\u002F3.dodock\u002F10.developpeurs\u002F1.api\u002F5.query-builder\u002F5.cte-recursives","fO1kjyXkCb094Kme54pD2QU8xXUC7-ALVHG7yPh6Eko",{},1793977365513]