Powerquery: разверните все столбцы, в которых есть записи

Используя Power Query в Microsoft Excel 2013, я создал таблицу, которая выглядит так:

// To insert this in Power Query, append a '=' before the 'Table.FromRows'
Table.FromRows(
  {
    {"0", "Tom", "null", "null"},
    {"1", "Bob", [ name="Berlin" , street="BarStreet" ], [ name="Mary", age=25 ]},
    {"2", "Jim", [ name="Hamburg", street="FooStreet" ], [ name="Marta", age=30 ]}
  },
  {"ID", "Name", "Address", "Wife"}
)

Теперь я хочу расширить столбцы Address и Wife, используя атрибут name в обеих записях. Вручную я бы сделал так:

// To insert this in Power Query, append a '=' before the 'Table.FromRows'
let
  t = Table.FromRows(
    {
      {"0", "Tom", "null", "null"},
      {"1", "Bob", [ name="Berlin" , street="BarStreet" ], [ name="Mary", age=25 ]},
      {"2", "Jim", [ name="Hamburg", street="FooStreet" ], [ name="Marta", age=30 ]}
    },
    {"ID", "Name", "Address", "Wife"}
  ),
  expAddress = Table.ExpandRecordColumn(t, "Address", {"name"}, {"Address → name"}),
  expWife = Table.ExpandRecordColumn(expAddress, "Wife", {"name"}, {"Wife → name"})
in
  expWife

Фон

Всякий раз, когда у меня есть таблицы данных с другим макетом, мне нужно переписать запрос. В фантастическом мире вы можете развернуть все столбцы, в которых есть записи, с помощью определенного ключа. В идеале у вас должны быть следующие библиотечные функции:

// Returns a list with the names of the columns that match the secified type.
// Will also try to infer the type of a column if the table is untyped.
Table.ColumnsOfTypeInfer(
  table as table,
  listOfTypes as list
) as list

// Expands a column of records into columns with each of the values.
Table.ExpandRecordColumnByKey(
  table as table,
  columns as list,
  key as text,
) as table

Тогда я мог бы позвонить

// To insert this in Power Query, append a '=' before the 'Table.FromRows'
let
  t = Table.FromRows(
    {
      {"0", "Tom", "null", "null"},
      {"1", "Bob", [ name="Berlin" , street="BarStreet" ], [ name="Mary", age=25 ]},
      {"2", "Jim", [ name="Hamburg", street="FooStreet" ], [ name="Marta", age=30 ]}
    },
    {"ID", "Name", "Address", "Wife"}
  ),
  recordColumns = Table.ColumnsOfTypeInfer(t, {type record}),
  expAll = Table.ExpandRecordColumnByKey(t, recordColumns, "name")
in
  expAll

Вопрос

  1. Можете ли вы получить список столбцов с определенным типом, который не указан в таблице, или вывести его?
  2. Можете ли вы сделать это расширение записи общим?

Изменить: добавлена ​​строка № 0 с двумя нулевыми ячейками.


person Jonas Gröger    schedule 03.03.2016    source источник


Ответы (2)


(Во-первых, спасибо за четкое объяснение, образцы данных и предложения!)

1) В M-коде нет способа сделать вывод типа. Это ограничение можно почти считать «функцией», потому что, если исходные данные изменяются таким образом, что выводимый тип становится другим, это почти наверняка нарушит ваш запрос.

После загрузки нетипизированных данных можно быстро использовать Определить тип данных, чтобы сгенерировать для этого букву M. Или, если вы читаете данные из JSON, они уже должны быть достаточно набраны.

Если у вас есть конкретный сценарий, в котором это не работает, хотите обновить свой вопрос? :)

2) Очень возможно и немного запутанно сделать расширение записи универсальным, если значения ячеек таблицы являются записями. Это находит столбцы, в которых все строки либо null, либо записи, и расширяет столбец name.

Вот несколько простых реализаций, которые вы можете добавить в свою библиотеку:

let
  t = Table.FromRows(
    {
      {"0", "Tom", null, null},
      {"1", "Bob", [ name="Berlin" , street="BarStreet" ], [ name="Mary", age=25 ]},
      {"2", "Jim", [ name="Hamburg", street="FooStreet" ], [ name="Marta", age=30 ]}
    },
    {"ID", "Name", "Address", "Wife"}
  ),

  Table.ColumnsOfAllRowType = (table as table, typ as type) as list => let
    ColumnNames = Table.ColumnNames(table),
    ColumnsOfType = List.Select(ColumnNames, (name) => 
      List.AllTrue(List.Transform(Table.Column(table, name), (cell) => Type.Is(Value.Type(cell), typ))))
  in
    ColumnsOfType,

  Table.ExpandRecordColumnByKey = (table as table, columns as list, key as text) as table  => 
    List.Accumulate(columns, table, (state, columnToExpand) => 
      Table.ExpandRecordColumn(state, columnToExpand, {key}, { columnToExpand & " → " & key })),

  recordColumns = Table.ColumnsOfAllRowType(t, type nullable record),
  expAll = Table.ExpandRecordColumnByKey(t, recordColumns, "name")
in
  expAll

Если новую библиотечную функцию можно реализовать только в M, мы с меньшей вероятностью добавим ее в нашу стандартную библиотеку, но если вы чувствуете, что она отсутствует, не стесняйтесь предложить ее по адресу: https://ideas.powerbi.com/forum/265200-power-bi/

У вас может быть хороший аргумент в пользу добавления чего-то вроде Table.ReplaceTypeFromFirstRow(table as table) as table, потому что создание типа с помощью M очень беспорядочно.

person Carl Walsh    schedule 04.03.2016
comment
Спасибо, что ответили. Однако небезопасно предполагать, что в первой строке есть записи и там могут быть нулевые значения. Я обновил свой пример в этом отношении. @ Карл Уолш - person Jonas Gröger; 07.03.2016

Извините, что пришел к этому немного поздно, но у меня была похожая проблема. Я пробовал использовать функцию ExpandAll Криса Уэбба:

http://blog.crossjoin.co.uk/2014/05/21/expanding-all-columns-in-a-table-in-power-query/

... но это работает только со столбцами типа таблицы, а не со столбцами типа записи, но мне удалось взломать его для этой цели. Я продублировал функцию Криса как «ExpandAllRecords» и сделал 3 правки:

  1. заменил each if _ is table then Table.ColumnNames(_) на each if _ is record then Record.FieldNames(_)
  2. заменил Table.ExpandTableColumn на Table.ExpandRecordColumn
  3. заменил ExpandAll на ExpandAllRecords

Я пробовал расширять таблицы и записи с помощью одной функции, но продолжал получать ошибки типа.

В любом случае, после этого последний запрос будет таким:

let
    t = Table.FromRows(
        {
            {"1", "Tom", null, [ name="Jane", age=35 ]},
            {"2", "Bob", [ name="Berlin" , street="BarStreet" ], [ name="Mary", age=25 ]},
            {"3", "Jim", [ name="Hamburg", street="FooStreet" ], [ name="Marta", age=30 ]}
        },
        {"ID", "Name", "Address", "Wife"}
    ),
    Output = ExpandAllRecords(t)
in
    Output

Редактировать:

Беспокоясь о том, что однажды отличный фрагмент (Криса Уэбба, упомянутый @MikeHoney) однажды исчезнет), я зеркалирую весь код здесь:

let
     //Define function taking two parameters - a table and an optional column number 
     Source = (TableToExpand as table, optional ColumnNumber as number) =>
     let
      //If the column number is missing, make it 0
      ActualColumnNumber = if (ColumnNumber=null) then 0 else ColumnNumber,

      //Find the column name relating to the column number
      ColumnName = Table.ColumnNames(TableToExpand){ActualColumnNumber},

      //Get a list containing all of the values in the column
      ColumnContents = Table.Column(TableToExpand, ColumnName),

      //Iterate over each value in the column and then
      //If the value is of type table get a list of all of the columns in the table
      //Then get a distinct list of all of these column names
      ColumnsToExpand = List.Distinct(List.Combine(List.Transform(ColumnContents, 
                         each if _ is table then Table.ColumnNames(_) else {}))),

      //Append the original column name to the front of each of these column names
      NewColumnNames = List.Transform(ColumnsToExpand, each ColumnName & "." & _),

      //Is there anything to expand in this column?
      CanExpandCurrentColumn = List.Count(ColumnsToExpand)>0,

      //If this column can be expanded, then expand it
      ExpandedTable = if CanExpandCurrentColumn 
                          then 
                          Table.ExpandTableColumn(TableToExpand, ColumnName, 
                                 ColumnsToExpand, NewColumnNames) 
                          else 
                          TableToExpand,
      //If the column has been expanded then keep the column number the same, otherwise add one to it
      NextColumnNumber = if CanExpandCurrentColumn then ActualColumnNumber else ActualColumnNumber+1,

      //If the column number is now greater than the number of columns in the table
      //Then return the table as it is
      //Else call the ExpandAll function recursively with the expanded table
      OutputTable = if NextColumnNumber>(Table.ColumnCount(ExpandedTable)-1) 
                         then 
                         ExpandedTable 
                         else 
                         ExpandAll(ExpandedTable, NextColumnNumber)
     in
     OutputTable
in
     Source

Затем вы можете использовать эту функцию в XML-файле следующим образом:

let
     //Load XML file
     Source = Xml.Tables(File.Contents("C:\Users\Chris\Documents\PQ XML Expand All Demo.xml")),
     ChangedType = Table.TransformColumnTypes(Source,{{"companyname", type text}}),

     //Call the ExpandAll function to expand all columns
     Output = ExpandAll(ChangedType)
in
     Output  

(Источник и загружаемый пример: Би-блог Криса Уэбба, 21 мая 2014 г.)

person Mike Honey    schedule 22.03.2016
comment
Гист для функции Криса Уэбба по расширению столбцов табличного типа: gist.github.com/Mike-Honey/ f5f3f708310eb1de7f4a - person Mike Honey; 29.09.2019
comment
Суть моей настройки по расширению столбцов типа записи: gist.github.com/Mike-Honey/0a252edf66c3c486b69b - person Mike Honey; 29.09.2019