Для тех, кто в танке
3.38K subscribers
8 photos
2 videos
3 files
196 links
Канал создан для себя, обсуждаем вопросы использования языка M и шарим всякие полезные ссылки.
На вопросы отвечаем в комментах и тут - t.me/pbi_pq_from_tank_chat

Для желающих поддержать канал - https://sponsr.ru/pq_m_buchlotnik/
Download Telegram
Web.Contents и динамические ссылки
#НеВсеЙогуртыОдинаковоПолезны
Всем добра.
Что-то меня работой привалило малость. Но я выбрался и снова научу вас наносить добро и причинять пользу себе и окружающим.
В моем самом втором посте я кое чего обещал, вот сдерживаю слово.
Как формировать веб ссылку с изменяемым параметром чтобы облачная служба PBI не ругалась на динамическое содержимое:

let
// ВОТ ТАК СЕРВИС БУДЕТ РУГАТЬСЯ, если ссылку формировать из динамических параметров в текстовую строку
Source = Xml.Tables(Web.Contents( "http://www.cbr.ru/scripts/XML_daily.asp?date_req=02/03/2002" )),

// ВОТ ТАК ругаться не будет, если Url это константа, а не элемент списка/записи
// при этом в параметр Query можно пихать все что хочется, лишь бы API вас понял
Url = "http://www.cbr.ru/scripts/XML_daily.asp",
Query = [Query = [date_req="02/03/2002"]],
Source1 = Xml.Tables(Web.Contents( Url, Query )),

// ВОТ ТАК тоже ругаться не будет, если Url это константа, а не элемент списка/записи
// но при этом оба элемента записи Query2 можно использовать как динамические параметры
Url2 = "http://www.cbr.ru/scripts/",
Query2 = [ RelativePath = "XML_daily.asp",
Query = [date_req="02/03/2002"]
],
Source2 = Xml.Tables(Web.Contents( Url2, Query2 ))
in
Source2
Здесь продублировал, чтобы удобнее код было на комп переносить.
Как сформировать таблицу на листе Excel с помощью формул DAX
#ВсякоРазно
Всем добра. И снова граждане интересуются странным, а я их учу нехорошему. Расскажу и вам.
Сценарий такой:
- Хочу увидеть на листе Excel таблицу из модели данных.
- Ну, раз хотите...
Итак, можно всегда сослаться на любую таблицу из модели, вот пример. Добавляйте любые столбцы в модели данных с формулами DAX, они тут же появятся в таблице на листе. Хотя таблица в модель вставлена просто из буфера и вообще не имеет внешних источников.
Небольшой гайд как такое сделать:
Если источником таблицы является запрос PQ, тогда просто в меню Загрузить в... кроме галочки о загрузки в модель данных, нужно одновременно выбрать вариант выгрузки еще и в таблицу.
Допустим источник - не запрос PQ,
1. Имеем в модели данных некую таблицу, к которой хотим подключить таблицу с листа и видеть все изменения, допустим в модели она называется Продажи
2. создаем пустой запрос с названием не совпадающим ни с одной из таблиц в модели данных, содержимое запроса не имеет значения, это может быть одна ячейка без значений.
3. результат загружаем и в модель и в таблицу на лист
4. тыкаем ПКМ по выгруженной таблице и выбираем пункт изменить DAX как на картинке
5. указываем имя нужной таблицы из модели
6. удаляем теперь не нужный запрос (он вместе с собой удалит и загруженную ненужную таблицу из модели данных)
7. пользуемся и радуемся.
З.Ы. На все вопросы, типа:
- Алексей, если это такое ненужное и странное, то откуда ты сам про такое знаешь?
Я отвечу:
- Я и сам был когда-то странным, а теперь я странный уж совсем.😜
Пользуйтесь на здоровье. Все картинки и файлы с примерами упомянутые в тексте можно поглядеть пройдя по ссылке.
Попытка намбер Ту
#ВсякиеКоннекторы
Всем бобра!
Итак, по просьбам трудящихся собрал ещё один коннектор, который на входе получает текстовую строку, а на выходе возвращает ее хэш при помощи алгоритма sha256. Использовать его очень легко:
положили в папку согласно инструкции. Запускаем pbi и находим его в разделе other.
Вызов осуществляется такой формулой

=GetHashSHA256.Contents( "text" )

Пока от единственного пользователя поступил отзыв что запрос к api с полученным хэшем работает в редакторе запросов, но не пашет при попытке загрузить результат запроса в модель.
Если кто желает потестировать. Велкам. Архив тут.
З.Ы. На подходе аналогичный коннектор для получения HMAC на базе того же sha256.
Для тех, кто в танке pinned «Попытка намбер Ту #ВсякиеКоннекторы Всем бобра! Итак, по просьбам трудящихся собрал ещё один коннектор, который на входе получает текстовую строку, а на выходе возвращает ее хэш при помощи алгоритма sha256. Использовать его очень легко: положили в папку согласно…»
Попытка намбер три
#ВсякиеКоннекторы
Всем добра!
Как и обещал, выкладываю последний коннектор из нужных для подключения к API с хэшированием ключей через Power BI. Сие творение используется для формирования HMAC (hash-based message authentication code) на основе двух аргументов, а именно текст запроса и ключа авторизации.
Для использования по инструкции из предыдущего поста кладем файл в нужную папку и наблюдаем новый коннектор в PBI Desktop в разделе others.
Для вызова функции используем код:
= GetHMAC.Contents( "text", "key" )
На выходе получаем хэш созданный на базе этих двух аргументов. Далее скармливаем его вашему API согласно приведенной к нему документации.
Функция протестирована, на API Ламоды - работает отлично. В теории должна работать и в DataFlows, но пока не тестировал.
Кстати, перевыложил предыдущий коннектор, исправил в нем блок для прохождения проверки авторизации при настройке подключения через шлюз.
Заранее предупреждаю, что с точки зрения безопасности коннектор не оптимален, т.к. подразумевает передачу логина и ключа в код в открытом виде. В планах исправить данную ситуацию и использовать креды для хранения конфиденциальной информации. Просто пока я так не умею, но обязательно научусь.
Удачных подключений!
Для тех, кто в танке
Как сформировать таблицу на листе Excel с помощью формул DAX #ВсякоРазно Всем добра. И снова граждане интересуются странным, а я их учу нехорошему. Расскажу и вам. Сценарий такой: - Хочу увидеть на листе Excel таблицу из модели данных. - Ну, раз хотите...…
Как сформировать таблицу на листе Excel с помощью формул DAX 2
#ПолезныеСсылки
Всем добра!
В продолжение темы вывода таблиц из модели данных на лист Экселя.
На днях "наше все" @MaximZelensky показал на пальцах как при помощи функции DAX EVALUATE() можно формировать справочники в таблицах на листе, после чего данные попадают в модель данных.
Таким образом, получаем возможность формирования справочников из таблиц фактов без многократного изнасилования их при помощи pq. Такое нельзя потерять, поэтому вот.
Релизная версия коннекторов для хэширования текстовых значений на лету
#ВсякиеКоннекторы
Всем добра.
Итак, завершаю свою эпопею с пользовательскими коннекторами и хэшированием всякого. Допилил оба коннектора для возможности передачи в них данных через учётные данные, залил двуязычную справку по использованию и залил исходники на гит.
Уже имеется как минимум 2 благодарных пользователя, у которых получилось даже с моими черновыми версиями. Это уже релиз.
https://github.com/PooHkrd/GetHashSHA256
https://github.com/PooHkrd/GetHMACwithCreds
Надеюсь кому-то ещё поможет. Инструкции по установке в репозиториях по ссылкам выше.
Все вопросы по использованию направляйте в личку, либо на планету, либо на гитхаб.
Для тех, кто в танке pinned «Релизная версия коннекторов для хэширования текстовых значений на лету #ВсякиеКоннекторы Всем добра. Итак, завершаю свою эпопею с пользовательскими коннекторами и хэшированием всякого. Допилил оба коннектора для возможности передачи в них данных через учётные…»
Ковыряемся в XML структуре файлов xlsx/xlsm/docx
#ВсякоРазно

Всем добра!
До сих пор часто задают вопросы: а может ли PQ вытянуть из книги эксель что-то кроме данных с листа/таблицы/именованного диапазона? Например формат ячейки, или только отфильтрованные пользователем строки? Обычно стандартный ответ: нет. Но это не совсем так, если файл имеет расширение xlsx/xlsm, то по сути своей он представляет собой zip-архив, который можно расковырять и покопаться во внутренней xml-структуре файла. А уж оттуда можно при должном желании и усидчивости вытащить вообще все что угодно. В общем все вышеизложенное для кого-то, наверняка уже не новость. Зато я предлагаю вашему вниманию подборку уже готовых решений, которые могут пригодиться в нашем замечательном ремесле:
- Достаем свойства ячеек от Камрада XL
- Достаем иерархию (вложенность) строк от Максима Зеленского
- Добываем числовые форматы ячеек от Максима Зеленского
- Вытаскиваем только отфильтрованные строки с листа от Михаил L
- Вытаскиваем таблички из docx.
- Сопоставляем какие таблицы/именованные диапазоны на каких листах книги расположены от вашего скромного слуги.
Как правильно работать с API (2 видео от Chris Webb и @IlyaNazarov)
#ПолезныеСсылки

Всем добра.
Набрёл тут случайно на видео 4х летней давности от Криса Вебба про то как правильно использовать Web.Contents при работе с api. Сохраняю здесь. Если с английским не дружите, то запускайте в яндекс.браузере, он сносно переводит на русский в режиме реального времени.
Очень полезный видос.
Также добавляю супер инструкцию от @IlyaNazarov
Как удалить все пустые столбцы с сохранением названий столбцов
#ВсякоРазно

Всем снова здрасьте.
Делюсь функцией, которая удаляет все пустые столбцы в таблице, и при этом не ломает заголовки столбцов. Оказывается это довольно востребованный функционал, особенно при обработке выгрузок из 1С или файлов, заполняемых шаловливыми ручками. Код приведен сразу с примером применения. Саму функцию публиковал тут.
let
FnRemoveEmptyColumns = (tab as table) =>
Table.RemoveColumns(
tab,
Table.SelectRows(
Table.Buffer( Table.Profile( tab ) ),
each [NullCount] = [Count] )[Column] ),
Source = #table( {"a","b","c"}, {{1,1,null},{2,2,null}} ),
Custom1 = FnRemoveEmptyColumns( Source )
in
Custom1
Функция удаления пустых столбцов
#АнатомияФункций - Table.Profile
Всем привет.
Делюсь решением достаточно часто возникающей задачки - а именно: на входе имеем таблицу, часть столбцов которой являются пустыми, соответственно нужно их удалить
Что ж, напишем функцию:
fnRemEmptyColumns = (tbl)=>
[ a = List.Sort(Table.ColumnNames(tbl)),
b = Table.Profile(tbl,{{"tmp", (x)=>true,List.NonNullCount}})[tmp],
c = List.PositionOf(b,0,Occurrence.All),
d = List.Transform(c,(x)=>a{x}),
e = Table.RemoveColumns(tbl,d)
][e]

Разберем по шагам:
a - получаем список имён столбцов и сортируем его по алфавиту
b - изюминка решения - используем Table.Profile(), но со вторым аргументом (более подробно про него - см. сцыль ниже). Т.е. добавляем дополнительную агрегацию "tmp" для всех столбцов ((x)=>true) с функцией List.NonNullCount. И забираем только этот столбец Table.Profile(...)[tmp]
с - в полученном списке определяем позиции пустых столбцов (обращаю внимание на Occurence.All - мощная штука)
d - номера позиций заменяем названиями (тоже приемчик интересный - как вытащить несколько позиций из списка)
e - удаляем - Вуаля! Задачка решена.

Надеюсь было полезно. Всех благ!
@buchlotnik

источник вдохновения:
https://ssbi-blog.de/blog/technical-topics-english/table-profile-and-its-unknown-second-parameter/
Зачем писать (x)=> вместо each _
#АнатомияФункций – основы, List.Transform
Возьмём простую задачку – имеем список текста
lst = {“1”,”2”,”3”} 
и хотим получить список чисел
Решение банальное:
to = List.Transform(lst, each Number.From(_))
Продвинутые могут сказать, что можно проще:
to = List.Transform(lst,Number.From)
И будут совершенно правы, ведь справка нам говорит
List.Transform(list as list, transform as function)
Т.е. вторым аргументом идёт на самом деле ФУНКЦИЯ – мы её указали, и всё заработало, логично.
В документации на язык M указано, что конструкция each _ является синтаксическим сахаром для (x)=> x; давайте попробуем:
to = List.Transform(lst, (x)=>Number.From(x))
Так тоже работает, но зачем нам это? А вот зачем:
Во-первых, можно сообразить, что List.Transform хочет от нас ЛЮБУЮ функцию, которая принимает на вход один аргумент x и что-то с ним делает
f=(x)=>Number.From(x)*8-11
to = List.Transform(lst,f)
т.е. пишем любую нужную нам функцию от одного аргумента, и далее просто её передаем – для отладки удобно, код более читаем.
А во-вторых, понимаем, что таким образом можно вкладывать одну функцию в другую.
Например задачка – из двух списков получить один (причем к элементам первого списка нужно добавить результат сцепки этого элемента с каждым элементом второго), попробуем решить:
let
lst1 = {"1".."3"},
lst2 = {"1".."9"},
lst = List.Transform(
lst1,
(x)=>{x}&List.Transform(
lst2,
(y)=>x&y
)
),
to = List.Combine(lst)
in
to
Посмотрите на шаг lst – в функцию от (x)=> вложена функция от (y)=> . С each _ так сделать не получится (потому что во вложенных each _ от _ никак нельзя отличить))), а теперь мы получили (на самом деле всегда имели, но не использовали) возможность давать переменным осмысленные имена вместо _ и использовать их в нужных местах кода.
В целом синтаксис (x)=> является основным (ещё раз напоминаю – от нас обычно хотят ФУНКЦИЮ преобразования и мы пишем её в явном виде) и по хорошему ставит мозги на место, позволяя в конечном счёте уйти от наклацанных мышкой шагов к написанию кода на функциях.
Ну и пытливые умы конечно скажут – а можно ли в последнем примере из шага lst сделать функцию? Конечно, мы же теперь знаем, как обозначать переменные:
let
f=(x,y)=>List.Transform(
x,
(i)=>{i}&List.Transform(
y,
(j)=>i&j
)
),
lst1 = {"1".."3"},
lst2 = {"1".."9"},
lst = f(lst1,lst2),
to = List.Combine(lst)
in
to
Вроде не сложно, а главное прозрачно – именно поэтому я почти не использую each _ и предпочитаю (x)=>

Надеюсь было полезно, всех благ!
@buchlotnik
#PQ, #pbi
Всем добра! Сегодня покажу как синхронно разворачивать списки из соседних ячеек.
Дано. Получили из API некий JSON который на выходе выдал структуру со вложенными списками как в таблице tbl (см. в коде ниже). Надо развернуть содержимое списков в новые строки, но если мы это сделаем стандартным мышиным способом с разворачиванием списков в строки по очереди то получится декартово произведение, не такое красивое как это но тоже ничего. Что же делать? А на самом деле все довольно просто: надо не разворачивать столбцы, а при помощи функции Table.FromColumns собрать из них готовую таблицу в новом столбце:
let
 tbl = Table.FromRecords (
   {
     [ Company = 1, Phone = { "1".."9" }, Mail = { "1".."6", "", "8".."9" } ],
     [ Company = 2, Phone = { "1".."5" }, Mail = { "2".."6" } ]
   }
 ),
 gentabs = Table.AddColumn(
   tbl,
   "tabs",
   each Table.FromColumns(
     {List.Repeat({[Company]}, List.Count([Phone])),[Phone],[Mail]},
     Table.ColumnNames(tbl)
   )
 ),
 tabs = Table.Combine(gentabs[tabs])
in
   tabs

Но может еще случиться, что столбцов с простым содержимым (не списки) будет много, и тогда использование List.Repeat станет узким местом с точки зрения производительности, да и код сильно потеряет свою наглядность. Но для этого случая можно применить мою любимую функцию Table.FillDown до объединения таблиц и получим точно такой же результат.
let
 tbl = Table.FromRecords (
   {
     [ Company = 1, Phone = { "1".."9" }, Mail = { "1".."6", "", "8".."9" } ],
     [ Company = 2, Phone = { "1".."5" }, Mail = { "2".."6" } ]
   }
 ),
 gentabs = Table.AddColumn(
   tbl,
   "tabs",
   each Table.FromColumns(
     {{[Company]},[Phone],[Mail]},
     Table.ColumnNames(tbl)
   )
 ),
   filled = Table.AddColumn(gentabs, "tabs_", each Table.FillDown([tabs],{"Company"})),
   tabs = Table.Combine(filled[tabs_])
in
   tabs
Зачем нужен [ ][ ] когда есть let in
#АнатомияФункций - основы
Возьмем абстрактный как сферический конь в вакууме запрос:
let
a = 8,
b = 16,
c = a*b,
d = a/b
In
d
На выходе, разумеется, получим 0,5.
Фишка в том, что в недрах спецификации на язык написано «Выражение let можно рассматривать как синтаксический сахар для неявного выражения записи».
Давайте проверим:
[   a = 8,
b = 16,
c = a*b,
d = a/b ][d]
Выдаст то же самое.
Почему? Запись – это набор полей (её ещё представляют в виде строки таблицы), но кто сказал, что не бывает вычисляемых полей? Пример выше как раз и показывает, что вполне себе бывают.
Соответственно, конструкция [ ][d] – это обращение к конкретному полю записи (аналогично in d).
Зачем нам всё это? Ну для начала – а если мы хотим вывести и произведение, и частное:
[   a = 8,
b = 16,
c = a*b,
d = a/b ][[c],[d]]
Сразу даст [c=128, d=0,5]
На мой взгляд это несколько проще и понятнее, чем
let
a = 8,
b = 16,
c = a*b,
d = a/b
in
[c=c, d=d]
Хотя эффект будет тем же.
А дальше – вычисления бывают разной степени сложности, иногда отдельный этап требует нескольких связанных вычислений и тогда приходится, либо вкладывать let in друг в друга:
let
f=(x)=>let
a=Text.From(x),
b=Text.ToList(a),
c=List.Distinct(b),
d=b=c
in
d,
from = List.Numbers(0,1024),
to = List.Select(from,f)
in
to
либо можно упаковать связанные шаги в запись:
let
f=(x)=>[a=Text.From(x), b=Text.ToList(a), c=List.Distinct(b), d=b=c][d],
from = List.Numbers(0,1024),
to = List.Select(from,f)
in
to
По мне второй вариант проще, хотя это дело вкуса.
Синтаксис через записи обычно более компактный и позволяет визуально локализовать связанные вычисления внутри [ ].
На скорости никак не сказывается, поэтому как удобнее - решать вам.

Надеюсь было полезно, всех благ!
@buchlotnik
List.Generate, осмысленный и пощадный
#АнатомияФункций – List.Generate
Думаю несложно догадаться, что для начала отправлю читать спецификацию, ибо там сказано:
List.Generate( initial as function,
condition as function,
next as function,
optional selector as nullable function)
Итак, функция, у которой в качестве аргументов выступают четыре функции. Уже страшно.
Для прозрачности далее буду использовать синтаксис с (x)=> (подробнее читаем тут).
Для начала сгенерим список от 1 до 10 (если что я знаю про {1..10} но у нас тут про другое:
List.Generate(  ()=>[i=1],
(x)=>x[i]<=10,
(x)=>[i=x[i]+1],
(x)=>x[i])
initial - просто даёт нам начальное значение 1, но в более сложных случаях мы будем использовать промежуточные вычисления, там нам потребуются вычисляемые поля, поэтому сразу пишем как надо – т.е. через запись
condition – это функция, вопрос в том от чего? Как ни странно, от текущего состояния. Таким образом, x[i] – это текущее состояние счётчика и мы проверяем не превысило ли оно 10
next – а эта функция говорит, что необходимо сделать с текущим состоянием. Поскольку на входе у нас была запись [i=…], на выходе тоже будет она [i=x[i]+1] – т.е. в поле i записали текущее, увеличенное на единицу
selector – нужен, чтобы выводить только интересующие нас поля, в данном случае x[i].
Да, немножко громоздко, но теперь давайте выведем не просто числа от 1 до 10, а накопленную сумму (собственно, для этой задачки функция обычно и используется):
List.Generate(  ()=>[i=1, s= i],
(x)=>x[i]<=10,
(x)=>[i=x[i]+1, s=x[s]+i],
(x)=>x[s])
Теперь
initial – это уже связанные поля [i,s], причем s вычисляется на основе i (про [ ] можно почитать тут)
condition – такой же
next – теперь в записи i вычисляется на основе предыдущего значения прибавлением единицы, а s – прибавлением i к предыдущему значению
selector – на выходе нас интересует именно s, так что счётчик мы используем, но не выводим.
Ну и что-нибудь похожее на боевой пример:
let
tbl = Table.FromRecords({[a="Вася", b = 2],
[a="Коля", b = 4],
[a="Петя", b = 8],
[a="Евлампий", b = 16]}),
lst = List.Buffer(tbl[b]),
n = List.Count(lst),
gen = List.Generate(()=>[i=0,s=lst{i}],
(x)=>x[i]<n,
(x)=>[i=x[i]+1,s=x[s]+lst{i}],
(x)=>x[s]),
to = Table.FromColumns(Table.ToColumns(tbl)&{gen},Table.ColumnNames(tbl)&{"cusum"})
in
to
Разбираем по шагам
tbl - на вход поступила таблица, допустим нам нужна накопленная сумма по столбцу b
lst – получаем столбец в виде списка (обращаю внимание на List.Buffer – здесь оно нужно, потому как к этому списку мы будем многократно обращаться
n – посчитали число элементов в списке (нам же нужно ограничение для счётчика)
gen – генерация столбца, фактически как в примере выше, но с оговорками:
initial - i=0 – потому как нумерация элементов в списке начинается с нуля, s=lst{i} – в сумму идёт i-ый элемент списка lst
condition – здесь условие x[i]<n, потому как нумерация была с нуля и <= даст нам на один элемент больше, чем надо
next – увеличиваем счётчик на 1, к сумме прибавляем i-ый элемент
selector – нас интересует только сумма
to – а вот теперь нужно прилепить новый столбец к таблице, для этого :
Table.ToColumns(tbl)&{gen} - получаем список имеющихся столбцов и добавляем к нему сгенерированный
Table.ColumnNames(tbl)&{"cusum"} – получаем список имен столбцов и добавляем к нему нужно е имя
Ну и упаковываем это всё через Table.FromColumns
Вуаля – столбец накопленной суммы добавлен!
@buchlotnik
Я знаю, что примеры в справке попроще и написаны через each, и код выше можно было оформить проще, но в общем виде оно выглядит именно так – запись на входе, функции от текущего состояния – одна для проверки, вторая для изменения, и функция вывода (которая вообще не обязательная, но вот почему-то в реальности почти всегда используется).

Надеюсь было полезно, всех благ!
Замыкания, или что за зверь (y)=>(x)=>…
#АнатомияФункций – основы
Всем привет, с оказией хочу разобрать такую штуку, как замыкания (closure).
Суть вот в чём. Напишем простой пример:
let
f=(x,y)=>x/y,
a=2,
b=5,
to=f(a,b),
in
to
Очевидно, на выходе получим 0,4
Но можно написать и по-другому:
let
f=(x)=>(y)=>x/y,
a=2,
b=5,
to=f(a)(b)
in
to
На выходе получим то же самое. Но в этой ситуации мы передали аргументы функции последовательно. И разрыв мозга наступает здесь:
let
f=(x)=>(y)=>x/y,
a=2,
b=5,
c=f(a),
to = c(b)
in
to
Тоже выдаст 0,4! Теперь давайте разбираться:
c – передали функции f только один аргумент, a подставилось вместо x и на выходе мы получили… с=(y)=>2/y, т.е. ФУНКЦИЮ
to
– этой новой функции передали b, и уже она выдала конечный результат.
Таким образом, замыкание (кложура!) – это упаковка функции в функцию (это упрощение - да простят меня функциональные теоретики). Думаю, у вас один вопрос – зачем оно надо?! А вот зачем. Разберем немного упрощенный боевой пример:
let
from = Table.FromRecords({ [n="Вася",a=90,b=70,c=50],
[n="Коля",a=120,b=130,c=110],
[n="Петя",a=75,b=80,c=130]}),
nms= Table.ColumnNames(from),
lst=List.Skip(nms),
f=(y)=>(x)=>[ a = Record.Field(x,y) ,
b = if a < 80
then "ниже предела"
else if a > 120
then "выше предела"
else "ок"][b],
g=(x)=>"Проверка "&x,
acc = List.Accumulate(lst,from,(s,c)=>Table.AddColumn(s,g(c),f(c))),
to = Table.SelectColumns(acc,{nms{0}}&List.Combine(List.Transform(lst,(x)=>{x,g(x)})))
in
to
from – на вход подана таблица с несколькими столбцами числовых значений, для каждого столбца нужно добавить столбец с оценкой (проверить нахождение значения в диапазоне от 80 до 120). (а самый жесткий вариант у меня был - 34 столбца и ABC-анализ)
nms – получили все имена,
lst – получен список имён столбцов, по которым нужна оценка
acc – ну самое разумное пройтись по списку lst - List.Accumulate(lst… и добавить для каждого оценку Table.AddColumn(…; только есть проблема – для функции Table.AddColumn мы должны передать функцию от одного аргумента, а именно – от записи (текущей строки) в таблице. А нам нужно обращаться к разным полям, вот так и возникает конструкция
f=(y)=>(x)=>[   a = Record.Field(x,y) , b = if a < 80 then "ниже предела" else    if a > 120 then "выше предела" else "ок"][b],
Т.е. сначала мы функции f будем передавать y – и это будет имя нужного поля записи, и на выходе получать функцию для обработки именно этого поля, что мы и используем в аккумуляторе:
(s,c)=>Table.AddColumn(s,g(c),f(c))
Т.е. добавить к таблице s оценку по полю c - f(c) и назвать столбец g(c)
Зачем надо было писать ещё и функцию g? Да просто преобразование это нужно ещё и в последнем шаге – так немножко короче. Мы же хотим оценку получить рядом, а не в конце таблицы, потому-то на шаге to мы и выбираем столбцы в интересующем нас порядке.
Собственно, всё. Замыкания встречаются нечасто, но бывают просто незаменимы.

Надеюсь было полезно, всех благ!
@buchlotnik
Коннектор к папке OneDrive Personal
#ВсякиеКоннекторы
Всем добра!
К новому году небольшой подарочек. С наступлением на наши компьютеры ОС Windows 10/11 все больше граждан получают в свое распоряжение облачный сервис OneDrive Personal. В отличие от такого же облака для бизнеса для него отсутствует встроенный в PQ коннектор подключения к папке, чтобы динамически обновлять данные из добавляемых в неё файлов. Тем не менее сама возможность такого забора со стороны Microsoft предусмотрена при помощи API. Ниже прилагаю код функции, которая по аналогии с функцией Folder.Files точно также показывает вам какие файлы имеются в папке на облаке и позволяет работать с их содержимым. Как использовать? Заходим в облако, тыкаем правой кнопкой по папке и выбираем пункт "поделиться", в появившемся меню жмем "копировать ссылку" и уже эту ссылку скармливаем моей функции. Тип авторизации выбираем анонимный. Пользуйтесь на здоровье.
OneDriveFolderFiles = (url)=>
let
     //Функция перекодирования ссылки в понятный формат для API
  fx = (t)=> Binary.ToText( Text.ToBinary( t, TextEncoding.Utf8 ), BinaryEncoding.Base64 ),
API_URL = "https://api.onedrive.com/v1.0/shares/"
     //тащим путь из параметра с адресом к общей папке из облака Onedrive
  FolderUrl = url,
     //преобразовываем ссылку для получения токена для API
  UrlToBase64 = fx( FolderUrl ),
     //заменяем всякое согласно инструкции по ссылке:
     //https://docs.microsoft.com/ru-ru/onedrive/developer/rest-api/api/shares_get?view=odsp-graph-online#encoding-sharing-urls
  Replaced = Text.Replace( Text.Replace( Text.TrimEnd( UrlToBase64, "=" ), "/", "_" ), "+", "-" ),
     //формируем итоговый текстовый параметр для передачи в RelativePath
  EncodedPath = "u!" & Replaced & "/root/children",
     //тащим содержимое папки из API
  Source = Json.Document(Web.Contents( API_URL, [RelativePath = EncodedPath] ) ),
     //преобразовываем полученный JSON в табличку с содержимым папки
  TableFromRecords = Table.FromRecords( Source[value] )[[name],[webUrl]],
     //добавляем столбец с текстовыми параметрами для передачи в RelativePath
  AddColEncodedPaths = Table.AddColumn(
     TableFromRecords,
     "GetRelativePath",
     each "u!" & fx([webUrl]) & "/root/content"
  ),
     //достаем бинарники по сформированным ссылкам, дальше по аналогии как с обычными файлами с диска
  GetBinaries = Table.AddColumn(
     AddColEncodedPaths,
     "Content",
     each Web.Contents(API_URL, [RelativePath = [GetRelativePath]] ),
     Binary.Type
  )
in
  GetBinaries

З.Ы. Если для работы нужны всякие дополнительные свойства файлов или еще какие-то атрибуты, то достаточно в шаге TableFromRecords удалить вот это в квадратных скобках: [[name],[webUrl]]. Тогда увидите все, что может показать API про вашу папку.
Измерение скорости выполнения запросов
#ПолезныеСсылки
Всем привет!
Имеется небольшая надстройка, предназначенная для оценки скорости выполнения запросов PQ в среде MS Excel. Вместе со справкой сложена на гитхаб:
https://github.com/buchlotnik/Merka
Надеюсь, будет полезна.
Всех благ!
@buchlotnik