Ceci est une page optimisée pour les mobiles. Cliquez sur ce texte pour afficher la vraie page.

POWERQUERY :: Combinaison de dossiers multiples non adjacents (non hiérarchiques) - Tables - Feuilles - Puis filtrage à l'issue de la combinaison

Boostez vos compétences Excel avec notre communauté !

Rejoignez Excel Downloads, le rendez-vous des passionnés où l'entraide fait la force. Apprenez, échangez, progressez – et tout ça gratuitement ! 👉 Inscrivez-vous maintenant !

oguruma

XLDnaute Impliqué
Bonjour Le Forum,
Ce post boucle la boucle à celui-ci.

POWERQUERY :: Combinaison de dossiers multiples non adjacents (non hiérarchiques) - Tables - Feuilles​

A l'issue de la combinaison des différents dossiers il peut être intéressant et nécessaire de procéder à un filtrage des données à traiter.

Pour cela on complète le paramétrage comme suit avec l'ajout de deux paramètres :
APPLIQUER_FILTRES : qui va actionner la fonction chargée de filtrer les données
TABLE_FILTRES : Table comportant la description des filtres


La table des filtres se présente comme suit :

Les opérateurs sont


Remarque : la liste est composée de cette manière
Egal|=;Différent|<>;Commence par|Text.StartsWith;Termine par|Text.EndsWith;Contient|Text.Contains

La fonction va récupérer les opérateurs avec ce contenu composé de deux parties : opérateur syntaxe française, séparateur "|", opérateur syntaxe PowerQuery
Cette chaine est découpée en deux pour n'en retenir que l'opérateur syntaxe PowerQuery
Egal|=
Commence par|Text.StartsWith

Là encore le nom des colonnes de la table de filtres n'a aucune importance. Il faut juste respecter l'ordre des colonnes. On travaille sur leur position relative.

ATTENTION : Pour des raisons de simplicité le filtrage ne sera possible que sur des champs de type texte. Une prochaine version devrait traiter tous types de colonnes.

ENVIRONNEMENT POWERQUERY


RQ_IMPORT_DONNEES_01 : Requête qui a permis de construire en mode pas à pas la fonction fnImportMultipleFoldersFilters. En effet avant de rédiger le code d'une fonction il est intéressant de l'écrire en mode requête. Cela rend son débuggage plus facile. TB_FINALE c'est la requête résultat de la requête .RQ_IMPORT_DONNEES_01

RQ_IMPORT_DONNEES_03
: Requête appelant la fonction fnImportMultipleFoldersFilters
Voici son code

PowerQuery:
let

    //******************************************************************************
    // Identification des paramètres
    //******************************************************************************
    STR_TYPE=fnGetParameter("TB_PARAMS", "TYPE"),
    STR_PROMOTE=Text.Upper(fnGetParameter("TB_PARAMS", "PROMOTE")),
    STR_TBL_COMBINE=fnGetParameter("TB_PARAMS", "TABLE_LIST_COMBINE"),
    STR_TAG_PREM=fnGetParameter("TB_PARAMS", "TAG_PREMIERE_COLONNE"),  
    STR_TAG_COL_DOSSIER=fnGetParameter("TB_PARAMS", "TAG_COL_DOSSIER"),
    STR_AJOUT_DU_CHEMIN_COMPLET=fnGetParameter("TB_PARAMS", "AJOUT_DU_CHEMIN_COMPLET"),
    STR_APPLIQUER_FILTRES=fnGetParameter("TB_PARAMS", "APPLIQUER_FILTRES"),
    STR_TABLE_FILERS=Text.Upper(fnGetParameter("TB_PARAMS", "TABLE_FILTRES")),
 
    TBL=fnImportMultipleFoldersFilters(STR_TYPE, STR_PROMOTE, STR_TBL_COMBINE, STR_TAG_PREM, STR_TAG_COL_DOSSIER, STR_AJOUT_DU_CHEMIN_COMPLET, STR_APPLIQUER_FILTRES, STR_TABLE_FILERS)

in
   TBL

Et enfin le code de la fonction fnImportMultipleFoldersFilters
PowerQuery:
let fnImportMultipleFoldersFilters = (
    pType           as text,
    pPromote        as text,
    pTableCombine   as text,
    pTag            as text,
    pFolder         as text,
    pPath           as text,
    pFilter         as text,
    pTableFilter    as text
        ) =>
    let

        //******************************************************************************
        //                      FONCTIONS SECTION
        //******************************************************************************


        //******************************************************************************
        // Combinaison des dossiers
        //******************************************************************************
        COMBINE_FOLDERS = () as any =>
            // Dans cette fonction on balayer la liste des dossiers à importer contenus dans TBL_COMBINE_FOLDERS_TO_RECORDS
                let
                    //***********************************************************************************************
                    // On va construire la liste des fichiers à combiner
                    //***********************************************************************************************
                    ListAccumlateCombineFolders = List.Accumulate(
                        LST_COMBINE_FOLDERS_TO_RECORDS,
                        {}, //#table({},{}),
                        (state, current) =>
                                let
                                    Folder=Record.Field(current,LIST_COLUMNS_TBL_COMBINE{0}),  // On récupère le dossier en cours de lecture
                                    // Si le nom du dossier n'est pas renseigné on passe au suivant
                                    ListCombineFolders=if Folder <> null
                                                    then
                                                            let
                                                                TblSourceFolder=Folder.Files(Folder),                      // Table contenant la liste des fichiers dans le dossier
                                                                TblRemoveFields = Table.RemoveColumns(TblSourceFolder, {"Content", "Extension", "Date accessed", "Date modified", "Date created", "Attributes"}),
                                                                // On récupère les noms des nouvelles colonnes qui seront ajoutées
                                                                CurrentTag=Record.Field(current, LIST_COLUMNS_TBL_COMBINE{1}),    // Tag fichier
                                                                CurrentItem=Record.Field(current, LIST_COLUMNS_TBL_COMBINE{2}),   // Item
                                                                CurrentFolder=Record.Field(current, LIST_COLUMNS_TBL_COMBINE{3}), // Tag dossier
                                                                // Ajout des nouvelles colonnes
                                                                TblAddTagColumn=Table.AddColumn(TblRemoveFields, LIST_COLUMNS_TBL_COMBINE{1}, each CurrentTag),     // Ajout colonne tag fichier
                                                                TblAddItemColumn=Table.AddColumn(TblAddTagColumn, LIST_COLUMNS_TBL_COMBINE{2}, each CurrentItem),   // Ajout colonne Item
                                                                TblAddTagFolder=Table.AddColumn(TblAddItemColumn, LIST_COLUMNS_TBL_COMBINE{3}, each CurrentFolder), // Ajout colonne tag dossier  
                                                                TblAddTagPath=if STR_AJOUT_DU_CHEMIN_COMPLET = "OUI" then Table.AddColumn(TblAddTagFolder, "CHEMIN_COMPLET", each Folder) else TblAddTagFolder,
                                                                // On construit la liste des fichiers qui seront combinés après exploration de tous les dossiers
                                                                ListFilesToRecords=Table.ToRecords(TblAddTagPath),         // Liste des enregistrements - fichiers de la table
                                                                ListCombineRecords=List.Combine({state, ListFilesToRecords}) // Liste finale des fichiers à traiter et à combiner entre-eux                          
                                                            in
                                                                ListCombineRecords
                                                        else state // on passe au dossier suivant
                                in
                                ListCombineFolders
                        ),
                //***********************************************************************************************
                // On va combiner la liste des fichiers contruite au prélable
                //***********************************************************************************************
                TableAccumlateCombineFiles = List.Accumulate(
                        ListAccumlateCombineFolders,
                        #table({},{}),
                        (state,current) =>
                                let
                                    PathName=current[Folder Path] & current[Name],                              
                                    // Récupération des valeurs Tag Colonne, Table(Item), Dossier
                                    TagColumn=Record.Field(current, LIST_COLUMNS_TBL_COMBINE{1}),
                                    CurrentItem=Record.Field(current, LIST_COLUMNS_TBL_COMBINE{2}),
                                    TagFolder=Record.Field(current, LIST_COLUMNS_TBL_COMBINE{3}),
                                    Path=if STR_AJOUT_DU_CHEMIN_COMPLET = "OUI"  then PathName else null,
                                    // Test existence de l'Item
                                    isExistsIem = if CurrentItem is null then false else fnIsExistsTableSheet(PathName,CurrentItem, STR_TYPE),
                                    TableCombine=if isExistsIem
                                                then
                                                    let
                                                        wbExcelTmp=try Excel.Workbook(File.Contents(PathName), null, true){[Item=CurrentItem,Kind=STR_TYPE]}[Data] otherwise state,
                                                        wbExcel=if STR_PROMOTE ="OUI" then Table.PromoteHeaders(wbExcelTmp, [PromoteAllScalars=true]) else wbExcelTmp,
                                                        TblAddTagColumn=Table.AddColumn(wbExcel, LIST_COLUMNS_TBL_COMBINE{1}, each TagColumn),     // Ajout colonne tag fichier                                        
                                                        TblAddTagFolder=Table.AddColumn(TblAddTagColumn, LIST_COLUMNS_TBL_COMBINE{3}, each TagFolder), // Ajout colonne tag dossier
                                                        TblAddPath=if STR_AJOUT_DU_CHEMIN_COMPLET = "OUI" then Table.AddColumn(TblAddTagFolder, "CHEMIN_COMPLET", each Path) else TblAddTagFolder, // Ajout colonne tag dossier
                                                        TableCombine=Table.Combine({state,TblAddPath})                                                  
                                                    in
                                                        TableCombine
                                                else state,  // Si l'Item n'existe pas on passe au fichier suivant

                                    v= TableCombine
                                in  
                                    v
                        )              
                in
                TableAccumlateCombineFiles,


        //******************************************************************************
        // Déplacement de l'étiquette à la 1ère colonne
        //******************************************************************************
        MOVE_COLUMN_TAG = (pTable as table, pCol as number) as any =>
                let
                    LstColumns=Table.ColumnNames(pTable),
                    NbrColumnsTotal=List.Count(LstColumns),
                    NbColumns=if STR_AJOUT_DU_CHEMIN_COMPLET = "OUI" then NbrColumnsTotal - 3 else NbrColumnsTotal - 2,
                    L1={LIST_COLUMNS_TBL_COMBINE{pCol}},
                    L2=List.FirstN(LstColumns,NbColumns),
                    LstCombine=List.Combine({L1,L2}),
                    TblReorder=Table.ReorderColumns(pTable,LstCombine)
                in
                TblReorder, //TblReorder,

        //******************************************************************************
        // Application des filtres
        //******************************************************************************
        // Ordre des colonnes de la table filtres
        // COLONNE_A_FILTRER    OPERATEUR    FILTRE
        SET_FILTERS = () as any =>
                let
                    // Une nouvelle fois on passe par une boucle pour lire les critères de filtres
                    TableAccumulateFilters = List.Accumulate(
                            LIST_COLUMNS_FILTERS_TO_RECORDS,  // Liste des critères de filtrage
                            TBL_TRY_02,                       // On démarre avec la table issue de la fusion des dossiers
                            (state, current) =>               // state initialisé avec la table issue de la fusion des dossiers, content critère de recherche en cours de traitement
                                let
                                    // On récupère les valeurs des filtres - Colonne - Opérateur - Valeur filtrée
                                    StrColumFilter=Record.Field(current, LIST_COLUMNS_FILTERS{0}),
                                    // On vérifie si la colonne existe dans la table des données issue de la combinaison des dossiers
                                    IsColumnExists=List.Contains(LIST_COLUMNS_TRY_02,StrColumFilter),
                                    StrOperFilter=Record.Field(current, LIST_COLUMNS_FILTERS{1}), // on récupère l'opératuer du filtre
                                    StrValueFilter=Text.From(Record.Field(current, LIST_COLUMNS_FILTERS{2})), // On force au format text pour éviter les plantages
                                    StrOperFilterPWQ=if StrOperFilter <> null then Text.Split(StrOperFilter,"|"){1} else "<N/A>", // pour éviter un plantage si l'opérateur est incomplet
                                    // Si la colonne existe on peut filtrer les données
                                    // On vérifie aussi si la ligne est complète
                                    TableEvalSelectRow=if IsColumnExists and StrOperFilter <> null and StrValueFilter <> null and  StrOperFilterPWQ <> "<N/A>"
                                                    then
                                                            if StrOperFilterPWQ = "=" or StrOperFilterPWQ = "<>"
                                                            // Cas des opérateurs qui ne font pas appel à une fonction Text de recherche
                                                            then
                                                                let
                                                                    // on construit la fonction de recherche de manière indirecte
                                                                    StrEval="each Record.Field(_, " & """" & StrColumFilter & """" & ")" & " " & StrOperFilterPWQ & " " & """" & StrValueFilter & """",
                                                                    // Traduction de l'expression de recherche en langage M
                                                                    EvaluateEach=Expression.Evaluate(StrEval, [Record.Field=Record.Field]),
                                                                    // Filtre selon les critères
                                                                    TableSelectRows=Table.SelectRows(state, EvaluateEach)
                                                                in
                                                                    TableSelectRows
                                                            // Cas des opérateurs qui font pas appel à une fonction Text de recherche
                                                            else
                                                                let
                                                                    // on construit la fonction de recherche de manière indirecte
                                                                    StrEval="each " & StrOperFilterPWQ & "(" & "Record.Field(_," & """" & StrColumFilter & """" & "), " & """" & StrValueFilter & """" & ")",                    
                                                                    // Traduction de l'expression de recherche en langage M
                                                                    EvaluateEach=Expression.Evaluate(StrEval, [Record.Field=Record.Field,
                                                                                                            Text.StartsWith=Text.StartsWith,
                                                                                                            Text.EndsWith=Text.EndsWith,
                                                                                                            Text.Contains=Text.Contains]),
                                                                    TableSelectRows=Table.SelectRows(state, EvaluateEach)
                                                                in
                                                                TableSelectRows
                                                        else state // si la colonne n'exite pas on passe au critère de filtre suivant
                                in
                                    TableEvalSelectRow
                        )
                in
                    TableAccumulateFilters,
                   
        //******************************************************************************
        //                      PRINCIPALE SECTION
        //******************************************************************************

        //******************************************************************************
        // Identification des paramètres
        //******************************************************************************
        STR_TYPE=pType,
        STR_PROMOTE=pPromote,
        STR_TBL_COMBINE=pTableCombine,
        STR_TAG_PREM=pTag,
        STR_TAG_COL_DOSSIER=pFolder,
        STR_AJOUT_DU_CHEMIN_COMPLET=pPath,



        //******************************************************************************
        // On prépare les éléments pour la fusion
        //******************************************************************************

        // Message d'erreur si plantage en fin de traitement
        TBL_MSG_ERROR=#table(type table [STATUT=text, MSG=text, ERRNUM=Int64.Type],{{"ERREUR DE TRAITEMENT", "Vérifier le chemin de vos dossiers et votre paramétrage",99}}),
        TBL_MSG_ERROR_FILTERS=#table(type table [STATUT=text, MSG=text, ERRNUM=Int64.Type],{{"ERREUR DE TRAITEMENT FILTRES", "Vérifier vos filtres - uniquement sur colonnes texte",99}}),

        // Ordre des colonnes de la table comportant la liste des dossiers à traiter
        // FICHIERS_EXCEL_COMBINE    ENSEIGNE    SHEET_COMBINE    DOSSIER
        TBL_COMBINE = Excel.CurrentWorkbook(){[Name=STR_TBL_COMBINE]}[Content],
        LIST_COLUMNS_TBL_COMBINE=Table.ColumnNames(TBL_COMBINE),
        STR_NOM_COLONNE_COMBINE=LIST_COLUMNS_TBL_COMBINE{0},  // Colonne comportant le nom du dossier à importer
        LST_COMBINE_FOLDERS_TO_RECORDS=Table.ToRecords(TBL_COMBINE),  // Transformation de la table en liste de Records

        //******************************************************************************
        // Fusion des fichiers
        //******************************************************************************
        TBL_DATA_COMBINE_FOLDERS=COMBINE_FOLDERS(),
        TBL_DATA_FINAL_01=if STR_TAG_PREM = "OUI" then MOVE_COLUMN_TAG(TBL_DATA_COMBINE_FOLDERS, 1) else TBL_DATA_COMBINE_FOLDERS,
        TBL_DATA_FINAL_02=try if STR_TAG_COL_DOSSIER = "OUI" then MOVE_COLUMN_TAG(TBL_DATA_FINAL_01, 3) else TBL_DATA_FINAL_01,
        HAS_ERROR=TBL_DATA_FINAL_02[HasError],
        TBL_TRY_02=if HAS_ERROR then TBL_MSG_ERROR else TBL_DATA_FINAL_02[Value],

        //***********************************************************************************
        // On va appliquer les filtres si nécessaire en fonction des options de paramétrage
        //***********************************************************************************
        LIST_COLUMNS_TRY_02=Table.ColumnNames(TBL_TRY_02),
        STR_APPLIQUER_FILTRES=pFilter,
        STR_TABLE_FILERS=pTableFilter,
        TBL_FILTERS = Excel.CurrentWorkbook(){[Name=STR_TABLE_FILERS]}[Content],
        LIST_COLUMNS_FILTERS=Table.ColumnNames(TBL_FILTERS),
        LIST_COLUMNS_FILTERS_TO_RECORDS=Table.ToRecords(TBL_FILTERS),
        STR_NOM_COLONNE_FILTRE=LIST_COLUMNS_FILTERS{0},
        TBL_FILTREE= if STR_APPLIQUER_FILTRES ="OUI" then SET_FILTERS() else TBL_TRY_02

    in
    TBL_FILTREE
in
    fnImportMultipleFoldersFilters

ICI en détail

PowerQuery:
        SET_FILTERS = () as any =>
                let
                    // Une nouvelle fois on passe par une boucle pour lire les critères de filtres
                    TableAccumulateFilters = List.Accumulate(
                            LIST_COLUMNS_FILTERS_TO_RECORDS,  // Liste des critères de filtrage
                            TBL_TRY_02,                       // On démarre avec la table issue de la fusion des dossiers
                            (state, current) =>               // state initialisé avec la table issue de la fusion des dossiers, content critère de recherche en cours de traitement
                                let
                                    // On récupère les valeurs des filtres - Colonne - Opérateur - Valeur filtrée
                                    StrColumFilter=Record.Field(current, LIST_COLUMNS_FILTERS{0}),
                                    // On vérifie si la colonne existe dans la table des données issue de la combinaison des dossiers
                                    IsColumnExists=List.Contains(LIST_COLUMNS_TRY_02,StrColumFilter),
                                    StrOperFilter=Record.Field(current, LIST_COLUMNS_FILTERS{1}), // on récupère l'opératuer du filtre
                                    StrValueFilter=Text.From(Record.Field(current, LIST_COLUMNS_FILTERS{2})), // On force au format text pour éviter les plantages
                                    StrOperFilterPWQ=if StrOperFilter <> null then Text.Split(StrOperFilter,"|"){1} else "<N/A>", // pour éviter un plantage si l'opérateur est incomplet
                                    // Si la colonne existe on peut filtrer les données
                                    // On vérifie aussi si la ligne est complète
                                    TableEvalSelectRow=if IsColumnExists and StrOperFilter <> null and StrValueFilter <> null and  StrOperFilterPWQ <> "<N/A>"
                                                    then
                                                            if StrOperFilterPWQ = "=" or StrOperFilterPWQ = "<>"
                                                            // Cas des opérateurs qui ne font pas appel à une fonction Text de recherche
                                                            then
                                                                let
                                                                    // on construit la fonction de recherche de manière indirecte
                                                                    StrEval="each Record.Field(_, " & """" & StrColumFilter & """" & ")" & " " & StrOperFilterPWQ & " " & """" & StrValueFilter & """",
                                                                    // Traduction de l'expression de recherche en langage M
                                                                    EvaluateEach=Expression.Evaluate(StrEval, [Record.Field=Record.Field]),
                                                                    // Filtre selon les critères
                                                                    TableSelectRows=Table.SelectRows(state, EvaluateEach)
                                                                in
                                                                    TableSelectRows
                                                            // Cas des opérateurs qui font pas appel à une fonction Text de recherche
                                                            else
                                                                let
                                                                    // on construit la fonction de recherche de manière indirecte
                                                                    StrEval="each " & StrOperFilterPWQ & "(" & "Record.Field(_," & """" & StrColumFilter & """" & "), " & """" & StrValueFilter & """" & ")",                    
                                                                    // Traduction de l'expression de recherche en langage M
                                                                    EvaluateEach=Expression.Evaluate(StrEval, [Record.Field=Record.Field,
                                                                                                            Text.StartsWith=Text.StartsWith,
                                                                                                            Text.EndsWith=Text.EndsWith,
                                                                                                            Text.Contains=Text.Contains]),
                                                                    TableSelectRows=Table.SelectRows(state, EvaluateEach)
                                                                in
                                                                TableSelectRows
                                                        else state // si la colonne n'exite pas on passe au critère de filtre suivant
                                in
                                    TableEvalSelectRow
                        )
                in
                    TableAccumulateFilters,

Le code est largement documenté. Il est construit sous la même logique que les autres fonctions.

Juste un point particulier sur cette partie de code qui représente le moteur du filtrage

PowerQuery:
                                                            then
                                                                let
                                                                    // on construit la fonction de recherche de manière indirecte
                                                                    StrEval="each Record.Field(_, " & """" & StrColumFilter & """" & ")" & " " & StrOperFilterPWQ & " " & """" & StrValueFilter & """",
                                                                    // Traduction de l'expression de recherche en langage M
                                                                    EvaluateEach=Expression.Evaluate(StrEval, [Record.Field=Record.Field]),
                                                                    // Filtre selon les critères
                                                                    TableSelectRows=Table.SelectRows(state, EvaluateEach)
                                                                in
                                                                    TableSelectRows
                                                            // Cas des opérateurs qui font pas appel à une fonction Text de recherche
                                                            else
                                                                let
                                                                    // on construit la fonction de recherche de manière indirecte
                                                                    StrEval="each " & StrOperFilterPWQ & "(" & "Record.Field(_," & """" & StrColumFilter & """" & "), " & """" & StrValueFilter & """" & ")",                    
                                                                    // Traduction de l'expression de recherche en langage M
                                                                    EvaluateEach=Expression.Evaluate(StrEval, [Record.Field=Record.Field,
                                                                                                            Text.StartsWith=Text.StartsWith,
                                                                                                            Text.EndsWith=Text.EndsWith,
                                                                                                            Text.Contains=Text.Contains]),
                                                                    TableSelectRows=Table.SelectRows(state, EvaluateEach)
                                                                in
                                                                TableSelectRows

En effet on doit construire au départ le filtrage des données de cette manière quand on applique les signes "=" ou "<>"
StrEval="each Record.Field(_, " & """" & StrColumFilter & """" & ")" & " " & StrOperFilterPWQ & " " & """" & StrValueFilter & """",
pour ensuite l'évaluer et le compiler en code PowerQuery
EvaluateEach=Expression.Evaluate(StrEval, [Record.Field=Record.Field]),

et dans le cas d'une fonction Text.*
StrEval="each " & StrOperFilterPWQ & "(" & "Record.Field(_," & """" & StrColumFilter & """" & "), " & """" & StrValueFilter & """" & ")",
EvaluateEach=Expression.Evaluate(StrEval, [Record.Field=Record.Field,
Text.StartsWith=Text.StartsWith,
Text.EndsWith=Text.EndsWith,
Text.Contains=Text.Contains]),



Rappel : Expression.Evaluate c'est un peu l'équivalent de la fonction INDIRECT sous Excel

REMARQUE : Toutes lignes incomplètes dans la table des critères de filtrage seront ignorées

QUELQUES EXEMPLES
















 

Pièces jointes

Bonjour Le Forum,
Ce post fait suite à un problème découvert avec l'intellisence. Un post a été fait à ce propos dans le Forum Excel ICI.
Ci-joint une nouvelle version corrigée et simplifiée.
Bref une requête dans la version précédente devait mettre la zizanie... j'ai donc tout refait.... ce qui est clair nous sommes sur la piste du bug PowerQuery...
 

Pièces jointes

- Navigue sans publicité
- Accède à Cléa, notre assistante IA experte Excel... et pas que...
- Profite de fonctionnalités exclusives
Ton soutien permet à Excel Downloads de rester 100% gratuit et de continuer à rassembler les passionnés d'Excel.
Je deviens Supporter XLD

Discussions similaires

Les cookies sont requis pour utiliser ce site. Vous devez les accepter pour continuer à utiliser le site. En savoir plus…