Это продолжение перевода книги Грегори Декер, Рик де Гроот, Мелисса де Корте. Полное руководство по языку М Power Query. В предыдущих главах мы рассмотрели общие сведения о языке M, интерфейс Power Query, получение доступа к данным и объединение данных. Теперь мы переходим к элементам языка M. И начнем со значения. Такой выбор естественно вытекает из предыдущей главы, поскольку каждый раз, когда мы извлекаем данные, мы получаем значение.
Мои комментарии набраны с отступом.
Предыдущая глава Содержание Следующая глава
Если вы думаете о Power Query и M как о лаборатории, в которой очищаете и формируете данные для последующего моделирования и создания отчетов, то значения – это атомы кода M, мельчайшие отдельные единицы. Эти значения объединяются в выражения, что делает их молекулами нашей лаборатории. Одно или несколько выражений могут быть объединены в запрос.
Скачать заметку в формате Word или pdf
В этой главе мы рассмотрим значения, и то, как тип значения указывает, что вы можете (или не можете) делать со значением. Мы также рассмотрим базовую структуру выражений и запросов, и то, как операторы и управляющие структуры связывают значения внутри выражений.
Мы рассмотрим следующие темы:
- Знакомство с типами значений
- Выражения
- Операторы
- Структура управления
- Перечисления (enumerators)
Знакомство с типами значений
Язык M распознает 15 типов значений, распределенных по нескольким категориям:
Поскольку в языке М большое значение имеет типизация данных, я стараюсь слово тип использовать только в этом контексте. В остальных случаях использую вид.
Примитивные значения – отдельные элементы данных, которые язык M рассматривает как единое целое при обработке и выполнении операций. В контексте DAX они называются скалярными значениями.
Структурированные значения могут содержать несколько значений с определенной структурой. Допустимо, что структурированное значение содержит только одно значение или не содержит значений вовсе. Существует три типа структурированных значений: списки, записи и таблицы. Все они будут подробно рассмотрены в главе 6 Структурированные данные. Структурированные значения могут содержать другие структурированные значения. Они будут рассмотрены в главе 8 Работа с вложенными структурами.
Значения функции. Функция сопоставляет набору значений аргументов одно значение – результат функции. В М более 700 встроенных функций, и пользователи могут разрабатывать собственные функции для повторного использования в иных запросах. Разработка пользовательских функций будет рассмотрена в главе 9 Параметры и пользовательские функции.
Значения типов. Их можно рассматривать как метазначения, которые предоставляют информацию о типе данных некоего значения. Классификация типов подробно рассматривается в главе 5 Общие сведения о типах данных.
Следующая таблица дает краткое описание 15 типов значений в четырех категориях. Термин литерал относится к представлению значения, как оно написано непосредственно в коде.
Chat GPT поясняет. Литерал – фиксированное значение, которое разработчик явно указывает в выражении: числа, строки, логические значения…
Рис. 4.0. Виды значений в M
Каждый из 15 типов значений имеет уникальную структуру и работает с различными функциями. Тип type (последняя строчка таблицы) может сбивать с толку. Мы его позже обсудим, а сейчас перейдем к более подробному рассмотрению отдельных типов значений.
Двоичные значения
Двоичные значения хранятся в виде байтов (последовательностей нулей и единиц). Как правило, это специальные файлы, читаемые программами, например изображения. Power Query обрабатывает двоичные данные из файлов (функция File.Contents), из Интернета (Web.Contents) и через пользовательские коннекторы.
Есть еще одна малоизвестная опция по созданию двоичных данных. Откройте новый файл Excel, пройдите Данные –> Получить данные –> Запустить редактор Power Query. В редакторе Power Query пройдите Главная –> Новый запрос –> Введите данные. В окне Создание таблицы введите:
Рис. 4.1. Создание двоичного значения с помощью опции Введите данные
Нажмите Ok. Откройте Расширенный редактор, изучите код М:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Запрос 4.1 let Источник = Table.FromRows( Json.Document( Binary.Decompress( Binary.FromText( "i45WcirKTMxT0lEKDXZUitWJVnIvSk1H4vqm5mQWFycCRfxcXcAiQZnJ2TBuLAA=", BinaryEncoding.Base64 ), Compression.Deflate ) ), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Autor = _t, Country = _t] ) in Источник |
Когда значения вводятся пользователем в окне Создание таблицы, они преобразуются в двоичный код в момент сохранения значения. В коде 4.1 данные преобразуются в двоичный код, а затем кодируются как текст.
Chat GPT поясняет. В этом контексте важно отметить, что язык M обрабатывает данные, вводимые через интерфейс Создание таблицы, таким образом, чтобы сохранить их целостность и точность. Преобразование в двоичный код и последующее кодирование в текст помогает обеспечить корректное хранение и обработку данных в последующих операциях.
Бинарное значение в кодировке Base 64 можно создать функцией #binary:
1 2 |
Синтаксис 4.2 #binary( value as any ) as any |
Два литерала в строке кода, создают одно и то же двоичное значение:
Рис. 4.2. Создание двоичных значений из списка чисел и текста
Связанные функции
Существует 40 двоичных функций в семи категориях:
- Буферизация и сжатие: буферизуют двоичные значения в памяти, а также сжимают и распаковывают двоичные значения.
- Порядок байтов: определяют порядок байтов двоичного значения.
- Создание и преобразование: создают и преобразуют двоичные значения в другие типы значений и обратно.
- Информационные: возвращают информацию о длине и предполагаемом типе содержимого двоичных значений.
- Чтение: определяют, как должны считываться различные двоичные значения. В эту категорию входит более половины всех двоичных функций
- Преобразование: объединяют, разделяют и извлекают части двоичных значений.
- Просмотр: используются для создания и управления представлениями двоичных данных без изменения базового двоичного содержимого.
Особое внимание при создании двоичных значений следует уделять уникальной структуре данных. Изображения, текст, числовые данные и многие другие виды данных могут обрабатываться как двоичные данные в M. Понимание того, как каждый из этих различных видов значений структурирован, кодируется и декодируется, имеет важное значение для успешной работы с двоичными значениями.
Обработка двоичных значений в пользовательских коннекторах рассматривается в главе 16 Включение расширений.
Комментарий Chat GPT. Текстовые значения в кодировке Base64 часто используются для передачи данных в формате, который можно легко включить в текстовые документы, такие как XML или JSON. В языке M Power Query предоставляются функции для работы с такими закодированными значениями.
Кодировка Base64 – это метод представления двоичных данных в виде ASCII-строк. Она используется для передачи данных через текстовые системы, которые могут не поддерживать двоичные данные напрямую. Base64 использует 64 символа, чтобы представить данные (буквы латинского алфавита в верхнем и нижнем регистрах, цифры и два дополнительных символа, обычно + и /).
Основные функции для работы с Base64 в языке M:
Binary.ToText – преобразует двоичные данные в текст в указанной кодировке:
1 2 3 4 5 6 |
Запрос 4.3 let binaryData = #binary({0x01, 0x02, 0x03}), base64Text = Binary.ToText(binaryData, BinaryEncoding.Base64) in base64Text |
В этом примере двоичные данные #binary({0x01, 0x02, 0x03}) преобразуются в строку Base64.
Text.ToBinary – преобразует текстовые данные в двоичные с использованием указанной кодировки:
1 2 3 4 5 6 |
Запрос 4.4 let base64Text = "AQID", binaryData = Text.ToBinary(base64Text, BinaryEncoding.Base64) in binaryData |
В этом примере строка Base64 «AQID» преобразуется обратно в двоичные данные.
Кодирование строки в Base64:
1 2 3 4 5 6 7 |
Запрос 4.5 let originalText = "Hello, world!", binaryData = Text.ToBinary(originalText, TextEncoding.Utf8), base64Text = Binary.ToText(binaryData, BinaryEncoding.Base64) in base64Text |
Этот код сначала преобразует строку «Hello, world!» в двоичный формат, используя кодировку UTF-8, а затем кодирует ее в Base64.
Декодирование строки из Base64:
1 2 3 4 5 6 7 |
Запрос 4.6 let base64Text = "SGVsbG8sIHdvcmxkIQ==", binaryData = Text.ToBinary(base64Text, BinaryEncoding.Base64), originalText = Binary.ToText(binaryData, TextEncoding.Utf8) in originalText |
Этот код декодирует строку Base64 «SGVsbG8sIHdvcmxkIQ==» обратно в двоичные данные, а затем преобразует их в текст с использованием кодировки UTF-8.
Binary.Length – возвращает длину двоичных данных в байтах:
1 2 3 4 5 6 |
Запрос 4.7 let binaryData = #binary({0x01, 0x02, 0x03}), length = Binary.Length(binaryData) in length |
Binary.InferContentType – возвращает предполагаемый тип содержимого для двоичных данных. Эта функция может быть полезна для определения вида файла или данных, закодированных в двоичном формате.
1 2 3 4 5 6 |
Запрос 4.8 let binaryData = #binary({0x01, 0x02, 0x03}), contentType = Binary.InferContentType(binaryData) in contentType |
Семейство значений Дата/время
Многие значения данных связаны с конкретными моментами времени. Независимо от того, анализируете ли вы закономерности в продажах, улучшение возможностей моделей искусственного интеллекта, изменение интенсивности штормов или результаты олимпийского забега на 100 метров, знание того, когда происходят эти события, очень важно. Поэтому группа значений, которая помогает нам понять, когда что-то произошло, является одним из наиболее значимых и часто встречающихся видов данных в M.
Кроме того, умелое обращение с этими значениями является важным навыком для разработчиков Power BI, поскольку любой анализ с временным элементом требует таблицы Даты/Календаря в модели данных Power BI. Если вы не получаете эту таблицу из хранилища данных или другого источника, настоятельно рекомендуем создать ее в Power Query с использованием языка M. Этот подход считается лучшей практикой. Мы глубоко окунемся в работу с датами, временем и длительностями, включая такие продвинутые темы, как анализ временных данных, в главе 10 Работа с датой, временем и длительностью.
Поскольку все эти функции помогают охарактеризовать временной аспект данных, в этом разделе мы рассматриваем значения Date, Time, DateTime, DateTimeZone и Duration вместе. Обсуждение вычислений с этими значениями также можно найти в разделе, посвященном операциям.
Значения Date
Даты часто являются наиболее подробным уровнем, на котором собираются данные. Таким образом, если вы поймете, как Power Query обрабатывает даты, и научитесь настраивать их с помощью M, вам будет легче обрабатывать другие типы значений в этой категории. Это связано с тем, что все они основаны на общих концепциях и шаблонах.
Значения дат могут быть созданы с использованием следующей структуры:
1 2 3 4 5 6 |
Синтаксис 4.9 #date( year as number, month as number, day as number, ) as date |
Входные данные – целые числа в диапазонах: 1 ≤ year ≤ 9999, 1 ≤ month ≤ 12, 1 ≤ day ≤ 31, иначе выражение вернет ошибку. Литерал #date чувствителен к регистру и не имеет альтернативного написания, поэтому #Date и #DATE вернет ошибку. Корректный синтаксис #date(2023, 11, 1) возвращает значение 1 ноября 2023 года.
Связанные функции
Существует около 60 функций в следующих категориях:
Функции Date.Add (например, Date.AddMonths) – принимают значение Date, DateTime или DateTimeZone, а также число, которое указывает сколько периодов времени добавить к исходному значению. Число может быть отрицательным, чтобы переместиться назад во времени.
Функции Date.Component – принимают значение Date, DateTime или DateTimeZone и возвращают указанный компонент в виде числа (например, Date.Month) или текстового значения (например, Date.MonthName).
Функции Date.Start и Date.End (например, Date.EndOfMonth) – принимают значение Date, DateTime или DateTimeZone и возвращают тот же тип значения. Например, Date.EndOfMonth(#date(2023, 11, 1)) возвращает 30 ноября 2023 года.
Функции Date.IsIn – проверяют, попадает ли заданная дата в определенный интервал времени или соответствует определенному критерию; принимают значение Date, DateTime или DateTimeZone и возвращают true или false. Например, функция Date.IsInPreviousYear(#date(2023, 11, 1)) при проверке в момент перевода главы (июнь 2024 г.) вернет true, так как 1 ноября 2023 года относится к предыдущему году.
Функции Date.To и Date.From – преобразуют значение Date в значение другого типа или извлекают значение даты из другого типа значения. Например, функция Date.ToText(#date(2023, 11, 1)) возвращает 01.11.2023 на компьютере переводчика, по умолчанию отражая локальные настройки Windows (подробнее о влиянии региональных настроек см. главу 5 Общие сведения о типах данных). Функция Date.ToText имеет необязательный параметр, который позволяет указать формат выходных данных. Например, Date.ToText(#date(2023, 11, 1), "dd.MMM.yy") возвращает на ПК переводчика 01.ноя.23.
Замечание
Особое внимание при использовании значений даты следует уделить системе дат в Power Query. Структура #date(year, month, day) является основным способом указания дат в Power Query. При этом в Power Query (как в Excel, так и в Power BI Desktop) даты вычисляются как целые числа, представляющие общее количество дней, прошедших с 30 декабря 1899 года.
Это оказывается полезным для функций, которые не принимают даты в качестве входных данных, но принимают числа. Например, выражение генерации списка с использованием формы A..B, которое будет подробно рассмотрено в главе 6, вернет ошибку, если вы введете входные данные как даты. Однако, если сначала преобразовать даты в их порядковые номера, выполнить вычисления с целыми числами, а полученный список преобразовать обратно в даты, это сработает:
Рис. 4.3. Генерация списка дат с использованием формы A..B
На этом мы завершаем наш обзор значений Date. Больше информации в главе 10 Работа с датой, временем и длительностью.
Значения времени
Значения времени в M представляют собой определенный момент в сутках, выраженный с помощью 24-часового цикла. Значения времени могут быть созданы с помощью структуры:
1 2 3 4 5 6 |
Синтаксис 4.10 #time( hour as number, minute as number, second as number, ) as date |
Входные данные имеют ограничения, иначе выражение вернет ошибку: 0 ≤ hour ≤ 24, 0 ≤ minute ≤ 59, 0 ≤ second < 60. hour и minute – целое, second – десятичное. Если hour = 24, то minute = second = 0. Выражение #time чувствительно к регистру. Примеры корректного синтаксиса:
1 2 3 4 |
Код 4.11 #time( 7, 23, 30) // возвращает 7:23:30 AM #time(23, 59, 59) // возвращает 11:59:59 PM #time(24, 0, 0) // возвращает 12:00:00 AM |
Связанные функции
В M 9 функций времени в трех категориях:
Функции Time.To и Time.From – преобразовывают значение времени в запись и текстовое значение. Обратное преобразование доступно для некоторых типов значений.
Time.EndOfHour и Time.StartOfHour – возвращают время начала и окончания часа, заданного значения времени.
Time.Hour, Time.Minute и Time.Second – извлекают компоненты часа, минуты и секунды в виде числовых значений из значения времени.
Замечания
#time не содержит никакой информации о часовом поясе. Если это нужно используйте #datetimezone. При необходимости, компонент времени может быть извлечен из этого значения.
Power Query поддерживает отслеживание и вычисление времени с точностью до микросекунд.
Значения DateTime
Значения DateTime – конкатенация значений Date и Time. Значения DateTime можно создать с помощью структуры:
1 2 3 4 5 6 7 8 9 |
Синтаксис 4.12 #datetime( year as number, month as number, day as number, hour as number, minute as number, second as number ) as datetime |
Входные данные имеют описанные выше ограничения. Выражение #datetime чувствительно к регистру. Например, #datetime( 2023, 11, 1, 7, 15, 0 ) вернет значение 01.11.23 7:15:00.
Связанные функции
Представлено 25 функций, большинство из которых соответствуют категориям Component, IsIn, To/From, как описано выше для функций #date. Однако есть также три функции DateTime, которые не имеют аналогов в функциях, связанных с #date.
DateTime.LocalNow и DateTime.FixedLocalNow возвращают значение datetime, равное текущим дате и времени в системе. Разница в том, что первое может изменяться по ходу выполнения выражения, а второе остается постоянным. Поскольку функции возвращают значение datetime, связанное с системой, в которой выполняется запрос, это означает, что если ваш набор данных обновляется в Австралии, но вы находитесь в Канаде, функция выдаст время Австралии. Если вы хотите вернуть канадское время, вам нужно добавить смещение часового пояса, чтобы настроить время с учетом разницы часовых поясов.
DateTime.AddZone принимает значения #datetime, часы (number) и минут (nullable number), и возвращает значение #datetimezone, чтобы добавить часовой пояс к #datetime для настройки на местное и/или летнее время.
Замечания
Значения DateTime используйте с осторожностью, так как они имеют два недостатка по сравнению со значениями Date и Time:
- Значения datetime значительно увеличивают количество уникальных элементов столбцов, содержащих datetime, что может привести к увеличению размера файла, необходимого для хранения всех этих уникальных значений, а также отрицательно сказаться на производительности и использовании памяти. В большинстве случаев лучше разделить дату и время на отдельные поля date и time, каждое из которых будет соединено отношением «один ко многим» с отдельными таблицами даты и времени в моделях данных Power BI (или Excel).
- Значения DateTime не содержат сведений о часовых поясах. Т.е. при сравнении значения DateTime в разных местоположениях, вам нужно будет учитывать разницу в часовых поясах.
Значения DateTimeZone
Значения DateTimeZone по сравнению с DateTime дополнительно включают смещение часового пояса, выраженное в часах и (возможно) минутах, которое дает разницу между местным временем и всемирным координированным временем (Coordinated Universal Time, UTC).
UTC является стандартом, основанным на атомном времени и используется для навигационных и научных измерений времени. Он заменил предыдущий стандарт, среднее время по Гринвичу, и все часовые пояса могут быть выражены в виде смещения от UTC (подробнее в главе 10 Работа с датой, временем и длительностью).
Значения DateTimeZone можно создать с помощью структуры:
1 2 3 4 5 6 7 8 9 10 11 |
Синтаксис 4.13 #datetime( year as number, month as number, day as number, hour as number, minute as number, second as number, offsetHours as number, offsetMinutes as nullable number ) as datetimezone |
Входные данные в дополнение к предыдущим ограничениям включают: -14 ≤ offset-hours + offset-minutes / 60 ≤ 14. Выражение #datetimezone чувствительно к регистру. Выражение #datetimezone( 2023, 11, 1, 7, 15, 0, -5, 0 ) возвращает значение 01.11.2023 7:15:00 -05:00.
Связанные функции
В Power Query 15 функций DateTimeZone, большинство из которых соответствуют категориям Component, LocalNow и To/From описанные выше для функции #datetime. Есть также четыре оригинальные функции:
DateTimeZone.UtcNow и DateTimeZone.FixedUtcNow возвращают установленное в системе значение DateTimeZone. Первое изменяется в ходе выполнения выражения, второе остается постоянным.
DateTimeZone.RemoveZone удалить компонент часового пояса, преобразуя значение в тип DateTime. DateTimeZone.SwitchZone переключает значение смещения времени в соответствии с другим поясом.
Замечания
Рекомендации по работе со значениями DateTimeZone:
- Смещение часового пояса в значении DateTimeZone не корректируется автоматически для летнего времени, к тому же разные страны, могут изменять его в разные даты. Мы обсудим решение этой проблемы в главе 10.
- При преобразовании значений DateTimeZone важно учитывать, как M обрабатывает преобразование смещения. Например, когда значение DateTimeZone преобразуется в значение DateTime, смещение часового пояса отбрасывается, что переводит время в UTC.
Значения длительности
Значения длительности в M используются для представления отрезков времени, выраженных в днях, часах, минутах и секундах (с долями). Структура для создания длительности:
1 2 3 4 5 6 7 |
Синтаксис 4.14 #duration( days as number, hours as number, minutes as number, seconds as number, ) as duration |
Например:
1 2 3 4 |
Код 4.15 #duration(0,1,2,3) // возвращает 0.01:02:03 #duration(9,8,7,6.54321) // возвращает 9.08:07:06.5432100 #duration(3,32,74,82) // возвращает 4.09:15:22 |
Связанные функции
Существует 12 функций в трех категориях:
Компоненты – извлекают значение дня, часа, минуты или секунды из длительности.
Вычисления – вычисляют дни, часы, минуты или секунды в заданном значении длительности.
Создание и преобразование – преобразуют длительность к другому типу значений и обратно.
Замечания
Длительность может принимать отрицательные значения, как при создании, так и при вычислении. Поскольку длительности представляют собой отрезки времени, а не конкретные моменты времени, к ним применим более широкий диапазон арифметических операций.
Логические значения
Логические значения (true и false) необходимы для выполнения логических операций с данными, а также для управления потоком данных с помощью управляющих структур.
Есть три функции в категории преобразования: Logical.From преобразует любое значение, которое может быть оценено как истинное или ложное, в соответствующее логическое значение; Logical.FromText создает логическое значение из текстовых значений true и false; Logical.ToText возвращает текст true или false на основе логического значения.
Замечания
Логические значения имеют несколько уникальных шаблонов преобразования.
false приравнивается к 0, true – к 1:
1 2 |
Number.From( true ) // возвращает 1 Number.From( false ) // возвращает 0 |
При преобразовании чисел в логические значения 0 преобразуется в false, любое другое число преобразуется в true. Например:
1 2 3 |
Logical.From( 0 ) // возвращает false Logical.From( -2 ) // возвращает true Logical.From( "csv") // возвращает Expression.Error: Не удалось преобразовать в логический тип |
Вычисления по короткой схеме. Как и многие другие языки, M использует этот метод вычисления логических выражений, когда второй аргумент оценивается только в том случае, если первого логического оператора недостаточно для определения значения выражения. Это означает, что в логическом выражении, использующем and, если первое условие ложно, второе условие вычисляться не будет. Для выражения or, если первое условие истинно, структура завершится до вычисления второго условия.
Это может привести к проблемам, когда второе условие задано таким образом, что возвращает ошибку, что может долго оставаться незамеченным. Например:
1 2 3 4 5 6 7 |
Запрос 4.16 let x = true, xNum = Number.From(x), Result = if (xNum < 1) and (Value.Divide(Number.PI, x)) <= 10 then true else false in Result |
Здесь Number.PI – константа, которая возвращает число π с точностью до 16 знаков после запятой. Number.From(true) = 1, таким образом, первое условие (xNum < 1) вернет false. Учитывая, что Result требует, чтобы оба условия в выражении были истинными , управляющая структура завершает работу после вычисления первого условия и вернет false для всего выражения.
Поскольку x – логическое значение, второе условие (Value.Divide(Number.PI, x)) всегда вернет ошибку, так как в нем пытаются разделить число пи на логическое значение (x), а не на числовой эквивалент x (определяемый в этом выражении как xNum). Тем не менее, M позволит выполнить код, так как выражение имеет правильный синтаксис, и проверки первого условия достаточно для оценки выражения Result. Так что ошибка останется незамеченной.
В главе 5 мы рассмотрим методы работы с типам данных, которые помогут предотвратить проблемы такого рода.
Значение null
Значение null используется для представления отсутствия значения или неопределенного значения или неизвестного состояния (т.е. в случае отсутствующего значения). Значение null записывается с использованием литерала null. Важно отметить, что null отличается от 0. Первое – это отсутствие значения, в то же время 0 – конкретное числовое значение, как 1 или 732.
Вот пример того, где вы можете это использовать:
1 |
if [Value] = null then "NA" else [Value] |
Не существует функций, которые были бы напрямую связаны со значениями null, но функции для многих других значений явно учитывают значения null.
Замечания
Сравнение на равенство: null имеет некоторые уникальные свойства относительно того, как оцениваются выражения с оператором равенства. Например, null равен только самому себе:
1 2 3 4 5 6 7 |
null = null // возвращает true null = true // возвращает false null = false // возвращает false null <= null // возвращает null null >= null // возвращает null null <> null // возвращает null null < 0 // возвращает null |
Конкатенация с null вернет null. Например:
1 |
null & " orange " // возвращает null |
Распространение null: этот термин в языке M означает, что значение null плюс значение, совместимое с оператором +, вернет значение null. Например:
1 2 3 |
null + number type // возвращает null null + datetime // возвращает null null + duration type // возвращает null |
Допустимость null: все типы данных в языке M могут быть сделаны допускающими null; другими словами, они совместимы со значением null. Мы исследуем эту концепцию более подробно в главе 5, посвященной типам данных.
Оператор объединения с null (coalesce): язык M включает в себя специальный оператор, облегчающий обработку null, обозначаемый двойным вопросительным знаком (??). Он позволяет возвращать другое значение вместо null. Если значение в левой части оператора равно null, он возвращает значение из правой части оператора. Рассмотрим выражение в if:
1 2 3 |
if [FirstLetter] <> null then [FirstLetter] else if [SecondLetter] <> null then [SecondLetter] else "ZZ" |
Оператор объединения с null позволяет записать это выражение гораздо проще:
1 |
[FirstLetter] ?? [SecondLetter] ?? "ZZ" |
Подробнее об обработке значений null мы поговорим в главе 12 Обработка ошибок и отладка.
Числовые значения
Числовые значения в M используются для выражения числовых величин и в математических вычислениях. Числовые значения в M имеют разнообразные литеральные формы:
Рис. 4.5. Числовые значения в M
Связанные функции
В M существует 52 числовые функции, распределенные по шести категориям.
Битовые функции напрямую манипулируют битами числа. Поскольку M – функциональный язык и обычно не включает низкоуровневые бинарные преобразования, побитовые операции недостаточно документированы и редко используются в M.
Создание и преобразование: используются для создания числовых значений или преобразования их в совместимые типы значений и из них.
Информационные: определяют, является ли числовое значение четным, нечетным и собственно числовым (Not a Number, NaN).
Математические: выполняют вычисления над числовыми значениями. Почти половина математических функций относится к тригонометрическим.
Случайные: используются для создания случайных чисел в определенных диапазонах.
Округление: управляют количеством знаков после запятой.
Замечания
Типы данных и форматы. Одна из самых важных концепций относительно чисел в языке M, заключается в том, что хотя тип данных числа устанавливается в Power Query, его формат устанавливается в приложении – Power BI или Excel. Тип управляет способом хранения числа, что влияет на его точность и объем занимаемой памяти. Формат управляет отображением числа в приложении. Подробнее см. главу 5, посвященную типам данных.
Фасеты. Вы можете указать более подробную информацию о типах чисел в языке M, используя понятие подтипа. Их называют утверждениями или предписаниями типа (type claims) или кратко – фасетами. Фасеты влияют на варианты хранения: валюта, десятичные или целые числа, тем самым задавая точность значения. Значения, выходящие за диапазон фасета, вызовут ошибку. Правильное использование и значение фасетов будут рассмотрены в главе 5.
Деление на ноль даст разные результаты в зависимости от числителя:
- Деление положительного числа на 0 вернет положительную бесконечность (∞)
- Деление отрицательного числа на 0 вернет отрицательную бесконечность (–∞)
- Деление 0 на 0 вернет NaN
Во большинстве вычислений в M деление на ноль приведет к ошибке во время выполнения, поэтому следует принять меры для защиты от этого сценария. Обработка ошибок подробно рассмотрена в главе 12.
Константы. В языке M определено шесть числовых констант:
- E: возвращает e или число Эйлера с точностью до 16 знаков после запятой.
- Epsilon: возвращает наименьшее возможное положительное число для числа с плавающей точкой.
- NaN: возвращает константу, представляющую 0, деленный на 0.
- NegativeInfinity: возвращает постоянное значение, представляющее –1, деленную на 0.
- PI: возвращает число пи с точностью до 16 знаков после запятой.
- PositiveInfinity: возвращает постоянное значение, представляющее 1, деленную на 0.
Текстовые значения
В M текстовое значение – последовательность символов. Текстовые значения в коде берутся в двойные кавычки " ". Например: " Баскетбол ", " 23 ", " 9 ноября 1993 года ".
На экране предварительного просмотра кавычки исчезают.
Поскольку все неабстрактные, неструктурированные типы значений, за исключением null , могут быть преобразованы в текстовые значения и обратно, неудивительно, что существует 45 функций, связанных с текстом, распределенных по пяти категориям:
Структурированные типы значений: списки, записи и таблицы. Абстрактные типы значений: any, anynonnull, none, nullable Т (здесь Т – любой неабстрактный тип). Подробнее см. Язык М Power Query. Система типов. Основы.
Преобразование и создание – создают текстовые символы и значения и преобразуют их в другие типы значений.
Извлечение и положение – определяют положение символов в текстовых значениях, и извлекают различные подмножества из текстовых значений.
Форматирование – очищают ненужные символы и управляют регистром текста и другими характеристиками форматирования.
Информация – определяют длину и содержимое текстовых значений.
Преобразование – объединяют, разделяют, вставляют или удаляют текст или подмножества значений.
Замечания
Конкатенация. Вы можете объединить текстовые значения с помощью оператора амперсанд (&). Например, выражение…
1 |
Sentence = "Код М не так уж и сложен, " & "если вы знаете основные принципы" |
… вернет
Код на M не так уж и сложен, если вы знаете основные принципы.
Escape-символы. Некоторые символы зарезервированы для специальных целей. Например, перевод строки (lf) и возврат каретки (cr). Чтобы программа прочитала их как литеральные текстовые символы требуется последовательность «escape-символов». Для указанных символов – это #(lf) и #(cr).
Операторы сравнения. Текстовые значения поддерживают все распространенные операторы сравнения, которые по умолчанию чувствительны к регистру. Однако регистр оценивается не так, как можно было бы подумать – строчные буквы имеют более высокие числовые значения, чем их прописные эквиваленты:
1 2 3 4 |
"gza" < "rza" // вернет true "rza" = "Rza" // вернет false "rza" > "Rza" // вернет true "RZA" < "Rza" // вернет true |
Если вы сомневаетесь в порядке вычисления текстовых значений, можно использовать функцию Character.ToNumber для возврата числового значения (на котором основано вычисление) любого текстового символа. Для сравнения текстовых значений без учета регистра можно использовать опцию Comparer.OrdinalIgnoreCase. Например, следующее выражение вернет true:
1 |
Comparer.Equals(Comparer.OrdinalIgnoreCase, "rza", "RZa ") |
Хотя текстовые значения на первый взгляд кажутся простыми, очистка текстовых данных часто представляет проблему. Эти вопросы рассматриваются в главе 14 Проблемные паттерны данных и в главе 11 Сравнение, замена, комбинирование, разделение. В лицензию Power BI Premium в Power Query включена текстовая аналитика на основе искусственного интеллекта, которая предоставляет расширенные возможности для обработки текста и анализа настроений.
Списки
Список – это последовательность значений. В M один столбец значений является списком. Списки могут содержать любое примитивное или структурированное значение. Списки описаны в главе 6 Структурированные данные. Списки в коде ограничены фигурными скобками, { }. Например:
1 2 3 |
{ 1, 2, 3 } { "Кошка", "Собака", "Обезьяна" } { { 1, 2, 3 }, { "Кошка", "Собака", "Обезьяна" } } |
Списки являются одним из самых гибких и мощных типов значений в M, что объясняет, почему с ними связан широкий спектр функций. Существует 71 функция в пяти категориях:
Генерация – создают списки с различными характеристиками, например, списки даты, чисел и случайных чисел, а также более сложные функции, позволяющие создавать списки рекурсивно.
Информационные – возвращают логическое значение в зависимости от содержимого списка.
Выбор – выбирают элемент или группу элементов из списка на основе критериев. Нотация {2} дает доступ к элементу списка по индексу.
Статистические – возвращают статистику. Например, сумму, медиану, максимум и т.п.
Трансформационные – принимают список в качестве входных данных и создают новый список, изменяя структуру и/или элементы исходного списка.
Важно помнить, что списки работают с индексом. А индекс, определяющий положение элементов в списке, начинается с 0. В DAX первый элемент в списке находится в позиции 1. Например, чтобы найти третью букву в слове Method, в M следует написать:
1 |
Text.At("Method ", 2) |
Записи
Запись – именованный список значений. Запись можно рассматривать как одну строку в таблице, где каждое поле в строке имеет имя столбца и значение. Как и списки, записи могут содержать примитивные и структурированные значения. Запись в коде ограничена квадратными скобками [ ].
Рис. 4.6. Код и предварительный просмотр записи, содержащей список в третьем поле
23 функции, связанные с записями, распределены по пяти категориям (похожих на категории функций списков):
Преобразование – создают запись из списка/таблицы или таблицу/список из записи.
Информационные – возвращают значение в зависимости от содержимого записи.
Выбор – выбирают поле (поля) или значение одного поля из записи по заданным критериям. Нотация [ имя поля ] дает доступ к значению поля записи.
Трансформационные – изменяют структуру или содержимое записи.
Геопространственные – обрабатывают геопространственные данные в точку или форму well-known text (WKT).
Chat GPT: формат well-known text (WKT) – это текстовый формат для представления геометрических объектов в геоинформационных системах (ГИС). Он используется для описания геометрических объектов на карте. WKT включают в себя тип геометрии (точка, линия, полигон и т.д.) и координаты, определяющие местоположение объекта на карте.
Таблицы
Таблица состоит из трех элементов:
- строки, где каждая строка представляет отдельную запись;
- столбцы, где каждый столбец определяет поле в записи и ему присваивается тип данных;
- заголовки, которые идентифицируют имена каждого столбца.
В M таблицы можно создавать различными методами, в том числе с помощью конструктора #table или путем преобразования значений, списков, записей, столбцов, строк с помощью функций.
1 2 3 4 5 |
Синтаксис 4.17 #table( columns as any, rows as any, ) as any |
Например:
1 2 3 4 5 6 7 8 9 10 11 12 |
Запрос 4.17 let Source = #table( type table [ProductID = text, ProductQuantity = number, Column3 = date], { {"P001", 10, #date(2023, 8, 13)}, {"P002", 25, #date(2023, 8, 14)}, {"P003", 30, #date(2023, 8, 15)} } ) in Source |
Рис. 4.6а. Таблица, сгенерированная кодом 4.17
1 2 |
Запрос 4.18 #table({}, {}) // возвращает пустую таблицу |
Рис. 4.6б. Пустая таблица, сгенерированная кодом 4.18
Связанные функции
Таблицы являются наиболее часто используемым типом структурированных значений. Они имеют наибольшее количество связанным функций (116), распределенных по пяти категориям:
Создание и преобразование – создают таблицы и преобразуют их в другие типы значений и обратно.
Информационные – извлекают информацию о любых атрибутах таблицы (размер, схема, отношения, содержание).
Специфичные для столбцов – манипулируют полями и заголовками таблицы.
Специфичные для строк – манипулируют записями или группами записей в таблице.
Другие функции управляют буферизацией, сворачиванием запросов и пользовательскими обработчиками. За исключением функции Table.Buffer, они редко используются.
Замечания
Выбор и проекция. Критическим аспектом работы с таблицами является возможность фильтрации таблицы до нужных строк (выбор) и столбцов (проекция). Способы выполнения выбора и проекции имеют значение для производительности и читаемости кода. Когда вы используете вложенные структуры, выбор и проекция могут быть весьма сложными.
Производительность. Поскольку большинство шагов в запросе возвращают табличные значения, оптимизация производительности Power Query сосредоточена на оптимизации создания и преобразования таблиц.
Функции
Значение функции – это значения, которые при вызове с набором входных значений (аргументов) создают новое значение. В M функции задаются перечислением входных параметров в круглых скобках, оператором перехода (=>), выражением, определяющим функцию. Например:
1 2 |
Concatenator = (parameter1, parameter2) => (parameter1 & parameter2) DifferentDistinct = (x) => List.Difference(x,List.Distinct(x)) |
Имеется пять связанных функций: две для создания функций, две для вызова пользовательских функций и одна информационная – для проверки того, действует ли конкретная функция в качестве источника данных.
На сайте Рика де Гроота powerquery.how в разделе Function Values упоминаются Function.From, Function.Invoke, Function.InvokeAfter, Function.IsDataSource, Function.ScalarVector.
Замечания
Гибкость и мощь. Функции могут быть присвоены переменным, переданы в качестве аргументов и возвращены из других функций, что делает их одним из самых универсальных и мощных видов значений в M.
Важность типов данных. Чтобы избежать ошибок, важно присвоить параметрам функции типы данных. Мы рассмотрим вопрос применения правильных типов данных в главе 6, а также в главе 9, которая посвящена пользовательским функциям.
Производительность. При вызове рекурсивных функций и функций, которые принимают одну или несколько функций в качестве аргументов и/или возвращают функцию, учитывайте негативные воздействия на производительность.
Пользовательские функции являются одним из самых мощных элементов M и подробно рассматриваются в главе 9.
Значения типов
Значения типа можно рассматривать как мета-значения, которые предоставляют информацию о типе данных другого значения. Поскольку они классифицируют типы значений, то могут принимать любой из дескрипторов значений (binary, Date, DateTime, DateTimeZone, Duration, logical, null, number, text, time, list, record, table, function или type).
Функция Value.Type возвращает значение, отражающее тип данных аргумента:
1 2 3 |
Value.Type( 400 ) = number Value.Type( "Book" ) = text Value.Type( { 2, 3, 5, 7, 11, 13 } ) = list |
Связанные функции
Всего со значениями типов связаны 22 функции. Поскольку значения типов передают информацию о других значениях, а не являются структурами сами по себе, эти функции в первую очередь предоставляют дополнительные сведения о типе значения. Например, Type.TableRow и Type.TableColumn возвращают значения типа для указанных строк или столбцов таблицы.
Замечания
Целостность данных. Значения типов важны для обеспечения целостности входных данных и результата функции. Типы используются для присвоения типов данных неструктурированным данным и для преобразования типов данных из одного в другой по мере выполнения преобразований.
Существует ряд абстрактных типов, которые не классифицируют значения однозначно. К ним относятся типы данных any, anynonnull и none, которые охватывают соответственно (1) все значения, (2) все значения, отличные от null, и (3) отсутствие значений. Первые два значения используются для поддержки входных значений нескольких типов, в то время как тип none не классифицирует значения и, как правило, не используется.
Значения пользовательских типов. Любые типы данных, выходящие за рамки примитивных и абстрактных, являются пользовательскими типами данных. Они используются для описания структурированных значений и для создания пользовательских коннекторов. Пользовательские типы данных и связанные с ними значения типов будут обсуждаться в главе 5.
Мы рассмотрели различные виды значений, доступных в M. Далее мы рассмотрим операторы, то, что связывает значения в выражениях.
Операторы
Так же, как существуют различные виды связей, соединяющих атомы, существуют и разные категории операторов в языке M, которые соединяют значения. В следующей таблице представлены различные категории и конкретные операторы, входящие в каждую категорию:
Рис. 4.6в. Операторы в языке M
Арифметические операции в M подчиняются стандартному порядку, сокращенно PEMDAS (скобки, возведение в степень, умножение, деление, сложение и вычитание). Однако, в отличие от многих языков, использующих шляпку (^) в качестве оператора для возведения в степень, в M используется функция Number.Power.
Точно так же, как тип значения определяет, какие функции будут или не будут работать с данным значением, тип значения также определяет набор совместимых операторов:
Рис. 4.7. Сопоставление видов значений с операторами
Применение арифметических операторов и операторов конкатенации к значениям Date, DateTime, DateTimeZone, Duration и Time включает множество особых случаев:
Рис. 4.8. Список допустимых арифметических операций и операций конкатенации и результирующих типов значений для Date, DateTime, DateTimeZone, Duration и Time
Для арифметических операций с этими значениями применяются следующие правила:
- При вычитании двух значений одного типа друг из друга получается положительное или отрицательное значение длительности
- При добавлении или вычитании длительности из одного из этих типов значений в результате получается тот же тип значения
- При добавлении или вычитании двух длительностей получится длительность
- Умножение или деление длительности на числовое значение даст длительность
- Объединение значений Date и Time приведет к получению значения DateTime
В языке M есть несколько дополнительных, менее распространенных операторов:
Унарный плюс (+Х) – оператор тождества для числовых значений. Например, +12 возвращает 12.
Унарный минус (-Х) – оператор отрицания для числовых значений. Например, -12 возвращает -12.
Оператор объединения с null возвращает результат левого операнда, если он не равен null; в противном случае возвращает результат правого операнда. Например, x ?? y.
Метаданные (meta) добавляет метаданные к значению. Например: x meta y. Этот оператор исследуется в главе 7 Концепция M и главе 16 Включение расширений.
В следующем разделе мы рассмотрим, как объединять операторы и значения в выражения.
Выражения
Выражения в M похожи на формулы: используя операторы и значения в качестве входных данных, они в результате дают новое значение. Выражения вычисляются таким образом, что всегда возвращается одно значение. Каждое выражение M возвращает либо значение, либо ошибку. С помощью выражений M выходит за рамки простого представления данных и позволяет преобразовывать данные.
В чем разница между значениями и выражениями? В то время как значения представляют точки данных в их простейшей форме, выражения являются формулами, с помощью которых этими значениями можно манипулировать или создавать. Выражения могут быть простыми, возвращающими одну константу, и сложными, содержащими сотни промежуточных выражений.
Следующие примеры являются выражениями с одним значением:
1 2 3 |
1 // возвращает числовое значение "We love Power Query" // возвращает текстовое значение [ Fruit = "Apple", Fruit = "Plum"] // возвращает значение записи |
Power Query также позволяет объединять несколько значений или создавать логику для возврата значения. Например, следующие строки кода также считаются выражениями:
1 2 3 4 |
1 + 5 // сумма двух чисел List.Count({1,2,3}) // выражение, использующее функцию ( x, y ) => x – y // функция, которая вычитает y из x let age = 2 in age * 2 // выражение let |
Можно создавать более сложные выражения путем объединения нескольких выражений:
1 2 |
Код 4.19 Date.AddMonths( #date( 2024,1,1), 1 ) |
Этот пример состоит из 6 значений: значения функций Date.AddMonths и #date, три числа – аргументы функции #date и еще одно число – единица, второй аргумент функции Date.AddMonths.
Работа со значениями с помощью выражений является важным элементом выполнения преобразований. Тем не менее, все может быстро усложниться, как только вы научитесь создавать выражения. Представьте, что вы использовали 20 различных значений. В такой ситуации можно рассмотреть возможность хранения логики в операторе let. Оператор let позволяет определить ряд переменных, сделать ссылки на них и вывести результат. Внутри оператора let код разбит на части, которые легко понять.
Если мы продолжим код 4.19, и добавим форматирование, то получится:
1 2 |
Код 4.20 Date.ToText( Date.AddMonths( #date( 2024,1,1), 1 ), [Format = "yyyy-MM-dd"] ) |
Это выражение возвращает дату 1 февраля 2024 года. Но чем больше значений мы включаем в выражение, тем труднее оно читается. Мы можем представить код так:
1 2 3 4 5 6 7 8 |
Код 4.21 Date.ToText( Date.AddMonths( #date( 2024,1,1 ), 1 ), [Формат = " yyyy-MM-dd "] ) |
Читать код стало легче. Чтобы еще больше упростить задачу, добавим переменные с помощью оператора let:
1 2 3 4 5 6 7 |
Код 4.22 let myDate = #date( 2024,1,1 ), addMonth = Date.AddMonths( myDate, 1 ), formatDate = Date.ToText( addMonth, [Format = " yyyy-MM-dd "] ) in formatDate |
Здесь за ключевым словом let следуют три переменные, а результат обозначается переменной formatDate. Код 4.20, 4.21 и 4.22 возвращает один и тот же результат, но благодаря именованию переменных выражение становится намного проще для понимания.
Вы познакомились с примером использования оператора let. Проще всего увидеть код с let в расширенном редакторе. При выполнении операций Power Query автоматически создает инструкцию let. Несмотря на то, что один оператор let создается автоматически, вы можете вручную включить дополнительные операторы let в любой из примененных шагов.
Вложенные выражения let
Power Query использует выражение let для представления шагов в запросе. Чтобы упростить логику преобразования выбранного шага, можно использовать для него вложенное выражений let.
В этом разделе мы рассмотрим запрос Palindromes, в котором используется вложенное выражение let. Запрос анализирует столбец слов и отбирает палиндромы – слова, читающиеся одинаково в прямом и обратном направлении. Запрос возвращает таблицу палиндромов, отсортированных в алфавитном порядке по возрастанию. Вот как выглядит запрос в расширенном редакторе:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Запрос 4.23 let Source = #table( type table[Word = Text.Type], {{"bizarre"},{"racecar"},{"deified"}, {"probabl"}, {"rotator"}} ), AddCheckPalindrome = Table.AddColumn(Source, "CheckPalindrome", each let Letters = Text.ToList( Text.Lower( [Word])), Reversed = List.Reverse( Letters ), Palindrome = if Letters <> Reversed then false else true in Palindrome ), FilterResult = Table.SelectRows(AddCheckPalindrome, each [CheckPalindrome] = true), SortPalindromes = Table.Sort( FilterResult, {"Word", Order.Ascending}) in SortPalindromes |
Рис. 4.9. Запрос, содержащий выражения, значения, операторы, структуры элементов управления и перечисления
Запрос использует оператор let с четырьмя переменными и возвращает таблицу на шаге SortPalindromes. Четыре переменных соответствуют четырем шагам запроса.
Шаг AddCheckPalindrome содержит вложенную инструкцию let состоящую из трех выражений, которые выдают значение для каждого слова, указывающее, является ли оно палиндромом (true) или не является палиндромом (false). Хотя поначалу это может показаться запутанным, не волнуйтесь. Вы привыкнете работать с операторами let. Это просто способ разбить код на переменные и вернуть результат.
Разберем работу запроса подробнее.
Source предоставляет исходные данные для выражения – таблицу с одним столбцом. Обычно мы получаем данные из внешнего источника, но здесь мы генерируем исходные данные на первом шаге.
AddCheckPalindrome. На этом шаге в таблицу Source добавляется новый столбец с именем CheckPalindrome с помощью вложенного оператора let. Это выражение распадается на три промежуточных:
- Letters, преобразует каждое слово в нижний регистр и разбивает слово на буквы.
- Reversed переворачивает список букв.
- Palindrome использует логику if … then … else для проверки того, идентичны ли исходный и перевернутый списки букв. На этом шаге столбец CheckPalindrome заполняется значениями true/false для каждой строки.
FilterResult применяет фильтр к столбцу CheckPalindrome, оставляя только строки, имеющие значение true (то есть, являющиеся палиндромом).
SortResult сортирует палиндромы в столбце Word в порядке возрастания. Order.Ascending – это перечисление. Ниже мы подробно рассмотрим этот класс значений.
На панели Примененные шаги (см. рис. 4.9) имена переменных во внешнем операторе let отображаются как отдельные шаги, а во вложенном операторе let – не отображаются.
Если бы мы не вложили выражения Letters, Reverse и Palindrome во второй оператор let, а вместо этого добавили их как отдельные шаги во внешнем операторе let, они бы отобразились на панели Примененные шаги. Поскольку эти три шага были необходимы лишь для получения результатов CheckPalindrome, и не использовались другими переменными, мы решили вложить их во второй оператор let.
Обратите внимание, что рядом с шагами AddCheckPalindrome и FilterResult отображаются шестеренки. Если щелкнуть шестеренку (или дважды щелкнуть имя шага), откроется окно, в котором можно изменить настройки шага с помощью пользовательского интерфейса Power Query. Например, если мы кликнем на шестеренку рядом с шагом FilterResult, появится экран, в котором можно изменить условия фильтра в пользовательском интерфейсе, а Power Query запишет изменения обратно в наше выражение кода М:
Рис. 4.10. Пример параметров, которые отображаются при нажатии на значок шестеренки
Как только вы освоитесь писать код M в расширенном редакторе, вы поймете, что вносить подобные изменения быстрее и проще с помощью кодирования, а не в пользовательском интерфейсе. Тем не менее, шестеренки позволяют использовать интерфейс, если вы хотите.
Важно отметить, что мы могли бы поместить последнюю операцию после ключевого слова in:
Рис. 4.11. Перенос конечного выражения сворачивает список примененных шагов
Вместо того, чтобы ссылаться на переменную, строка после in сама задает выражение. Результат не изменился, но список примененных шагов свернулся. Важным преимуществом Power Query является возможность навигации по каждому выражению путем выбора шага. Это позволяет увидеть результаты отдельных выражений. Рекомендуется структурировать запросы таким образом, чтобы они возвращали имя одной переменной после финального in, и показывали шаги преобразования.
Рекомендации по написанию кода для выражений
Используйте описательные идентификаторы. Может показаться заманчивым использовать отдельные буквы или аббревиатуры для идентификаторов выражений. Однако это затруднит понимание вашего кода другими (или даже вами через месяц или два).
Избегайте использования пробелов в идентификаторах:
- Если идентификатор содержит пробелы, на него необходимо ссылаться в последующих выражениях с помощью решетки и кавычек (например, #"Результат фильтрации"). Так Power Query поймет, что это один идентификатор, а не отдельные переменные. Эти дополнительные символы могут сделать ваш код загроможденным и трудным для чтения, а также источником потенциальных ошибок.
- Power Query позволяет вызывать скрипты Python и R из выражения кода M. Эта дает M возможность выполнять статистический анализ, машинное обучение и многое другое. Однако Python, и R предпочитают идентификаторы без пробелов.
Используйте последовательный подход для имен идентификаторов. Если вы решите следовать предыдущей рекомендации, выберите одно правило и придерживайтесь его. Например, PascalCase, camelCase, snake_case и т.д.). Конкретный выбор является вопросом личных предпочтений.
Комментарии. Существует два способа комментирования кода M. Можно использовать две косые черты (//). После этого знака и до конца строки все содержимое будет комментарием. Можно использовать /* и */ для открытия и закрытия многострочного блока комментариев. Если щелкнуть правой кнопкой мыши выражение в списке Примененные шаги, и выбрать Свойства, появится диалоговое окно для ввода комментариев к выражению:
Рис. 4.12. Использование Свойства для комментирования выражений
В обоих случаях ваш комментарий будет отображаться зеленым цветом в расширенном редакторе, а справа от соответствующего шага в списке Примененных шагов появится небольшой значок с буквой i в кружке.
Теперь, когда мы рассмотрели базовую структуру выражений, обратим внимание на управляющие структуры.
Структуры управления
Управляющие структуры – это конструкции, которые манипулируют потоком выполнения, позволяя программе разветвляться в разных направлениях кода на основе оценки условий.
Этот раздел краток не потому, что управляющие структуры не важны, а потому, что (как мы рассмотрели в главе 1) M является функциональным языком и имеет гораздо меньше управляющих структур, чем C, Python или JavaScript. На самом деле в M единственная управляющая структура – оператор if-then-else, также известный как условный оператор.
Если условие имеет значение true, возвращается выражение then. Если условие имеет значение false, возвращается выражение else. Если условие приводит к значению не являющемуся логическим (ни true, ни false), возвращается ошибка. Например:
1 2 3 |
if true then 1 else 0 // возвращает 1 if false then 1 else 0 // возвращает 0 if "12345" then 1 else 0 // возвращает ошибку, так как условие – текст, а не логическое значение |
Хотя true часто представляют, как 1, а else – как 0, следующее выражение возвращает ошибку:
1 |
if 1 then 1 else 0 |
Это связано с тем, что число один является не логическим значением, а числовым.
Можно вкладывать операторы if-then-else таким образом, что после then может следовать вложенная конструкция else if и так несколько раз. К сожалению, M не имеет структуры управления переключением или обращением, как DAX или SQL, которая позволяет учитывать более двух исходов для данного условия. Такие сценарии обычно решаются с помощью нескольких вложенных операторов if-then-else.
Следующий пример, демонстрирующий использование вложенных управляющих структур if-then-else, основан на классической задаче программирования под названием FizzBuzz, которая обычно используется для оценки кандидатов на должность разработчика. Задача состоит в том, чтобы создать список целых чисел от 1 до 100, а затем каждому числу, делящемуся без остатка на 3, присвоить значение Fizz, делящемуся без остатка на 5 – Buzz, а делящемуся без остатка на оба числа – FizzBuzz. Строки, не удовлетворяющие ни одному из этих условий, возвращают число в этой строке.
Рис. 4.13. Иллюстрация вложенных операторов if-then-else в задаче FizzBuzz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Запрос 4.24 let Source = Table.FromList({1 .. 100}, Splitter.SplitByNothing(), {"Numbers"}, ExtraValues.Error), AddFizzBuzz = Table.AddColumn( Source, "FizzBuzz", each if Number.Mod([Numbers], 3) = 0 and Number.Mod([Numbers], 5) = 0 then "FizzBuzz" else if Number.Mod([Numbers], 3) = 0 then "Fizz" else if Number.Mod([Numbers], 5) = 0 then "Buzz" else [Numbers], Any.Type ) in AddFizzBuzz |
Number.Mod – это функция, которая принимает два числовых значения, делит первое на второе и возвращает остаток. Таким образом, используя эту функцию во вложенных управляющих структурах, задача становится довольно простой.
Строка 1 инициирует выражение с let.
Строка 2 с помощью оператора {1..100} создается список целых чисел и преобразует его в таблицу.
Строки 3–6 добавляют в таблицу столбец с именем FizzBuzz для хранения результатов.
Строка 7 начинает управляющую структуру if-then-else. Во-первых, мы оцениваем самые строгие условия (FizzBuzz), чтобы управляющая структура не завершалась преждевременно. Если оба условия истинны, то возвращается значение FizzBuzz (строка 8) и программа выходит из управляющей структуры. Если это не так, она переходит к следующей строке.
Строка 9. Когда выполняется эта строка, мы знаем, что оба условия одновременно не были оценены как true, поэтому каждое условие тестируем отдельно. Сначала мы тестируем делитель 3. Если число делится без остатка, возвращаем значение Fizz (строка 10). В противном случае переходим к следующей строке.
Строка 11. Здесь мы тестируем делитель 5. Если число делится без остатка, возвращаем значение Buzz (строка 12). В противном случае переходим к следующей строке.
Строка 13. Это финальное else, если ни одно из предыдущих утверждений не вернуло true. Если элемент управления достигает этой точки, мы возвращаем исходное числовое значение (строка 14).
Строка 15. Устанавливает текстовый тип данных добавленного столбца FizzBuzz, потому что он содержит как текстовые, так и числовые значения. Мы подробно обсудим этот процесс, называемый приписыванием типов данных, в главе 5.
Строки 16-18 закрывают выражение let и возвращают конечное значение выражения.
Эта задача представляет собой распространенный сценарий с множеством реальных применений, поэтому она так часто используется в тестовых заданиях. Обратите внимание, что начальное условие в управляющей структуре может быть чем-то очень простым, например [color] = "red", или крайне сложным множественным вложенным выражением let. Все, что требуется, это чтобы условие возвращало логическое значение (true или false).
Несмотря на то, что M имеет только одну формальную структуру управления, это не означает, что он не является мощным языком. У него есть другие функции и возможности, которые воспроизводят действия управляющих структур в других языках:
- Функция Accumulate может выполнять выражение несколько указанных раз так же, как и типичная управляющая структура цикла for
- Функция Generate может выполнять выражение многократно неопределенное количество раз до тех пор, пока условие вычисления остается истинным, так же, как и типичная управляющая структура цикла while
- M также резервирует символ @ для создания пользовательских рекурсивных функций, которые могут воспроизводить поведение управляющих конструкций for или while.
Наконец, многие программы, такие как R, включают функцию map, которая действует как тип управляющей структуры, применяя указанную функцию ко всем строкам столбца. В языке M есть несколько способов сделать это, включая функции Table.TransformColumn и List.Transform. Эти более сложные циклические и рекурсивные структуры будут единственной темой главы 13 Итерация и рекурсия, так как они очень гибкие и мощные, с широким спектром применений.
Мы подошли к последнему моменту, связанному со значениями, известным как перечисления.
Перечисления
Перечисления являются фундаментальной концепцией программирования. Они представляют собой наборы именованных значений, определяющих поведение функций. Эти имена облегчают нам выбор опций в коде, делая намерения ясными. Пользователи могут указать перечисление, используя либо соответствующий индекс, либо его текстовое представление. Текстовое представление часто упрощает понимание кода.
Возьмем в качестве примера функцию Table.Sort. Эта функция сортирует табличные данные с помощью перечисления для указания порядка. Выше мы обсуждали сортировку палиндромов в порядке возрастания. Для этого мы создали следующее выражение:
1 |
SortPalindromes = Table.Sort(FilterResult, {"Word", Order.Ascending}) |
Здесь Order.Ascending сообщает, как должна быть отсортирована таблица FilterResult. Мы также могли бы написать:
1 |
SortPalindromes = Table.Sort(FilterResult, {"Word", 0}) |
Этот код работает потому, что в языке M каждая опция перечисления имеет целочисленный эквивалент. Документация Microsoft для этого перечисления показывает, что у вас есть возможность использовать 0 или Order.Ascending для сортировки по возрастанию и 1 или Order.Descending для сортировки по убыванию.
Интересным моментом в перечислениях является то, что их можно подменять на основе индекса. Например, Day.Sunday и Order.Ascending имеют числовое значение 0. Таким образом, если в Table.Sort используется Day.Sunday, он считывается как 0 и функция работает. Следующие фрагменты возвращают одинаковый результат:
1 2 3 |
List.Sort( { 4, 3, 2, 1 }, Order.Ascending ) List.Sort( { 4, 3, 2, 1 }, Day.Sunday ) List.Sort( { 4, 3, 2, 1 }, 0 ) |
Это иллюстрирует тот факт, что перечисления являются синтаксическим сахаром для целочисленного значения. Функции, включающие перечисление, были разработаны таким образом, чтобы вести себя определенным образом на основе этих целых чисел.
Помимо Order.Type, другими часто используемыми перечислениями являются:
RoundingMode.Type сообщает таким функциям, как Number.Round, Currency.From и Int64.From, как округлять числа. Имеется пять опций.
MissingField.Type. Не редкость встретить отсутствующие поля в данных. Это перечисление позволяет вам решить, какое действие предпринять — игнорировать, вернуть ошибку и так далее. Оно полезно для таких функций, как Record.RemoveFields, Table.SelectColumns и Table.TransformColumns.
GroupKind.Type. У вас могут быть разные требования при группировке данных. Это перечисление определяет, как функция Table.Group собирает данные в группы. GroupedGlobal – не гарантирует сохранения порядка исходных строк после группировки. Это позволяет функции Table.Group быть более производительной. GroupedLocal внутри каждой группы сохраняет порядок строк из исходных данных.
JoinKind.Type. Определение типа соединения важно при объединении наборов данных с помощью функций Table.Join, Table.NestedJoin и их нечетких аналогов. Это перечисление определяет тип соединения — внутреннее, внешнее, левое, правое и так далее.
Day.Type. Для вычислений, связанных с датами, выбор дня, с которого начинается неделя, может повлиять на результаты. Это перечисление позволяет нам установить этот начальный день для функций, таких как Date.StartOfWeek, Date.EndOfWeek и Date.WeekOfMonth.
На момент написания книги в M существует 30 перечислений:
Рис. 4.17. Перечисления в M (источник)
Дополнительные сведения о каждом из этих перечислений, в том числе о том, какие функции их поддерживают и как их можно использовать, можно получить на сайте powerquery.how.
Подводя итог, можно сказать, что перечисления играют важную роль в том, чтобы сделать код более выразительным и удобным для чтения. Допуская как числовое, так и текстовое представление, пользователи могут выбрать краткость кода или удобочитаемость.
Саммари
В этой главе мы обсудили, что такое значения и выражения и почему они так важны в М. Мы исследовали 15 различных типов значений в четырех категориях. Мы получили представление о каждом типе значения, включая их структуру, связанные функции и то как они дополняют друг друга. Мы рассмотрели особенности использования каждого типа значений. Наконец, мы обсудили как связывать значения в выражения с помощью операторов, управляющих структур и перечислений.
В следующих главах мы соберем два оставшихся элемента: типы данных (глава 5) и структурированные значения (глава 6). Вооружившись этими знаниями, мы будем готовы к продвинутым этапам нашего путешествия. Мы узнаем, как собирать базовые компоненты во все более сложные структуры, которые могут решить практически любую задачу, связанную с данными.