POWERQUERY :: Le VLOOKUP d'Excel

oguruma

XLDnaute Occasionnel
Bonjour,
Bien qu'il existe de multiples versions sur la toile et par le passé j'avais déjà publié une version du RechercheX ou une fonction faisant l'équivalent du Index+Equiv vous trouverez 3 versions du VLOOKUP Excel.
Pour ces 3 versions l'algorithme du code reste le même. Les points qui les diffèrent sont les lignes d'optimisation : si on bufferise la table des données, si on trie la table ou pas sur la 1ère colonne de recherche et si on effectue au préalable les premières recherches pour palier ou pas à la recherche approximative.

Version 1 : fnVLOOKUP
Peu optimisée. Les 1ères recherches préalables sont lancées systématiquement. La table des données n'est pas bufferisée.
PowerQuery:
let fnVLOOKUP=
    (
        LookupValue as any,
        TableData as table,
        ColumnIndex as number,
        optional pSortTable as logical,
        optional pApproximative as logical,
        optional pMsg as text
    ) as any =>

    let
    // Recherche approximative demandée ? Par défaut la recherche sera approximative
    bMatchApproximative = if pApproximative = null then true else pApproximative,
    bSort = if pSortTable = null then true else pSortTable,
    ErrMsg=if pMsg = null then "#N/A" else pMsg,

    // Initialisation de la recherche - on liste les colonnes de la table
    ListColumns = Table.ColumnNames(TableData),

    // La liste des colonnes est transformée en table
    // On fait appel au buffer pour optimiser les traitements, attention volumétrie vs mémoire
    ColumnsTable = Table.Buffer(Table.FromList(ListColumns, Splitter.SplitByNothing(), null, null, ExtraValues.Error)),

    // On identifie la colonne de recherche
    ColumnNameMatched = Record.Field(ColumnsTable{0},"Column1"),  
    ColumnNameReturned = Record.Field(ColumnsTable{ColumnIndex - 1},"Column1"),

    // Doit-on trier la table d'entrée
    SortedData = if bSort then
                          Table.Sort(TableData,{{ColumnNameMatched, Order.Descending}})
                          else TableData,

    // On fait appel au buffer pour optimiser les traitements, attention volumétrie vs mémoire
    RenameLookupColumn = Table.Buffer(Table.RenameColumns(SortedData,{{ColumnNameMatched, "LookupColumn"}})),
    SeekRow = Table.Buffer(Table.SelectRows(RenameLookupColumn, each [LookupColumn] <= LookupValue)),

    // On évalue la 1ère tentative de recherche
    IstMatch=
    if Table.IsEmpty(SeekRow)=true
        then ErrMsg
        else Record.Field(SeekRow{0},"LookupColumn"),

    // Valeur retournée en cas de recherche approximative
    ReturnValue=
    if Table.IsEmpty(SeekRow)=true
        then ErrMsg
        else Record.Field(SeekRow{0},ColumnNameReturned),

    // Cas où la recherche doit être approximative
    FinalReturn =
    if bMatchApproximative=true
        then ReturnValue
        // Sinon on renvoie la valeur exacte
        else if LookupValue = IstMatch
            then ReturnValue
            else ErrMsg

    // On renvoie la valeur recherchée quelque soit le mode de recherche
    in
        FinalReturn
in
    fnVLOOKUP


Version 2 : fnVLOOKUP
La table des données n'est pas bufferisée mais la recherche préalable au mode approximatif n'est effectuée que si précisée dans les paramètres
Code:
let fnVLOOKUP=
    (
        LookupValue as any,
        TableData as table,
        ColumnIndex as number,
        optional pSortTable as logical,
        optional pApproximative as logical,
        optional pMsg as text
    ) as any =>

    let
    // Recherche approximative demandée ? Par défaut la recherche sera approximative
    bMatchApproximative = if pApproximative = null then true else pApproximative,
    bSort = if pSortTable = null then true else pSortTable,
    ErrMsg=if pMsg = null then "#N/A" else pMsg,

    // Initialisation de la recherche - on liste les colonnes de la table
    ListColumns = Table.ColumnNames(TableData),

    // La liste des colonnes est transformée en table
    // On fait appel au buffer pour optimiser les traitements, attention volumétrie vs mémoire
    ColumnsTable = Table.Buffer(Table.FromList(ListColumns, Splitter.SplitByNothing(), null, null, ExtraValues.Error)),

    // On identifie la colonne de recherche
    ColumnNameMatched = Record.Field(ColumnsTable{0},"Column1"),  
    ColumnNameReturned = Record.Field(ColumnsTable{ColumnIndex - 1},"Column1"),

    // Doit-on trier la table d'entrée
    SortedData = if bSort then
                          Table.Sort(TableData,{{ColumnNameMatched, Order.Descending}})
                          else TableData,
                         
    // On fait appel au buffer pour optimiser les traitements, attention volumétrie vs mémoire
    RenameLookupColumn = Table.Buffer(Table.RenameColumns(SortedData,{{ColumnNameMatched, "LookupColumn"}})),
    SeekRow = Table.Buffer(Table.SelectRows(RenameLookupColumn, each [LookupColumn] <= LookupValue)),

    // On évalue la 1ère tentative de recherche
    IstMatch=
    if Table.IsEmpty(SeekRow)=true
        then ErrMsg
        else Record.Field(SeekRow{0},"LookupColumn"),

    // On démarre les recherches
    FinalReturn =
    // Cas où la recherche doit être approximative
    if bMatchApproximative=true
        then let
                // On recherche la clef la plus proche
                V= if Table.IsEmpty(SeekRow)=true then ErrMsg else Record.Field(SeekRow{0},ColumnNameReturned)
             in
                V          
        // Sinon on renvoie la valeur exacte si on trouve la clef de recherche
        else if LookupValue = IstMatch
            then let
                    V= if Table.IsEmpty(SeekRow)=true then ErrMsg else Record.Field(SeekRow{0},ColumnNameReturned)
                 in
                    V          
            else ErrMsg

    // On renvoie la valeur recherchée quelque soit le mode de recherche
    in
        FinalReturn
in
    fnVLOOKUP

Version 3 : fnVLOOKUP3
Possibilité de bufferiser la table des données dans les options, les traitements préalables de recherche et quelque soit le mode ne sont effectués que si nécessaires

PowerQuery:
let fnVLOOKUP=
    (
        LookupValue as any,
        TableData as table,
        ColumnIndex as number,
        optional pSortTable as logical,
        optional pApproximative as logical,
        optional pMsg as text,
        optional pBuff as logical
    ) as any =>

    let
    // Recherche approximative demandée ? Par défaut la recherche sera approximative
    bMatchApproximative = if pApproximative = null then true else pApproximative,
    bSort = if pSortTable = null then true else pSortTable,  
    ErrMsg=if pMsg = null then "#N/A" else pMsg,
    bBuff = if pBuff = null then false else pBuff,

    // On fait appel au buffer pour optimiser les traitements, attention volumétrie vs mémoire
    TableDataWork=if bBuff then Table.Buffer(TableData) else TableData,

    // Initialisation de la recherche - on liste les colonnes de la table
    ListColumns = Table.ColumnNames(TableData),

    // La liste des colonnes est transformée en table
    // On fait appel au buffer pour optimiser les traitements, attention volumétrie vs mémoire
    ColumnsTable = Table.Buffer(Table.FromList(ListColumns, Splitter.SplitByNothing(), null, null, ExtraValues.Error)),

    // On identifie la colonne de recherche
    ColumnNameMatched = Record.Field(ColumnsTable{0},"Column1"),  
    ColumnNameReturned = Record.Field(ColumnsTable{ColumnIndex - 1},"Column1"),

    // Doit-on trier la table d'entrée
    SortedData = if bSort then
                          Table.Sort(TableDataWork,{{ColumnNameMatched, Order.Descending}})
                          else TableDataWork,

    // On fait appel au buffer pour optimiser les traitements, attention volumétrie vs mémoire
    RenameLookupColumn = Table.Buffer(Table.RenameColumns(SortedData,{{ColumnNameMatched, "LookupColumn"}})),
    SeekRow = Table.Buffer(Table.SelectRows(RenameLookupColumn, each [LookupColumn] <= LookupValue)),

    // On démarre les recherches
    FinalReturn =
    // Cas où la recherche doit être approximative
    if bMatchApproximative=true
        then let
                // On recherche la clef la plus proche
                V= if Table.IsEmpty(SeekRow)=true then ErrMsg else Record.Field(SeekRow{0},ColumnNameReturned)
             in
                V          
        // Sinon on renvoie la valeur exacte si on trouve la clef de recherche
        else let
                IstMatch=
                        if Table.IsEmpty(SeekRow)=true
                        then ErrMsg
                        else Record.Field(SeekRow{0},"LookupColumn"),
                V=if LookupValue = IstMatch
                  then if Table.IsEmpty(SeekRow)=true then ErrMsg else Record.Field(SeekRow{0},ColumnNameReturned)
                  else ErrMsg      
             in
                V      

    // On renvoie la valeur recherchée quelque soit le mode de recherche
    in
        FinalReturn
in
    fnVLOOKUP
 

Pièces jointes

  • VLOOKUP_0.004.xlsx
    119 KB · Affichages: 2

Discussions similaires

Statistiques des forums

Discussions
312 864
Messages
2 093 002
Membres
105 593
dernier inscrit
Damien49