PowerQuery / Comparer deux tables

mromain

XLDnaute Barbatruc
Bonjour,

Ce post sert à présenter la fonction PowerQuery fnCompareTables qui permet de comparer deux tables de données.

Elle prend en entrée :
  • les deux tables à comparer ;
  • la liste des colonnes composant "la clef des données" ;
  • optionnellement, le nom des sources.

Elle retourne :
  • les colonnes composant "la clef des données" ;
  • une colonne StatutComparaison qui peut prendre quatre valeurs :
    • Existe dans Source1 mais pas dans Source2 ;
    • Existe dans Source2 mais pas dans Source1 ;
    • Écarts, si la donnée existe dans les deux sources, mais ne contient pas les mêmes informations ;
    • OK, si la donnée existe dans les deux sources et est identique.
  • une colonne contenant la donnée de la Source1 ;
  • une colonne contenant la donnée de la Source2 ;
  • une colonne contenant la liste des écarts.

Le fichier ci-joint contient un exemple d'utilisation de cette fonction.

Code de la fonction fnCompareTables :
PowerQuery:
let
    // fonction permettant de faire la comparaison entre deux tables
    fnCompareTables = (source1 as table, source2 as table, keyColumns as list, optional sourceName1 as nullable text, optional sourceName2 as nullable text) as table =>
        let

            ErrorMsg = [
                Title = "Erreur dans les données sources à comparer",
                KeyColumnNotDefined = "la/les colonne(s) permettant de définir la ""clé"" des données à comparer n'est/ne sont pas définie(s).",
                ErrorDataSourcesStructure = "les sources de données ne comportent pas les mêmes colonnes — colonne(s) concernée(s) : '#{0}'.",
                KeyColumnNotFound = "la/les colonne(s) suivante(s) '#{0}' est/sont absente(s) des sources de données.",
                EmptyDataSource = "la source '#{0}' ne contient aucune donnée.",
                DuplicatesInSource = "la/les colonne(s) '#{0}' ne permet(tent) pas d'identifier la clé des données dans la source '#{1}'."
            ],

            ResultColumns = [
                ComparisonStatus = "StatutComparaison",
                DataPrefix = "Data - ",
                Differences = "Écarts"
            ],

            ComparisonStatus = [
                ExistsInOnlyFirstSource = "Existe dans '#{0}' mais pas dans '#{1}'",
                ExistsInBothButDifferences = "Écarts",
                ExistsInBothAndOK = "OK"
            ],
            
            sName1 = if sourceName1 = null then "Source 1" else sourceName1,
            sName2 = if sourceName2 = null then "Source 2" else sourceName2,
            keyCols = List.Transform(keyColumns, Text.From),
            listColumnsNames = Table.ColumnNames(source1),
            listColumnsInErrors = let listCols2 = Table.ColumnNames(source2) in List.RemoveItems(listColumnsNames, listCols2) & List.RemoveItems(listCols2, listColumnsNames),
            missingKeyColumns = List.RemoveItems(keyCols, listColumnsNames),

            TableKeys = 
                let
                    fnExtractKeysTable = (dataTable as table, sourceName as text) as table =>
                        let
                            colCountName = Text.Combine(keyCols) & "_",
                            GroupByKeysAndCount = Table.Group(dataTable, keyCols, {{colCountName, each Table.RowCount(_), Int64.Type}})
                        in 
                            if  Table.RowCount(dataTable) = 0 then
                                error [Reason = ErrorMsg[Title], Message = Text.Format(ErrorMsg[EmptyDataSource], {sourceName})] 
                            else if List.Max(Table.Column(GroupByKeysAndCount, colCountName)) > 1 then
                                error [Reason = ErrorMsg[Title], Message = Text.Format(ErrorMsg[DuplicatesInSource], {Text.Combine(List.Transform(keyCols, each "[" & _ & "]"), " / "), sourceName})]
                            else 
                                Table.SelectColumns(GroupByKeysAndCount, keyCols)
                in
                    Table.Distinct(fnExtractKeysTable(source1, sName1)&fnExtractKeysTable(source2, sName2)),
            
            MergeSource1 = Table.NestedJoin(TableKeys, keyCols, source1, keyCols, sName1, JoinKind.LeftOuter),
            MergeSource2 = Table.NestedJoin(MergeSource1, keyCols, source2, keyCols, sName2, JoinKind.LeftOuter),
            
            ExtractSourceRecords = 
                let 
                    fnTransform=(tableSrc as table) as nullable record => if Table.RowCount(tableSrc)=0 then null else tableSrc{0} 
                in 
                    Table.TransformColumns(MergeSource2, List.Transform({sName1, sName2}, each {_, fnTransform, type nullable record})),
            
            resultRecordFields = {ResultColumns[ComparisonStatus], ResultColumns[DataPrefix] & sName1, ResultColumns[DataPrefix] & sName2, ResultColumns[Differences]},
            tmpColComparison = "<" & Text.Combine(keyCols, "_") & ">",
            
            AddColumnResultComparaison = 
                let
                    fnCompareSourceRecords = (recordSource1 as nullable record, recordSource2 as nullable record) as record => 
                        let
                            status = 
                                if recordSource1 = null then
                                    Text.Format(ComparisonStatus[ExistsInOnlyFirstSource], {sName2, sName1})
                                else if recordSource2 = null then
                                    Text.Format(ComparisonStatus[ExistsInOnlyFirstSource], {sName1, sName2})
                                else if recordSource1 = recordSource2 then
                                    ComparisonStatus[ExistsInBothAndOK]
                                else
                                    ComparisonStatus[ExistsInBothButDifferences],
                            dataRecord1 = if recordSource1 <> null then Record.RemoveFields(recordSource1, keyCols) else null,
                            dataRecord2 = if recordSource2 <> null then Record.RemoveFields(recordSource2, keyCols) else null,
                            differences =  
                                if status = ComparisonStatus[ExistsInBothButDifferences] then
                                    Table.RenameColumns(
                                        Table.FromRecords(
                                            List.RemoveNulls(
                                                List.Transform(Record.FieldNames(dataRecord1), each let valData1=Record.Field(dataRecord1, _), valData2=Record.Field(dataRecord2, _) in if valData1=valData2 then null else [Champ=_, valData1=valData1, valData2=valData2])
                                            )
                                        ), 
                                        {{"valData1", sName1}, {"valData2", sName2}}
                                    )
                                else
                                    null
                        in 
                            Record.FromList({status, dataRecord1, dataRecord2, differences}, resultRecordFields)
                in            
                    Table.AddColumn(ExtractSourceRecords, tmpColComparison, each fnCompareSourceRecords(Record.Field(_, sName1), Record.Field(_, sName2)), type record),
            
            ExtractResultComparaison = 
                let
                    listTransformations = List.Zip({resultRecordFields, {type text, type nullable record, type nullable record, type nullable table}})
                in
                    List.Accumulate(listTransformations, AddColumnResultComparaison, (s, c)=> Table.AddColumn(s, c{0}, each let compRecord = Record.Field(_, tmpColComparison) in Record.Field(compRecord, c{0}), c{1})),
            
            SelectColumns = Table.SelectColumns(ExtractResultComparaison,keyCols & resultRecordFields),

            ResultFunction = 
                if List.Count(keyCols) = 0 then
                    error [Reason = ErrorMsg[Title], Message = ErrorMsg[KeyColumnNotDefined]]
                else if List.Count(listColumnsInErrors) > 0 then
                    error [Reason = ErrorMsg[Title], Message = Text.Format(ErrorMsg[ErrorDataSourcesStructure], {Text.Combine(List.Transform(listColumnsInErrors, each "[" & _ & "]"), " / ")})]
                else if List.Count(missingKeyColumns) > 0 then 
                    error [Reason = ErrorMsg[Title], Message = Text.Format(ErrorMsg[KeyColumnNotFound], {Text.Combine(List.Transform(missingKeyColumns, each "[" & _ & "]"), " / ")})]
                else
                    SelectColumns
        in
            ResultFunction
in
    fnCompareTables

A+
 

Pièces jointes

  • Exemple.xlsx
    27.8 KB · Affichages: 24

oguruma

XLDnaute Occasionnel
Bonjour,

Ce post sert à présenter la fonction PowerQuery fnCompareTables qui permet de comparer deux tables de données.

Elle prend en entrée :
  • les deux tables à comparer ;
  • la liste des colonnes composant "la clef des données" ;
  • optionnellement, le nom des sources.

Elle retourne :
  • les colonnes composant "la clef des données" ;
  • une colonne StatutComparaisonqui peut prendre quatre valeurs :
    • Existe dans Source1 mais pas dans Source2 ;
    • Existe dans Source2 mais pas dans Source1 ;
    • Écarts, si la donnée existe dans les deux sources, mais ne contient pas les mêmes informations ;
    • OK, si la donnée existe dans les deux sources et est identique.
  • une colonne contenant la donnée de la Source1 ;
  • une colonne contenant la donnée de la Source2 ;
  • une colonne contenant la liste des écarts.

Le fichier ci-joint contient un exemple d'utilisation de cette fonction.

Code de la fonction fnCompareTables :
PowerQuery:
let
    // fonction permettant de faire la comparaison entre deux tables
    fnCompareTables = (source1 as table, source2 as table, keyColumns as list, optional sourceName1 as nullable text, optional sourceName2 as nullable text) as table =>
        let

            ErrorMsg = [
                Title = "Erreur dans les données sources à comparer",
                KeyColumnNotDefined = "la/les colonne(s) permettant de définir la ""clé"" des données à comparer n'est/ne sont pas définie(s).",
                ErrorDataSourcesStructure = "les sources de données ne comportent pas les mêmes colonnes — colonne(s) concernée(s) : '#{0}'.",
                KeyColumnNotFound = "la/les colonne(s) suivante(s) '#{0}' est/sont absente(s) des sources de données.",
                EmptyDataSource = "la source '#{0}' ne contient aucune donnée.",
                DuplicatesInSource = "la/les colonne(s) '#{0}' ne permet(tent) pas d'identifier la clé des données dans la source '#{1}'."
            ],

            ResultColumns = [
                ComparisonStatus = "StatutComparaison",
                DataPrefix = "Data - ",
                Differences = "Écarts"
            ],

            ComparisonStatus = [
                ExistsInOnlyFirstSource = "Existe dans '#{0}' mais pas dans '#{1}'",
                ExistsInBothButDifferences = "Écarts",
                ExistsInBothAndOK = "OK"
            ],
          
            sName1 = if sourceName1 = null then "Source 1" else sourceName1,
            sName2 = if sourceName2 = null then "Source 2" else sourceName2,
            keyCols = List.Transform(keyColumns, Text.From),
            listColumnsNames = Table.ColumnNames(source1),
            listColumnsInErrors = let listCols2 = Table.ColumnNames(source2) in List.RemoveItems(listColumnsNames, listCols2) & List.RemoveItems(listCols2, listColumnsNames),
            missingKeyColumns = List.RemoveItems(keyCols, listColumnsNames),

            TableKeys =
                let
                    fnExtractKeysTable = (dataTable as table, sourceName as text) as table =>
                        let
                            colCountName = Text.Combine(keyCols) & "_",
                            GroupByKeysAndCount = Table.Group(dataTable, keyCols, {{colCountName, each Table.RowCount(_), Int64.Type}})
                        in
                            if  Table.RowCount(dataTable) = 0 then
                                error [Reason = ErrorMsg[Title], Message = Text.Format(ErrorMsg[EmptyDataSource], {sourceName})]
                            else if List.Max(Table.Column(GroupByKeysAndCount, colCountName)) > 1 then
                                error [Reason = ErrorMsg[Title], Message = Text.Format(ErrorMsg[DuplicatesInSource], {Text.Combine(List.Transform(keyCols, each "[" & _ & "]"), " / "), sourceName})]
                            else
                                Table.SelectColumns(GroupByKeysAndCount, keyCols)
                in
                    Table.Distinct(fnExtractKeysTable(source1, sName1)&fnExtractKeysTable(source2, sName2)),
          
            MergeSource1 = Table.NestedJoin(TableKeys, keyCols, source1, keyCols, sName1, JoinKind.LeftOuter),
            MergeSource2 = Table.NestedJoin(MergeSource1, keyCols, source2, keyCols, sName2, JoinKind.LeftOuter),
          
            ExtractSourceRecords =
                let
                    fnTransform=(tableSrc as table) as nullable record => if Table.RowCount(tableSrc)=0 then null else tableSrc{0}
                in
                    Table.TransformColumns(MergeSource2, List.Transform({sName1, sName2}, each {_, fnTransform, type nullable record})),
          
            resultRecordFields = {ResultColumns[ComparisonStatus], ResultColumns[DataPrefix] & sName1, ResultColumns[DataPrefix] & sName2, ResultColumns[Differences]},
            tmpColComparison = "<" & Text.Combine(keyCols, "_") & ">",
          
            AddColumnResultComparaison =
                let
                    fnCompareSourceRecords = (recordSource1 as nullable record, recordSource2 as nullable record) as record =>
                        let
                            status =
                                if recordSource1 = null then
                                    Text.Format(ComparisonStatus[ExistsInOnlyFirstSource], {sName2, sName1})
                                else if recordSource2 = null then
                                    Text.Format(ComparisonStatus[ExistsInOnlyFirstSource], {sName1, sName2})
                                else if recordSource1 = recordSource2 then
                                    ComparisonStatus[ExistsInBothAndOK]
                                else
                                    ComparisonStatus[ExistsInBothButDifferences],
                            dataRecord1 = if recordSource1 <> null then Record.RemoveFields(recordSource1, keyCols) else null,
                            dataRecord2 = if recordSource2 <> null then Record.RemoveFields(recordSource2, keyCols) else null,
                            differences =
                                if status = ComparisonStatus[ExistsInBothButDifferences] then
                                    Table.RenameColumns(
                                        Table.FromRecords(
                                            List.RemoveNulls(
                                                List.Transform(Record.FieldNames(dataRecord1), each let valData1=Record.Field(dataRecord1, _), valData2=Record.Field(dataRecord2, _) in if valData1=valData2 then null else [Champ=_, valData1=valData1, valData2=valData2])
                                            )
                                        ),
                                        {{"valData1", sName1}, {"valData2", sName2}}
                                    )
                                else
                                    null
                        in
                            Record.FromList({status, dataRecord1, dataRecord2, differences}, resultRecordFields)
                in          
                    Table.AddColumn(ExtractSourceRecords, tmpColComparison, each fnCompareSourceRecords(Record.Field(_, sName1), Record.Field(_, sName2)), type record),
          
            ExtractResultComparaison =
                let
                    listTransformations = List.Zip({resultRecordFields, {type text, type nullable record, type nullable record, type nullable table}})
                in
                    List.Accumulate(listTransformations, AddColumnResultComparaison, (s, c)=> Table.AddColumn(s, c{0}, each let compRecord = Record.Field(_, tmpColComparison) in Record.Field(compRecord, c{0}), c{1})),
          
            SelectColumns = Table.SelectColumns(ExtractResultComparaison,keyCols & resultRecordFields),

            ResultFunction =
                if List.Count(keyCols) = 0 then
                    error [Reason = ErrorMsg[Title], Message = ErrorMsg[KeyColumnNotDefined]]
                else if List.Count(listColumnsInErrors) > 0 then
                    error [Reason = ErrorMsg[Title], Message = Text.Format(ErrorMsg[ErrorDataSourcesStructure], {Text.Combine(List.Transform(listColumnsInErrors, each "[" & _ & "]"), " / ")})]
                else if List.Count(missingKeyColumns) > 0 then
                    error [Reason = ErrorMsg[Title], Message = Text.Format(ErrorMsg[KeyColumnNotFound], {Text.Combine(List.Transform(missingKeyColumns, each "[" & _ & "]"), " / ")})]
                else
                    SelectColumns
        in
            ResultFunction
in
    fnCompareTables

A+
Bonjour, superbe job :) juste un ptit truc ;) quelques commentaires ça serait le top du top surtout pour ceux et celles qui veulent mettent le pied à l'étrier au langage M car tu utilises de très belles subtilités de programmation ne serait-ce que la création de ta table dynamique de messages :) et je ne parle pas du NestedTable construit on the fly
 

Discussions similaires

Statistiques des forums

Discussions
312 413
Messages
2 088 199
Membres
103 761
dernier inscrit
rouazali