Это продолжение перевода книги Грегори Декер, Рик де Гроот, Мелисса де Корте. Полное руководство по языку М Power Query. В предыдущей главе мы рассмотрели роль значений в формировании выражений и запросов в языке M. Мы получили представление о каждом типе значения, включая их структуру, связанные функции и то, как они дополняют друг друга. Мы узнали, как значения взаимодействуют с операторами и функциями. Основываясь на этих знаниях, давайте продолжим путешествие, обратив внимание на типы данных.
Мои комментарии набраны с отступом.
Предыдущая глава Содержание Следующая глава
Скачать заметку в формате Word или pdf
Типы данных служат системой классификации значений, предоставляя информацию об их структуре и использовании в M. Понимание типов данных имеет важное значение для эффективного использования M и эффективного хранения значений. В этой главе мы приводим всесторонний обзор типов данных, их значения и применения на практике.
Мы начнем с того, почему типы данных важны. Вы узнаете о различных типах данных в M и контекстах, в которых они используются. Мы также объясняем, как идентифицировать и понимать различные типы данных, уделяя особое внимание преобразованию типов. Мы представим фасеты и объясним, как присваивать типы данных значениям.
Краткий обзор главы:
- Важность типов данных
- Типы данных, доступные в M
- Определение типов
- Преобразование типов
- Фасеты
- Присвоение типов
- Эквивалентность и совместимость типов
К концу главы у вас будет полное представление о типах данных в M. Вы сможете создавать эффективные запросы, не сталкиваясь с ошибками типов данных.
Что такое типы данных?
Языку M присущи, как значения, так и типы данных. В официальной документации типы данных формально называются типами, потому что они классифицируют не только данные, но и функции и сам тип данных. В книге мы используем термины «типы данных» и «типы», чтобы не запутаться при сравнении их с типами значений. Итак, что же такое типы данных и как они соотносятся со значениями?
Система типов
Система типов в Power Query помогает классифицировать значения, предоставляя сведения о структуре данных. Типы данных могут задаваться при создании пользовательских функций. Кроме того, типы передают важную информацию в любую систему, в которую загружаются данные.
Каждый тип значения в языке M имеет тип данных. Это особый вид значения, который характеризует тип значения и содержит дополнительные метаданные, специфичные для формы значения. Тип данных – это значение, точно так же, как число – это значение. Чтобы проиллюстрировать это, давайте рассмотрим пример. Предположим, вам нужно добавить новый столбец в таблицу. Вы можете сделать это с помощью следующего оператора:
1 2 3 4 5 6 7 |
Код 5.1 Table.AddColumn( #"Changed Type", // значение таблица "Addition", // значение текст each [Value] + 10, // значение функция type number // значение тип ) |
В этом выражении каждый аргумент функции Table.AddColumn получил значение типа: #"Changed Type" ссылается на таблицу на шаге с именем #"Changed Type", "Addition" – текстовое значение с именем нового столбца, each [Value] + 10 определяет функцию, которая генерирует значения для заполнения нового столбца, type number определяет тип данных для нового столбца.
В языке M 15 типов значений, в том числе и Type, который также является значением:
Рис. 5.1. Различные виды значений в языке M
В таблице наряду с видом значения приведен пример литерального синтаксиса (обозначения), связанного с каждым значением. Посмотрите, как следующие выражения возвращают четыре разных значения:
1 2 3 4 5 |
Код 5.2 "MyText" // возвращает текст 2024 // возвращает число { 1, 2, 3 } // возвращает список type list // возвращает тип |
Сосредоточьте свое внимание на последнем примере, в котором показан тип список. Как и любое другое значение, тип также является значением, и существует определенный синтаксис для возврата типа. Примеры, возвращающие тип:
1 2 3 4 |
Код 5.3 type number // возвращает числовой тип type binary // возвращает двоичный тип type text // возвращает текстовый тип |
Если значение имеет связанный с ним тип, когда этот нюанс становится актуальным?
Столбцы со смешанными типами
При работе с таблицами разница между типами данных и значениями становится особенно очевидной. Столбцы имеют тип данных, описывающий тип значений, содержащихся в них.
Для лучшего усвоение материала выполняйте упражнения в Power BI Desktop или Excel. Скачать упражнения к главе можно с репозитория GitHub.
Представьте ситуацию, когда тип данных столбца не является однозначным. Данные поступают из неструктурированного источника (например, Excel), и могут содержать различные типы значений. Создайте новый запрос в редакторе Power Query:
1 2 3 4 5 |
Код 5.4 Table.FromColumns( { { "abc", 123, true, #date(2024,1,1) } } , {"Mixed Data"} ) |
Этот код создает таблицу со столбцом Mixed Data с четырьмя значениями. Так как мы не указали тип данных, Power Query автоматически присвоил этому столбцу тип any, что нашло отражение в значке ABC123 в заголовке столбца.
Рис. 5.2. Столбец с разными типами значений
Присвоить столбцу конкретный тип данных затруднительно, так как столбец содержит различные типы значений. Если мы не зададим тип данных в выражении, Power Query по умолчанию использует тип any. Тип any допускает различные типы значений в одном столбце. Тем не менее, выбор типа данных зависит от ваших потребностей в вычислениях или преобразованиях, которые вы планируете применять.
Например, если предполагается работать со значениями даты, можно задать для столбца тип Date. Однако, значения в столбце должны соответствовать указанному типу.
Чтобы указать тип данных столбца, нажмите на значок типа данных, расположенный слева от заголовка столбца и выберите Дата (рис. 5.4А). Power Query попытается преобразовать значения в столбце в тип Date и проверит, соответствуют ли значения этому типу данных. Если по какой-то причине движок не может преобразовать значение в указанный тип данных, отображается ошибка:
Рис. 5.4. Преобразование столбца, содержащего различные типы данных, может привести к ошибкам
В примере обработчик успешно преобразует число 123 и дату 01.01.2024 в значение даты, но выдает ошибку для текстового и логического значения. Это говорит о том, что M не поддерживает преобразование любых значений в тип Date, и в этом случае более подходящим является тип данных any. Несмотря на то, что каждое значение в столбце имеет определенный тип, столбец принимает сочетание значений с типом any. Далее мы рассмотрим, какие преобразования между типами данных поддерживаются в M.
Предыдущий пример призван проиллюстрировать, что тип данных отличается в зависимости от того, какие значения он описывает. При сочетании значений тип any может быть наиболее подходящим.
Перейдем к другому примеру, где рассмотрим тонкое, но важное различие между типом данных и типом значения. Разница важна, потому что столбец определенного типа может содержать значения другого типа. Давайте узнаем, что это значит.
Тип данных столбца и тип значения
Тип данных отличается от значения, которое он описывает. Рассмотрим таблицу, содержащую три столбца. Предположим, вы хотите добавить новый столбец, чтобы отобразить скидку 5% во всех строках. Чтобы вставить новый столбец в редакторе Power Query пройдите Добавление столбца ‑> Настраиваемый столбец. В окне Настраиваемый столбец введите имя нового столбца Discount и в настраиваемой формуле столбца значение 0.05 после знака равно:
Рис. 5.4а. Добавление настраиваемого (пользовательского) столбца Discount; чтобы увеличить изображение кликните на нем правой кнопкой мыши и выберите Открыть картинку в новой вкладке
Рис. 5.5. К таблице добавится столбец Discount типа any
Обратите внимание: в коде десятичный разделитель – точка; в данных (импортируемых из внешних источников, в окне предварительного просмотра, возвращаемых в приложение) разделитель определяется локальными настройками Windows.
Когда мы добавляем столбец Discount, Power Query автоматически присваивает ему тип данных any, так как мы не определили для него конкретный тип. Однако здесь возникает небольшая сложность: столбец Discount помечен как any, но фактические значения в нем являются числовыми. В столбце просто нет метки, объявляющей «правильный» тип. В окне Настраиваемый столбец введите имя нового столбца Type и в настраиваемой формуле столбца Value.Type( [Discount]) после знака равно. Нажмите Ok:
Рис. 5.6. Power Query распознает тип значения даже без назначенного типа данных
Применяя функцию Value.Type к каждому значению в столбце Discount, мы получаем столбец, который для каждой строки показывает, какой тип значения она содержит. Чтобы просмотреть тип, вы можете щелкнуть по пустому месту в ячейке, и увидите, что значение имеет типа number.
Таким образом, даже без типа данных, присвоенного столбцу, функция распознает тип значений, над которыми выполняет операции. Как это работает?
Помните, как мы вводили значение скидки? В таблицу мы передали значение 0.05. Функция AddColumn распознала литеральный синтаксис (нотацию), используемую для ввода значения скидки. Движок распознает, с каким значением код имеет дело.
Наличие типа, такого как any, в заголовке столбца не означает, что все значения в этом столбце одного типа. Это лишь указывает на то, что значения соответствуют типу данных этого столбца. Указав any, мы позволили столбцу содержать любые значения, но движок распознает каждое значение по отдельности.
Разумный вопрос, который можно задать на этом этапе: если M все равно распознает, с каким типом значения мы работаем, почему бы для простоты не использовать тип данных any для любого набора данных? Ответ мы рассмотрим в следующем разделе.
Важность типов
Язык M является языком запросов с динамической типизацией. Это означает, что вам не нужно объявлять переменные и их типы перед использованием. И, как вы только что узнали, Power Query может распознавать, какие данные он получает. Но отсутствие явного указания типов данных влечет за собой и риски. Когда ваш столбец помечен как any, это означает, что он может содержать любое значение. Если вы затем выполняете операцию, которая работает только с определенным типом значения, ваша операция может привести к ошибке.
В этом разделе мы рассмотрим, почему лучше указывать типы данных. Представьте типы данных как маркировку коробок при переезде; это требует некоторого внимания при упаковке, но избавляет от путаницы позже. Итак, почему типы данных важны?
Ясность и последовательность
Типы данных играют важную роль в повышении ясности и обеспечении согласованности в данных. Типы дают четкий сигнал о природе значений столбца. Данные, которые не соответствуют типу, вернут ошибку, помечая несоответствия и помогая поддерживать целостность данных.
Представьте, что вы работаете с набором счетов. Ранее для номеров счетов-фактур использовалась простая числовая последовательность: 10000, 10001, 10002 и т. д. С наступлением нового года формат был обновлен и теперь включает префикс, например INV-11000, INV-11001.
Без указания типа данных беглый взгляд на набор данных может ввести в заблуждение, заставив поверить, что вы имеете дело с числовыми значениями. В конце концов, только когда вы дойдете до строки 220, вы увидите новый формат данных:
Рис. 5.7. Набор данных, в котором обзор первых 200 строк может привести к неверным выводам о типе данных
Этот пример показывает, что отсутствие определенного типа данных может привести к неверным предположениям о данных и, возможно, к ошибкам обработки. Если вы затем примените функции, совместимые только с числами, вы столкнетесь с ошибками. Когда данные будут загружаться в приложение движок пытаясь обработать текст, как числа, вернет ошибку.
И наоборот, четко определенный тип данных для столбца предотвращает такие проблемы, сообщая природу данных, и гарантируя, что все значения соответствуют указанному формату. Эта ясность вместе с последовательностью, которую она обеспечивает, позволяет уверенно работать с данными. Но это не единственная причина присвоения типов данных. Типы также полезны для проверки данных.
Проверка данных
Типы данных играют важную роль в проверке данных и предотвращении ошибок. Устанавливая типы данных, можно убедиться в том, что данные соответствуют определенному типу. Это важно, так как многие операции зависят от типа данных.
Возьмем, например, сложение. Суммировать текстовые значения не имеет смысла. Аналогичным образом, при объединении таблиц нужно, чтобы столбцы, используемые для слияния, имели совпадающие типы данных. Предположим, что слияние приводит к неожиданным результатам. Если вы заранее не определили типы данных, найти источник проблемы будет сложно.
Возможно, наиболее очевидным примером того, почему типы данных важны, является создание пользовательских функций, тема, которую мы рассмотрим в главе 9. Пользовательские функции принимают параметры, выполняют с ними операции и возвращают результат. При определении функции рекомендуется объявить ожидаемый тип данных для каждого параметра. Следующая функция ожидает text как на входе, так и на выходе:
1 |
( myText1 as text, myText2 as text ) as text => myText1 & myText2 |
Функция принимает два текстовых значения, объединяет их и возвращает конкатенацию. Явная установка типов данных сообщает движку, что любое значение, передаваемое в функцию, должно соответствовать объявленному типу данных. Если функция получает входное значение другого типа, скажем, число, она выдает ошибку и сигнализирует пользователю: что-то не так. Обратная связь помогает убедиться в том, что функция используется должным образом.
Помимо двух рассмотренных, есть и другие причины для задания типов данных.
Другие причины
Типы данных также важны по следующим причинам:
Производительность. Определение типов данных может ускорить выполнение запросов. Когда вы задаете типы для столбцов, Power Query оптимизирует способ хранения и извлечения данных, сокращая использование памяти. С другой стороны, если вы не укажете типы, Power Query попытается вывести (угадать) их. Вывод типов потребляет дополнительные вычислительные ресурсы, замедляя выполнение запросов. Дополнительные сведения о повышении производительности запросов см. в главе 15 Оптимизация производительности.
Обработка ошибок. Указав тип данных, Power Query может лучше обрабатывать ошибки. Если Power Query обнаруживает значение, которое не соответствует назначенному типу, он либо отклоняет его, либо преобразует, в зависимости от выражения. Различные методы обработки ошибок обсуждаются в главе 12 Обработка ошибок и отладка.
Совместимость. Типы данных могут быть важны при интеграции с другими системами или при экспорте данных. Некоторым системам требуются определенные типы данных для операций или интерфейсов. Используя типы данных, Power Query обеспечивает совместимость с этими системами. Например, вы можете вызывать скрипты R из Power Query для выполнения расширенного статистического анализа. Но, если данные выражены в виде валюты (что совершенно допустимо в M), они будут считываться в R как текст, и большинство статических функций, вызываемых в скрипте, не будут работать.
Как обсуждалось ранее, типы данных имеют нечто большее, чем описательное назначение. Они играют важную роль в поддержании согласованности и целостности данных, повышении производительности, обработке ошибок, обеспечении совместимости систем и повышении общей ясности. В следующем разделе мы углубимся в различные типы данных, доступные в языке M. Мы познакомим вас с типами, объясним их свойства и покажем, как их использовать.
Типы данных, доступные в M
Язык M поддерживает различные типы данных. Они предоставляют способ классификации значений и иногда ограничивают вид данных, допустимых в пользовательской функции. Можно сказать, что типы данных определяют форму значения и указывают на операции, которые можно выполнять над ним.
Систему типов в языке M можно рассматривать как иерархию. В её основе все значения, соответствующие типу any. Ниже в иерархии специфические типы: примитивные типы, а также более сложные конструкции, таких как записи, списки и таблицы. Система типов также позволяет определять пользовательские типы, давая возможность задавать собственные структуры данных.
Следует различать значения, которые могут и не могут содержать null. Если могут, то перед названием типа используют nullable, например, nullable text. Это различие полезно при работе с источниками данных, где значение может возвращать null, и должно быть правильно обработано.
Итак, какие существуют типы и как их использовать?
Примитивные типы
Примитивные типы – это фундаментальные типы, из которых создаются все остальные типы. Их можно использовать для классификации примитивных значений, а также в качестве строительных блоков для более сложных значений, таких как записи, таблицы и функции.
Рис. 5.8. Обзор примитивных типов в M
О терминах абстрактный (Abstract) и пользовательский (Custom) тип мы поговорим далее в этой главе. В столбце Значение некоторые типы не соответствуют одному значению, поэтому ячейки остаются пустыми. Таблица не содержит таких типов, как Int64.Type (целое число), Currency.Type (фиксированное десятичное число) и Number.Type (десятичное число). Причина в том, что это утверждения типов, которые относятся к фасетам. Каждый из них имеет базовый type number. Фасеты также будут подробно изучены ниже.
Одна из причин изучать типы данных заключается в том, что различные типы операций поддерживаются разными значениями. Понимание и определение типов данных указывает, что можно с ними делать. Вот пять примеров значений и их синтаксис:
1 2 3 4 5 |
2024 // возвращает значение типа number [a = 10, b = 20] // возвращает значение типа record #date(2024, 12, 31) // возвращает значение типа date {1} // возвращает значение типа list null // возвращает значение типа null |
Эти примеры показывают различные возвращаемые виды значений. Если вместо этого вы хотите вернуть тип данных, с которым имеете дело, вы можете воспользоваться функцией Value.Type. Эта функция принимает значение и возвращает его тип данных. Например:
1 2 3 4 5 |
Value.Type(2024) // возвращает type number Value.Type([a = 10, b = 20]) // возвращает type record Value.Type(#date(2024, 12, 31)) // возвращает type date Value.Type({1}) // возвращает type list Value.Type(null) // возвращает type null |
Интересно, что функция Value.Type также может возвращать тип type. Например:
1 |
Value.Type(type function) // возвращает type type |
Это еще раз показывает, что мы имеем дело с видом значения – type, используемым для классификации других значений. В редакторе Power Query это выглядит так:
Рис. 5.9. Функция Value.Type возвращает текстовое представление типа
Хотя выходные данные этого выражения выглядят как текст, помните, что это всего лишь текстовое представление типа, а не фактическое значение или его свойство. Для извлечения сведений о типе Power Query предоставляет набор функций с префиксом Type.
Абстрактные примитивные типы
В языке M примитивные типы делятся на две категории: абстрактные и неабстрактные. Существует 6 абстрактных и 12 неабстрактных типов, и основное различие заключается в том, как эти типы классифицируют значения. Неабстрактные примитивные типы специфичны: они однозначно идентифицируют одно конкретное значение. Абстрактные типы, напротив, шире и менее конкретны. Они не могут полностью классифицировать одно уникальное значение.
Возьмем, например, тип function. Хотя все функции в M соответствуют этому типу, одного типа function недостаточно для описания деталей функции: ее параметров и типов данных. Для полной и конкретной классификации необходим пользовательский тип.
Эта концепция распространяется также на типы table и record. Эти типы в целом классифицируют таблицы и записи, но они не описывают такие особенности, как имена столбцов или полей, а также типы данных столбцов или полей. Для такой детальной классификации требуется пользовательский тип.
Типы any и anynonnull. Каждое возможное значение в M соответствует типу any, а каждое значение, отличное от null, соответствует типу anynonnull. Однако ни один из этих типов не задает уникального, индивидуального значения.
Тип none уникален тем, что невозможно создать соответствующее ему значение. Выражение типа none должно неизменно приводить к ошибке (ошибки не считаются значением) или входить в бесконечный цикл. Исходя из этого, тип none не классифицирует уникальное значение, так как ни одно из значений ему не соответствует. Поэтому это не тот тип, который вы будете использовать при написании кода.
Всякий раз, когда вы описываете значение с помощью одного из абстрактных типов данных, это никогда не является точной классификацией значения. Классификация, использующая абстрактные типы, неточно представляет базовое значение, или классификация вообще не классифицирует тип (как в случае с none). Абстрактные типы требуют более тонкой обработки, поскольку они могут описывать различные значения или не иметь значения.
Для столбцов, которые ожидают несколько типов значений, можно использовать широкие типы, такие как any или anynonnull. Но есть еще одна полезная концепция, которая заставляет другие типы данных поддерживать также и значение null. Обсудим это подробнее.
Примитивные типы, допускающие значение null
Помимо базовых форм, рассмотренных выше, существуют разновидности примитивных типов, допускающие значение null. Представьте их, как систему, способную содержать два вида значений. Первый не допускает значения null, т.е. хранит определенные значения, например, число или текст. Второй – хранит значения null. Этот null означает отсутствие значения. Примитивные типы, допускающие значение null, следует рассматривать как абстрактные.
Типы, допускающие значение null, важны в сценариях, где операциям требуется определенный тип данных, но при этом нужно сохранить гибкость для обработки отсутствующих значений. Рассмотрим функции Number.IsOdd и Number.Sign:
1 2 3 |
Синтаксис 5.5 Number.IsOdd( number as number ) as logical Number.Sign( number as nullable number ) as nullable number |
Функция Number.IsOdd принимает число и возвращает true, если число нечетное, и false в противном случае. Она не принимает значения null и строго требует значение аргумента типа number. Функция Number.Sign гибче. Она принимает число, допускающее значение null, то есть принимает либо число, либо значение null. Это важно, так как позволяет применить функцию к набору данных, в котором некоторые значения могут отсутствовать.
Рассмотрим пример – набор данных со столбцом Amount, содержащим числовые значения где некоторые записи отсутствуют и представлены как null. При применении функций Number.IsOdd и Number.Sign к этому столбцу возникают различные варианты поведения, как показано на рисунке:
Рис. 5.10. Поведение функций с типами, допускающими и не допускающими значение null
Функция Number.IsOdd обрабатывает только записи с числовыми значениями. Записи содержащие значение null, возвращают ошибку. Функция Number.Sign обработала все строки. Для чисел возвращает значения 1, 0 и -1, в зависимости от того, является ли число положительным, 0 или отрицательным. Для значений null возвращает null, не вызывая ошибки.
Концепция типов, допускающих значение null, особенно важна при работе с функциями. Для функций вы хотите знать, принимают ли они null в качестве входных данных или могут выдавать null в результате. Понимание того, как использовать типы, допускающие значение null, помогает предотвратить ошибки. В главе 9 Параметры и пользовательские функции вы узнаете, как использовать типы, допускающие значение null, для параметров функций, что сделает ваши пользовательские функции более гибкими и менее подверженными ошибкам.
Использование ключевого слова nullable
Рассмотрим влияние ключевого слова nullable на типы данных в языке M. Эмпирическое правило простое: добавление к любому типу ключевого слова nullable позволяет этому типу вместить как свое примитивное значение, так и null:
1 2 |
nullable text // описывает значения text и значения null nullable table // описывает значения table и значения null |
В некоторых случаях использование nullable с типом данных приводит к преобразованию типа:
1 2 |
nullable anynonnull // возвращает тип any nullable none // возвращает тип null |
Из этого правила есть исключения. Когда nullable применяется к типу, который уже содержит null, тип остается неизменным:
1 2 |
nullable any // возвращает тип any nullable null // возвращает тип null |
Для обратного действия – превращения типа, допускающего null в примитивный тип – используется функция…
1 |
Type.NonNullable( type nullable number ) |
Изучив теорию примитивных типов, рассмотрим их применение.
Задание типов данных для столбцов
Наиболее часто типы данных задаются для столбцов. Имеется набор данных:
Рис. 5.11. Таблица со столбцами типа any
В столбце Sales я заменил десятичный разделитель точку на запятую.
Во всех столбцах содержатся примитивные значения, но тип данных не задан. Power Query по умолчанию использует тип any, представленный значком ABC123 в заголовке столбца. Допустим, мы хотим задать типы данных для столбцов: date, text и number. Перед установкой типа проверьте значения столбцов, чтобы убедиться, что они соответствуют предполагаемому типу, чтобы избежать ошибок преобразования.
Чтобы указать тип данных, щелкните значок слева от заголовка каждого столбца и выберите нужный тип. В коде добавится шаг:
1 2 3 4 5 |
Код 5.6 Table.TransformColumnTypes( Источник, {{"Date", type date}, {"Product", type text}, {"Sales", type number}} ) |
Функция Table.TransformColumnTypes определяет типы данных для каждого столбца, а когда значение не соответствует новому типу, пытается преобразовать соответствующее значение в новый тип. Это может произойти, например, при преобразовании столбца с числовыми значениями в текст.
Использование примитивных типов для фильтрации данных
Прежде чем двигаться дальше, давайте рассмотрим сценарий, в котором можно извлечь выгоду из знания примитивных типов данных. У вас есть таблица со смешанными значениями. Вы хотите сохранить только числовые значения. К сожалению, преобразование всего столбца в числовой тип не будет работать, так как в числа преобразуются и иные сущности:
Рис. 5.12. Преобразование типа столбца не помогает выделить числовые значения
Для решения этой проблемы язык M предоставляет функции, которые могут идентифицировать тип каждого значения. Например, функция Value.Type принимает значение в качестве аргумента и возвращает его тип. Создайте пользовательский столбец с именем Is number?, и настраиваемой формулой:
1 2 |
Код 5.7 = Value.Type( [Mixed Data] ) |
Новый столбец заполнится типами данных для каждого значения:
Рис. 5.13. Функция Value.Type возвращает тип значения
Щелкнув по свободному пространству ячейки, в поле предварительного просмотра появится тип данных для ячейки. Важно отметить, что вы увидите информацию о том, какого типа данные содержатся в ячейке (например, целое число, строка, дата и т.д.), но не само значение данных, например, 42.
Мы же хотим проверить, имеем ли мы дело с числовыми значениями. Для этого используем функцию Type.Is. Она определяет, соответствует ли значение, указанное в первом аргументе, типу данных, указанному во втором аргументе. Внесите изменения в формулу пользовательского столбца Is number?, заменив код 5.7 на:
1 2 |
Код 5.8 Type.Is( Value.Type( [Mixed Data] ), type number ) |
Столбец заполнится значениями true и false:
Рис. 5.14. Выражение, которое проверяет, относится ли значение к определенному типу
Логические значения true соответствуют числовым значениям. У вас есть два варианта фильтрации. Первый – использовать фильтр по столбцу Is number? в интерфейсе Power Query. Второй, более лаконичный подход, заключается в использовании выражения внутри функции Table.SelectRows. В этом случае не нужен столбец Is number? Фильтр применяется к исходному столбцу. Полный код:
1 2 3 4 5 6 7 8 9 10 11 12 |
Запрос 5.9 let Источник = Table.FromColumns( {{"PQ", 999, true, #date(2024, 1, 1), #time(9, 0, 0), 25, "M"}}, {"Mixed Data"} ), Фильтр = Table.SelectRows( Источник, each Type.Is(Value.Type([Mixed Data]), type number) ) in Фильтр |
Рис. 5.15. Выражения могут фильтровать значения определенного типа
Более того, шаг фильтрации можно еще упростить с помощью оператора is:
1 2 |
Код 5.10 Table.SelectRows(Источник, each [Mixed Data] is number) |
Эта альтернатива не только уменьшает объем кода, но и улучшает читабельность. Как видно из этого примера, при наличии правильных функций относительно легко фильтровать значения определенного типа.
Перейдем теперь к пользовательским типам. Эти более сложны типы полезны для точного описания структурированных значений и функций.
Пользовательские типы
Пользовательские типы не являются частью закрытого набора примитивных типов. В то время как примитивные типы описывают простые значения, такие как текст или числа, пользовательские типы описывают более сложные значения. Они актуальны для структурированных значений и пользовательских функций. Чтобы точно описать, что содержат эти значения, нам нужна структура, включающая один или несколько примитивных типов.
В языке M четыре пользовательских типа: списки, записи, таблицы и функции. Они могут быть построены с помощью выражений типов, которые позволяют классифицировать более сложные структуры. В структурированных данных пользовательские типы позволяют:
- классифицировать типы данных элементов списка;
- указать имена и типы данных для каждого поля записи;
- указать имена и типы данных для каждого столбца таблицы;
- указать имена и типы параметров функции, а также тип данных возвращаемого значения.
Эти пользовательские типы основываются на примитивных и абстрактных типах, но накладывают на них структурные ограничения. Примитивные типы классифицируют примитивные значения, пользовательские типы добавляют слой классификации для более сложных значений. Для иллюстрации пользовательских типов будем применять функцию Table.AddColumn. Она удобна, так как позволяет присваивать типы данных столбцам.
Присвоение типа похоже на объявление, что значение соответствует определенному типу без выполнения каких-либо проверок. В этом случае язык M не проверяет, соответствуют ли значения в столбце присвоенному типу. Т.е., вы можете случайно присвоить тип, который не соответствует основным значениям в столбце. На данный момент достаточно быть в курсе этого поведения; мы глубже рассмотрим концепцию присвоения типов позже в этой главе.
Рассмотрим пользовательские типы подробнее, и начнем с типа list.
Тип list
Пользовательский тип списка можно использовать для классификации значений списка. В настраиваемом типе списка, можно указать значение типа list, а также тип данных содержащихся в нем значений. Значения списка представляют собой коллекцию элементов, тип данных которых может быть любым. А вот задание примитивного типа type list не будет указывать, какие типы значений содержатся в нём. Например, при создании нового столбца, содержащего список чисел, четвёртый аргумент функции Table.AddColumn позволяет присвоить тип данных столбцу, и мы указали, что это list. И вот тут начинается самое интересное: когда вы разворачиваете столбец mylist и выбираете Развернуть в новые строки, новые строки не имеют определенного типа данных:
Рис. 5.16. Типы теряются при раскрытии столбца с примитивным типом list
Это связано с тем, что тип list является универсальным (примитивным) и не указывает тип содержащихся в нем значений. Чтобы классифицировать список с помощью более специфического пользовательского типа, укажите, что все элементы списка соответствуют определенному типу данных. Например:
1 2 |
type { text } // возвращает тип списка, содержащий текстовые значения type { number } // возвращает тип списка, содержащий числовые значения |
Чтобы указать этот пользовательский тип, начните со слова type, за которым следует примитивный тип в фигурных скобках. Этот более сложный тип классифицирует данные, как список, а также классифицирует значения элементов списка. Такой подход гарантирует, что Power Query развернет списки в столбец с соответствующими типами:
Рис. 5.17. Типы сохраняются при развертывании столбца с пользовательским типом списка
Таким образом, пользовательский тип позволяет задавать типы данных и сохранять их при развертывании столбцов.
Как видно на рисунках 5.16 и 5.17 значок возле заголовка столбца myList до развертывания не зависит от того, указан примитивный тип type list или пользовательский тип type { number }.
Если вы имеете дело с пользовательским/настраиваемым типом списка и хотите проверить, какие значения принимает список, вы можете использовать функцию…
1 |
Type.ListItem( type { number } ) // возвращает number |
Функция извлекает тип элементов списка, и возвращает его текстовое представление.
Тип record
Запись в Power Query M похожа на строку в таблице. Она состоит из одного или нескольких полей, каждое из которых имеет имя и тип данных. Тип record классифицирует значение записи в целом, но ничего не говорит о типах полей. Чтобы описать, какой тип значений ожидается для каждого поля, нужен настраиваемый (пользовательский) тип записи:
1 2 |
type record // примитивный тип type [ Date = date ] // пользовательский тип |
Пользовательский тип включает имя поля и тип в квадратных скобках. Для создания типа записи с двумя полями: Column1 с текстовыми значениями и Column2 с числовыми значениями, введите…
1 |
type [ Column1 = text, Column2 = number ] |
При добавлении нового столбца со значением record и его развертывании тип данных сохраняется только в том случае, если он явно задан в настраиваемом типе записи. Кроме того, если в записи есть поля, которые не указаны в определении типа, операция Развернуть запись не предлагает эти отсутствующие поля:
Рис. 5.18. Поля, отсутствующие в пользовательском типе, не отображаются при раскрытии столбца
Если при развертывании столбца с записями какие-то поля отсутствуют, обязательно проверьте определение типа. Скорее всего, в нем эти поля пропущены. При создании пользовательского типа записи нет необходимости указывать тип каждого столбца. Например, вполне допустимо такое определение:
1 |
type [ Column1 = text, Column2 ] |
При развертывании столбца с записями любые поля, для которых не указан тип, получат тип any.
Вы также можете использовать синтаксис открытой записи. Маркер открытой записи – три точки (…) – определяет запись, которая допускает появление новых (неизвестных) полей в будущем. Например, у вас есть запись с набором полей. Вы хотите указать типы только для полей Date и Amount. Все остальные поля могут иметь тип any. Используйте синтаксис:
1 |
type [ Date = type date, Amount = type number, ... ] |
Этот настраиваемый тип записи говорит, что запись может содержать или не содержать больше полей, чем описано. Определение записи примитивного типа эквивалентно открытой записи. Например выражение…
1 |
type [...] = type record |
… вернет true.
Перейдем к одному из самых важных пользовательских типов – type table. Так как большинство преобразований в Power Query связано с таблицами, этот пользовательский тип будет встречаться наиболее часто.
Тип table
Таблицу можно рассматривать как список записей с определённой структурой. Каждый столбец в таблице имеет тип данных, и все записи (строки) в таблице соответствуют этой структуре (типу). Тип table является базовым типом всех таблиц и задается синтаксисом:
1 |
type table |
… или …
1 |
type table [] |
Оба этих выражения означают, что у нас есть таблица, но мы не определяем, какие столбцы она содержит. Эти столбцы могут быть добавлены позже, и они могут быть любыми.
Вариант type table [] – единственный случай, когда тип table может иметь открытый строковый тип. Пользовательские типы таблиц всегда должны иметь форму закрытой записи.
Определение пользовательского типа таблицы подобно определению типа record. Например, при выполнении группировки (Group By) движок автоматически генерирует пользовательский тип таблицы, чтобы указать имена и типы столбцов:
1 |
type table [ Column1 = text, Column2 = number ] |
Этот тип указывает, что все строки в таблице соответствуют этим значениям. При желании можно даже задать пользовательский тип записи и использовать его для определения типа таблицы:
1 2 3 4 5 6 |
Код 5.11 let myRecordType = type [ Column1 = number, Column2 = text ], myTableType = type table myRecordType in myTableType |
Код возвращает тип table, который сам по себе редко используется в качестве значения. Чаще всего он встречается внутри табличной функции для спецификации столбцов (имен и типов данных). Код 5.11 иллюстрирует, что тип table может включать в своем определении тип record. Однако, между типом record и типом table есть тонкие различия. Для того чтобы тип record использовать в определении пользовательского типа таблицы, он должен быть закрытым. Это означает, что он не может иметь необязательных полей.
Наверное было бы полезно определять открытый тип table, чтобы задать типы некоторых столбцов и указать, что не уверены в типе других. К сожалению, это не допускается.
Аналогично ситуации со списками и записями, когда вы определяете пользовательский тип таблицы и не включаете определенные столбцы, они не отображаются в диалоговом окне Развернуть столбцы.
Уникальной особенностью типа table является то, что он поддерживает определение ключей таблицы. Power Query использует информацию о ключах для повышения производительности, например, при выполнении операций объединения. Вы можете использовать функцию Type.AddTableKey для установки ключа в типе table. Например, следующий код устанавливает Column1 в качестве первичного ключа для типа table:
1 2 3 4 5 6 |
Код 5.12 let myType = type table [ Column1 = text, Column2 = number ], setKeys = Type.AddTableKey(myType, {"Column1"}, true) in setKeys |
На выходе кода – тип table с информацией о ключе.
Рис. 5.18а. Таблица с первичным ключом
После установки ключа вы можете получить существующие ключи с помощью функции Type.TableKeys или заменить их, используя функцию Type.ReplaceTableKeys.
Тип function
Вы можете присвоить функции примитивный тип function, и она будет работать. Однако это не наложит никаких ограничений на типы входных параметров и значение функции. Определение пользовательского типа функции преодолевает это ограничение.
Тип функции состоит из возвращаемого типа и списка параметров функции (их может быть ноль или больше). Тип функции совместим с другим типом функции, если их возвращаемые типы совместимы, а типы параметров идентичны. Определение пользовательского типа функции может быть выполнено с помощью следующего синтаксиса:
1 2 |
Код 5.13 type function (date as date, days as number) as date |
Вы можете определить значение функции, не указывая тип:
1 2 |
Код 5.14 (date, days) => Date.AddDays(date, days) |
В этом случае значение автоматически получает пользовательский тип функции:
1 |
type function (date as any, days as any) as any |
Утверждения типа не являются обязательными при определении значения функции (код 5.14). Однако, при указании пользовательского типа функции (код 5.13) вы должны включать утверждения типа для всех параметров и результата. Следующие выражения вернут синтаксическую ошибку:
1 2 3 |
type function (date, days) as date type function (date as date, days) as date type function (date as date, days as number) |
При определении типа данных для параметра в пользовательской функции вы можете использовать только примитивные типы. Указание пользовательского типа приведет к ошибке.
В случаях, когда вам нужно применить пользовательский тип функции к ранее определенной функции, вы можете сделать это только с помощью функции Value.ReplaceType.
Пояснения Chat GPT. Предположим, у вас есть функция, которая добавляет дни к дате:
myFunction = (date, days) => Date.AddDays(date, days)
Эта функция работает, но параметры и возвращаемое значение имеют тип any. Если вы хотите задать типы данных для параметров и возвращаемого значения этой функции, вы используете Value.ReplaceType:
1 2 3 4 5 6 7 8 |
let myFunction = (date, days) => Date.AddDays(date, days), typedFunction = Value.ReplaceType( myFunction, type function (date as date, days as number) as date ) in typedFunction |
До сих пор в этой главе мы рассматривали доступные типы данных и то, как мы можем указывать их, используя примитивные и пользовательские типы. Это часто является утомительным процессом, требующим ручной работы. Однако, вам не всегда нужно вручную определять типы данных. В Power Query есть методы для автоматического выполнения этой задачи.
Определение типа
Первое взаимодействие с типами данных в Power Query у вас скорее всего произойдет при импорте данных. Данные часто представлены в виде таблицы, столбцы которой имеют определенный тип. При использовании настроек по умолчанию Power Query автоматически распознает типы данных в импортируемых таблицах. То, как Power Query определяет типы данных, будет зависеть от используемого источника данных и ваших настроек Power Query. Доступны два метода:
- Извлечение типов данных из источников
- Автоматическое определение типов
Когда вы импортируете данные из структурированного источника (SQL, Oracle, Azure Data Lake, OData feed и т.д.), Power Query автоматически получает схему таблицы из этих источников. Эта схема доступна только в структурированных источниках. Она содержит информацию о типах данных каждого столбца. Если источник содержит информацию о типах данных импортированных столбцов, Power Query устанавливает типы данных в соответствии с этим определением.
Если вы импортируете данные из неструктурированного источника, который не содержит схемы, движок использует другой подход – автоматическое определение типов данных столбцов в наборах данных. Это особенно полезно при работе с файлами Excel, CSV или текстовыми. PQ изучает первые 200 строк набора данных, чтобы определить наиболее подходящий тип данных для каждого столбца.
Предположим, вы работаете с таблицей без указания каких-либо типов данных:
Рис. 5.19. Таблица без информации о типе
Чтобы автоматически определить типы данных, в редакторе Power Query выберите все столбцы и пройдите Преобразование –> Определить тип данных. Power Query проанализирует данные, сделает обоснованное предположение о типах, и преобразует столбцы:
Рис. 5.20. Операция определения типа данных преобразует типы
По умолчанию Power Query выполняет операцию определения типа данных при подключении к неструктурированному источнику. У вас есть возможность изменить поведение по умолчанию. В редакторе Power Query пройдите Файл –> Параметры и настройки –> Параметры и на вкладке Глобальные –> Загрузка данных в области Определение типов установите переключатель в положение Никогда не определять типы столбцов для неструктурированных источников.
Если автоматическое определение типов отключено в глобальных настройках или для конкретного файла, вы все равно можете вручную применить функцию определения типа данных, как указано на рис. 5.20.
При выборе настроек в Power Query учитывайте ваши конкретные потребности и обстоятельства. Хотя функция автоматического определения типов данных обычно эффективно предсказывает типы столбцов, она основывает свой анализ только на первых 200 строках. Поэтому, независимо от того, включаете ли вы автоматическое определение на глобальном уровне или на уровне файла, всегда полезно проверять тип столбца.
Мы рассмотрели типы данных и объяснили, почему они важны как концепция. Кроме этого разные значения поддерживают разные операции. Из-за этого приходится преобразовывать значения в другой тип.
Преобразование типов данных
Независимо от того, импортируете вы данные или создаете их самостоятельно, установка правильного типа данных имеет решающее значение. Иногда вам нужно будет временно изменить тип данных, чтобы все работало правильно, например, чтобы определенные данные были приняты какой-то функцией или при смешивании разных типов данных.
Например, у вас есть число, введенное как текст. Чтобы выполнять с ним математические операции, нужно изменить его тип с текста на числовой формат. Или вы хотите добавить дату в текстовое сообщение. Сначала нужно переключить дату в текстовый формат. Такой тип преобразования часто требуется, когда ваши данные хранятся в таблицах. M предоставляет два основных способа преобразования значений в другой тип:
- Преобразовать столбец в другой тип.
- Создать новый столбец, и применить для него функцию преобразования.
Преобразование типов значений
Рассмотрим ряд функций, которые позволяют преобразовывать значение из одного типа в другой. Они делятся на две категории:
- Функции извлечения типов извлекают определенный тип значения из более общих входных данных, которые могут быть любыми. Например, Date.From, Text.From и Number.From. Они доступны для различных типов исходных данных: binary, date, duration, function, logical, number, time, datetime, datetimezone, text.
- Функции преобразования типов преобразуют данные из одного типа в другой. Они работают в обоих направлениях. Например, ToText и Date.FromText, Binary.ToText и Binary.FromText.
Функции преобразования и извлечения типов обычно используются во время вычисления выражения. Например, следующие выражения извлекают значения даты и числа:
1 2 3 4 |
Date.From( "31 dec 2024" ) // возвращает #date( 2024, 12, 31 ) Date.From( 45657 ) // возвращает #date( 2024, 12, 31 ) Number.From( "550" ) // возвращает 550 Number.From( true ) // возвращает 1 |
Как видите, функции извлечения довольно гибкие в том, какие аргументы они принимают. С другой стороны, функции преобразования требуют строго определенных типов на вход:
1 2 |
Number.ToText( 125 ) // возвращает "125" Record.ToList( [A = 1, B = 2, C = 3] ) // возвращает {1, 2, 3 } |
При работе с таблицами функции преобразования внутри выражений имеют два преимущества:
- сохраняется исходная структура таблицы (не добавляются новые столбцы);
- не возникает дополнительного шага по преобразованию типов данных; вы можете выполнять преобразования прямо внутри выражения, которое уже используется для обработки данных.
Следующее упражнение мы начнем с таблицы (запрос Converting Value Types в файле Chapter 5 — Data Types.Pbix):
Рис. 5.22. Таблица, содержащая даты, текст и числовые значения
Я выделил столбец Price, прошел Преобразование –> Замена значений, и поменял точки (.) на запятые (,).
Таблица содержит три столбца с типами данных: Date, Text и Number. Вы хотите объединить информацию из этих столбцов в одно связное предложение. В языке M перед конкатенацией нужно преобразовать значения в текст. Для этой задачи идеально подходит функция Text.From:
1 2 3 4 5 6 7 8 |
Код 5.15 "On " & Text.From([Date]) & " we sold " & Text.From([Price]) & " worth of " & [Product Name] & "s" |
Добавьте код 5.15 в пользовательский столбец:
Рис. 5.23. Функции преобразования превращают значения в текст перед конкатенацией
При использовании функции преобразования в выражении типы исходных столбцов таблицы остаются неизменными для будущих операций.
Язык M поддерживает не все преобразования типов. Некоторые комбинации приводят к ошибке. Следующая матрица показывает, какие преобразования поддерживаются. Значения на левой стороне показывают текущие значения, а значки указывают, можно ли преобразовать значение в тип, указанный в верхней строке.
Рис. 5.24. Матрица преобразования типов данных
Изучив, как функции преобразования изменяют значение без изменения исходных типов столбцов, перейдем теперь ко второму подходу.
Преобразование типов столбцов
Этот метод включает в себя изменение типа данных всего столбца. Он особенно полезен, когда требуется загрузить соответствующий тип данных в приложение (например, на лист Excel), или когда результат преобразования будет использован в нескольких других вычислениях. На рисунке представлен набор данных, столбец Neighbours которого отформатирован как целое число:
Рис. 5.25. Преобразуйте столбец в нужный тип, выбрав его в заголовке столбца
Если нужно изменить тип на text, дважды кликните на значок 123, и выберите тип Текст. Этой операции будет соответствовать код::
1 |
= Table.TransformColumnTypes(#"Измененный тип",{{"Neighbors", type text}}) |
Функция Table.TransformColumnTypes принимает два аргумента: таблица, которую вы хотите изменить (в данном случае это таблица с предыдущего шага #"Измененный тип") и список списков, где каждый элемент представляет собой пару: имя столбца и новый тип данных. У нас список состоит из одного списка – {"Neighbors", type text}.
Функция выполняет две операции:
- назначает новый тип данных указанному столбцу, что найдет отражение в значке рядом с заголовком столбца;
- пытается преобразовать все значения в столбце в новый тип данных.
Установка типа данных столбца сработает всегда – иконка в заголовке изменится. А вот преобразование значений может потерпеть частичную (или полную) неудачу. Например, если мы попытаемся преобразовать столбец из целого числа в тип time вместо text, то получим ошибку:
Рис. 5.26. Преобразование значения в неподдерживаемое приводит к ошибке DataFormat.Error
Число не может быть преобразовано в значение времени. Вернитесь к рис. 5.24, на котором показано, какие преобразования поддерживаются в языке M.
В этом разделе мы увидели, что типы столбцов можно преобразовывать так же, как и значения. Вы узнали, какие преобразования типов данных поддерживаются и когда могут возникнуть ошибки. Есть еще одна важная тема, которую нужно изложить. Даже если преобразование прошло успешно, оно может привести к потере данных. В следующем разделе мы рассмотрим, когда это происходит.
Предотвращение потери данных во время преобразования
При изменении значений с одного типа данных на другой важно быть осторожным и понимать, что означает это изменение. Во время преобразования типов Power Query попытается выполнить преобразование, если это возможно, но имейте в виду, что это может привести к потере данных или их точности. Этот процесс влияет на то, как данные хранятся и, как возвращаются в приложение, например Power BI.
В отличие от преобразования типов в Power Query, форматирования на уровне приложения (Power BI Desktop или Excel) работает по-другому. Маски формата в приложении влияют лишь на визуальное представление, не изменяя базовое хранение данных. Это различие помогает понять, как корректировки данных влияют на серверную часть (хранилище) и внешний интерфейс (отображение) в Power BI. Давайте рассмотрим пример, где потеря данных является проблемой.
У нас есть данные о ежедневных платных парковках в Сиэтле, штат Вашингтон. Типы данных были определены в соответствии со схемой, сопровождающей набор данных:
Рис. 5.27. Таблица со столбцом Amount Paid, содержащим десятичные числа
В столбце Amount Paid я заменил точки (.) на запятые (,), а также изменил тип с локалью столбца Transaction Date.
Допустим, нас интересует анализ целой части долларов по полю Amount Paid (а центы на данный момент не важны). Изменим тип столбца на целое число, используя интерфейс:
Рис. 5.28. Таблица со столбцом Amount Paid, переведенной в целые числа
Если теперь сделать обратное преобразование и вернуть столбцу Amount Paid десятичный тип, исходные центы не будут восстановлены. Это подчеркивает ключевой момент: изменение типа данных влияет на его хранение и может снизить степень детализации данных.
В некоторых случаях может показаться, что изменение типов данных увеличивает детализацию, но это не так. Например, преобразование столбца Transaction Date в тип datetime добавляет временные значения к датам:
Рис. 5.29. Преобразование типа date в datetime добавляется время по умолчанию, равное 12 AM
В локали RU добавляемое время по умолчанию равно 0:00:00
Эти примеры показывают важность понимания последствий преобразования типов данных. Power Query не помешает преобразованиям… но данные могут быть потеряны.
Преобразование типа данных может привести к одному из четырех результатов:
- Успешно: изменение типа данных осуществляется без увеличения или потери данных.
- Успех с потерей данных.
- Успех с дополнительными данными, которые могут быть верными, а могут и не быть.
- Ошибка: изменение типа данных не было реализовано из-за несовместимости входных и выходных типов.
Хотя потери данных из-за преобразования типов обычно следует избегать, в некоторых случаях это делают умышленно. Например, преобразование datetime в date удаляет компонент времени, но положительно влияет на сжатие данных при загрузке в Power BI.
Еще одна тема, связанная с преобразованием, – это влияние языкового стандарта. В зависимости от локали одно и то же значение может интерпретироваться по-разному.
Влияние региона/культуры (locale/culture)
В регионах мира действуют различные соглашения о виде данных: десятичный разделитель (запятая или точка), формат дат, использование обозначения AM/PM. Эти региональные соглашения оказывают влияние на то, как преобразуются типы данных в Power Query, поскольку процесс преобразования основан на распознавании формата.
Пусть у вас есть столбец текстовых значений, представляющий даты с 1 по 12 января 2024 года. В зависимости от используемых соглашений эти даты могут интерпретироваться по-разному. Их можно рассматривать как даты с 1 по 12 января 2024 года или как первый день каждого месяца в течение 2024 года. Power Query устраняет эту неоднозначность с помощью параметров языкового стандарта. По умолчанию он соответствует языковым и региональным настройкам вашего компьютера. Таким образом, если компьютер настроен на интерпретацию дат в формате ММ/ДД/ГГГГ, Power Query использует этот формат при преобразовании текстовых значений в дату.
Однако, что делать, если нужно преобразовать текстовые значения в даты, а ваши настройки отличаются от настроек коллеги? Как обеспечить единообразное распознавание дат в разных системах? Ключевым моментом является использование параметров языкового стандарта для преобразования значений. Как это работает?
При нажатии на иконку типа данных, расположенную в заголовке столбца, появляется меню. В этом меню предлагаются различные варианты типов данных. Последняя опция в списке позволяет изменить тип данных в соответствии с настройками языкового стандарта.
Рис. 5.30. Пользовательский интерфейс позволяет изменять тип данных, используя локаль
При выборе этого параметра открывается диалоговое окно, в котором можно выбрать нужный тип данных и языковой стандарт. Эта функция предназначена для учета локальных соглашений о форматировании в процессе преобразования типов данных. Например, если в качестве типа данных в диалоговом окне выбрать Date, отобразится предварительный просмотр:
Рис. 5.31. Изменение типа с использованием локали показывает предварительный просмотр ожидаемых соглашений о входных данных
В этом предварительном просмотре показано, какие входные значения Power Query ожидает при использовании выбранного языкового стандарта. Это означает, что выбранная страна (в поле Locale) представляет формат исходных данных, а не формат, в котором вы хотите получить выходные данные. Выходные данные всегда будут соответствовать текущим региональным настройкам вашего ПК. Предварительный просмотр особенно полезен для проверки соответствия загружаемых данных ожидаемому формату для выбранного языкового стандарта.
При подтверждении преобразования с помощью языкового стандарта English (Netherlands) Power Query будет обрабатывать исходные данные в предположении, что входной формат соответствует English (Netherlands). При этом сгенерится код:
1 |
Table.TransformColumnTypes( Source, {{"Date", type date}}, "en-NL") |
Третий необязательный аргументом этой функции – код языка и региональных параметров.
Если вместо этого вы ожидаете, что формат входных данных будет m/d/yyyy, необходимо использовать языковой стандарт English (United States), а код изменится на:
1 |
Table.TransformColumnTypes( Source, {{"Date", type date}}, "en-US") |
Этот подход предлагает удобный способ изменения (распознавания) типов столбцов через интерфейс. Кроме того, Power Query включает несколько функций, которые принимают код языка и региональных параметров для управления процессом преобразования. Эти функции часто используются для извлечения или преобразования типов данных, а также форматирования значений в виде текста. Например, интерпретация дат в функции Date.From зависит от кода языка и региональных параметров:
1 2 |
Date.From( "01-12-2024", "nl-NL" ) // возвращает #date(2023,12,1) Date.From( "01-12-2024", "en-US" ) // возвращает #date(2023,1,12) |
Поведение функция Text.Upper также зависит от языкового стандарта. В турецком и английском языках прописные буквы различаются:
1 2 |
Text.Upper( "i am lucky", "tr-TR" ) // возвращает "İ AM LUCKY" Text.Upper( "i am lucky", "en-US" ) // возвращает "I AM LUCKY" |
Совет профессионала. Всякий раз, когда в документации к функции упоминается необязательный параметр culture, это означает, что вы можете использовать код языка и региональных параметров для влияния на результат. Если параметр культуры доступен в функции, мы рекомендуем его использовать. Это гарантирует, что ваш результат будет предсказуемым независимо от языка системы ваших пользователей.
Мы переходим к фасетам – концепции, которая позволяет предоставлять дополнительные сведения о типах данных.
Фасеты
До сих пор мы рассматривали базовые типы данных в языке M: примитивные и пользовательские. Однако вы могли столкнуться со значениями, которые выглядят как типы данных, но с другой нотацией. Например, при выборе нового типа данных вы могли видеть всплывающее окно:
Рис. 5.32. Меню для изменения типов столбцов
В меню показаны четыре различных способа обозначения числовых значений. Если в таблице для столбца Weight вы выберите Десятичное число, а для столбца Price – Десятичное число с фиксированной запятой, то обнаружите код:
1 2 3 4 5 6 7 |
Код 5.16 Table.TransformColumnTypes( Source , { { "Weight", type number }, { "Price", Currency.Type } } ) |
Первое преобразование типа ссылается на знакомый type number, но что такое Currency.Type? Это – фасет. Фасеты предоставляют дополнительную информацию о типах данных. Они полезны при взаимодействии Power Query с приложениями (Power BI или Excel). Фасеты не изменяют то, как Power Query работает с данными. Фасеты предоставляют ценную информацию другим системам. Например, фасет Int64.Type указывает: это 64-битные целые числа. Приложение, получающее данные, использует информацию о типе для резервирования памяти и обработки данных.
Фасеты дают подсказки о данных, например, какова максимальная длина текстового поля или точность числа. Имеются библиотечные функции Power Query для работы с информацией о фасетах. Однако внутри Power Query вы редко увидите фасеты в действии. Обычно они используются при получении данных из других систем или передаче данных в другие системы. Приложения часто обладают более сложными системами типов, чем Power Query.
Многие фасеты не имеют значения для повседневного использования… за исключением утверждения типов (Type Claims). Этот фасет важен, так как часто появляется даже в пользовательском интерфейсе и используется во многих функциях. Для большинства пользователей детальное понимание всех фасетов не обязательно для того, чтобы стать опытным пользователем Power Query. Однако, поскольку вы столкнетесь с утверждением типов при использовании интерфейса, мы кратко рассмотрим эту тему в следующем разделе.
В зависимости от того, как вы импортируете данные, таблицы могут иметь или не иметь связанную с ними информацию о фасетах. Если в базе данных SQL содержится информация о фасетах, Power Query ее извлечет.
Предположим, что у вас примитивный тип таблицы (type table). По умолчанию с этим значением не будет связано никаких фасетов. Вы можете добавить информацию о фасетах с помощью функции Type.ReplaceFacets:
1 2 3 4 5 6 7 8 |
Запрос 5.17 let myType = type table, myFacets = [NumericPrecisionBase = 10, MaxLength = 4, NativeTypeName = "INTEGER"], addFacets = Type.ReplaceFacets(myType, myFacets), showFacets = Type.Facets(addFacets) in showFacets |
На шаге myFacets значения фасетов определяются для NumericPrecisionBase, MaxLength и NativeTypeName. На шаге addFacets сведения о фасетах добавляют к типу таблицы. На шаге showFacets функция Type.Facets извлекает информацию о фасетах:
Рис. 5.33. Фасеты, связанные с типом таблицы
Как видно, существует много дополнительной информации, которую может содержать тип данных. Фасеты, показанные на рисунке, считаются простыми. Как правило, вы не будете добавлять такие значения самостоятельно, но полезно знать, что их вполне допустимо использовать в выражениях. Помните, что внешние приложения, работающие с Power Query, часто используют эти сведения для улучшения хранения и обработки данных.
В предыдущем примере мы получили сведения об общем типе таблицы, но вы можете захотеть получить сведения для каждого столбца таблицы. Используйте функцию Table.Schema. В следующем примере создается таблица, а затем возвращаются сведения о ее фасетах:
1 2 3 4 5 6 7 8 9 |
Запрос 5.18 let myTable = #table( type table [ProductKey = Int64.Type, Product = Text.Type], {{1, "Apple"}, {2, "Prume"}} ), tableFacets = Table.Schema(myTable) in tableFacets |
Код 5.18 возвращает таблицу, содержащую сведения о схеме для каждого столбца:
Рис. 5.34. Информация о Type Facet для столбцов, возвращаемых Table.Schema. Утверждение типа и тип данных – в третьем и четвертом столбцах
Информации о фасетах также можно извлечь не из самой таблицы, а из типа таблицы с помощью функции Type.TableSchema:
1 2 3 4 5 6 |
Запрос 5.19 let myTableType = type table [ProductKey = Int64.Type, Product = Text.Type], typeFacets = Type.TableSchema(myTableType) in typeFacets |
Запрос 5.19 возвращает тоже, что и 5.18.
Итак, фасеты – это то, как Power Query хранит дополнительные сведения (о типах), полученные из внешней системы. Power Query может затем передать эту информацию внешней системе – приложению. Простейшие фасеты (возвращаемые функцией Type.Facets) часто специфичны для Power Query и не обеспечивают легкий обмен между приложениями. Однако некоторые фасеты более универсальны и могут использоваться для передачи информации о типах между системами. Это и есть Type Claims. Type Claims – это фасеты, с которыми вы, вероятно, будете работать. Именно поэтому следующий раздел посвящен им.
Утверждения типов (Type Claims)
Ранее в этой главе мы изучили примитивные и пользовательские типы данных, доступные в языке M, например, date и logical. Хотя движок запросов (mashup engine) Power Query в основном использует эти стандартные типы данных, внешние приложения могут работать с более тонкими классификациями типов. Здесь и вступают в игру Type Claims.
Type Claims относятся к категории фасетов в языке M. Они предоставляют способ указания более детальной информации о типах данных, выходящей за рамки того, что обычно используется в Power Query. Например, хотя Power Query может обрабатывать числа одинаково, база данных может различать десятичные, 8-, 16-, 32- и 64-битные целые числа. Эти подробные спецификации не важны в Power Query, но они ценны для внешних приложений, которые обрабатывают и хранят данные, полученные от Power Query. Думайте о Type Claims как о универсальном языке для передачи информации о типах данных от одной внешней системы к другой.
Какие утверждения типов доступны в языке M?
Доступные утверждения типов
В языке M утверждения типов соответствуют типам данных. Например, Currency.Type и Decimal.Type имеют тип number, а Text.Type и Password.Type относится к типу text:
Рис. 5.35. Обзор утверждений типов и соответствующих им базовых типов данных
Пояснения Chat GPT. GUID (Globally Unique Identifier) – 128-битное значение, предназначенное для уникальной идентификации объектов в распределенной системе. GUID также известен как UUID (Universally Unique Identifier). Вероятность того, что два сгенерированных GUID будут одинаковыми, крайне мала. Это делает GUID полезными для идентификации объектов в больших и распределенных системах. GUID обычно представлен в текстовом формате, разделенном дефисами. Формат включает 32 шестнадцатеричных цифры, разделенных на пять групп: 8-4-4-4-12. Пример GUID: 123e4567-e89b-12d3-a456-426614174000.
Важно отметить, что утверждения типов сами по себе не являются типами данных. Они функционируют, присваивая значению базовый тип, дополненный информацией о фасетах. Тем не менее, при работе с функцией, требующей указания в аргументе типа данных, допустимо использовать как базовый тип, так и утверждение типа.
Рассмотрим, как мы можем использовать утверждения типов для преобразования значений.
Преобразование значений с помощью утверждений типов
Рассмотрим сценарий, в котором необходимо преобразовать столбец в числовой тип. Можно использовать либо базовый тип, либо утверждение типа:
1 2 |
Table.TransformColumnTypes( Source, {{"Sales", type number}} ) Table.TransformColumnTypes( Source, {{"Sales", Int64.Type}} ) |
Оба выражения допустимы, но они по-разному поведут себя со следующими числовыми типами:
- Тип валюты: Type возвращает число, содержащее до 4 знаков после запятой.
- Целочисленные типы: Int8, Int16, Int32 и Int64 возвращают целые числа, обрезая десятичные знаки.
Посмотрите на таблицу с четырьмя столбцами, каждый из которых содержит идентичное текстовое значение. Изучите, как меняется возвращаемое значение при преобразовании разных утверждений типов:
Рис. 5.36. Преобразование типов может привести к потере точности
В средней строке показано, как выглядят значения после преобразования на основе утверждения типа, указанного в заголовке столбца. Обратите внимание на изменение точности данных после преобразования. Например, когда текстовое значение преобразуется в Int64.Type, удаляются все десятичные знаки, а преобразование значений в Currency.Type сохраняет точность до четырех знаков после запятой. Это иллюстрирует, как различные утверждения типов влияют на точность и формат преобразованных данных.
Теперь внимательно посмотрите на нижний ряд. Когда мы преобразуем различные типы данных обратно в десятичный тип, который может содержать большое количество десятичных знаков, мы видим потерю точности для столбцов Int64.Type и Currency.Type.
Важно понимать, что использование Type Claim для преобразования типа значения может привести к безвозвратной потере точности. Единственный способ вернуться к исходной точности – отменить шаг преобразования.
Ранее в этой главе вы рассмотрели, как можно узнать тип данных значения. Можно ли вернуть тип для утверждения типа?
Проверка утверждений типов
В языке M утверждения типов в основном используются в таблицах. Поэтому в M проверить утверждения типов можно только в таблицах. Для этого используется функция Table.Schema. Если нужно изучить фасеты, связанные с конкретным значением, наиболее простой метод –представить значения в виде таблицы. В следующем примере мы создаем таблицу специально для проверки типа:
1 2 3 4 5 6 7 |
Запрос 5.20 let myType = Character.Type, mySchema = Table.Schema(#table(type table [Col1 = myType], {})), typeInfo = mySchema[[TypeName], [Kind]] in typeInfo |
Мы определяем myType как Character.Type. Создали пустую таблицу с одним столбцом, указав Character.Type в качестве типа столбца. Использовали функцию Table.Schema для извлечения информации о схеме таблицы, оставив в схеме только столбцы TypeName и Kind:
Рис. 5.36а. Имя типа столбца и его тип
Итак, вы узнали, что фасеты предоставляют дополнительную информацию о типах данных. Эти сведения особенно полезны при импорте и экспорте данных в/из Power Query. Обращайте внимание на утверждения типов, изменяющие точность значений, так как они могут изменить значения незаметно для вас.
Рассмотрим теперь концепцию присвоения типов, используемую для типов данных и утверждения типов. В отличие от преобразования значений, присвоение типов – более смелый подход, требующий осторожности из-за потенциальных рисков.
Присвоение типа
Присвоение типа – процесс, в котором вы объявляете, что значение соответствует определенному типу. Этот процесс предписывает движку принять ваш типа значения. В отличие от преобразования типов данных, при котором Power Query активно преобразует значения в назначенный тип, присвоение типа помечает тип значения, не изменяя само значение. Во время атрибуции проводится ограниченная проверка соответствия – совместим ли объявленный тип с внутренним примитивным типом значения.
В оригинале используются термины ascription и ascribing. При переводе я употребляю слова присвоение и приписывание, как синонимы.
Такой подход может привести к некоторым сложностям. Например, если вы приписываете тип text столбцу, содержащему целые числа, Power Query пометит тип столбца как текст, но значения будут храниться в числовом виде. Следовательно, вы не сможете применить к этим значениям текстовые функции, так как они по-прежнему являются числами.
Это существенно отличается от преобразования значений. При преобразовании столбца система помечает значения, проверяет совместимость и преобразует их в новый тип данных. Если значение не может быть преобразовано, Power Query возвращает ошибку. Это гарантирует, что результирующие значения действительно соответствуют выбранному типу данных или содержит ошибку. В процессе приписывания вы не получаете ошибку.
Рассмотрим, как можно присвоить значение. Продолжим наш пример с таблицей, содержащей только столбец даты. Выберите столбец, пройдите Добавить столбец –> Из даты и времени –> Дата –> День –> День года:
Рис. 5.37. Table.AddColumn приписывает Int64.Type столбцу с числовыми значениями
Эта операция создала новый столбец с именем День года и автоматически присвоила целое число (In64.Type) в качестве типа столбца (четвертый аргумент функции Table.AddColumn). Обратите внимание, что эта функция не преобразует тип столбца, а присваивает его. Это означает, что преобразование значений не происходит.
В оригинале: Notice that this function does not transform a column type but ascribes one. That means no value conversion takes place. На самом деле в столбце День года содержатся целые числа 1, 2, 3, …, и при преобразовании их в даты, возвращается:
Рис. 5.37а. Значения дат в столбце День года утеряны
Не ясно, что авторы имели в виду.
Обратите внимание, если выбрать столбец День года, большинство числовых операций на ленте подсвечены, что говорит о их доступности.
Рис. 5.37б. Для столбца День года доступны числовые операции
Теперь давайте скорректируем формулу и вставим тип, несовместимый с числовыми значениями, например, текстовый:
Рис. 5.38. Table.AddColumn присваивает Text.Type столбцу с числовыми значениями
Значок типа в заголовке столбца изменился. Кроме того, обратите внимание, что столбец больше не поддерживает числовые операции. А теперь самое интересное. Несмотря на то, что в заголовке столбца указано, что столбец имеет тип text, фактические значения являются числовыми. Попытка выполнить выражение, требующее текстового значения, приводит к ошибке. Например, если бы значение имело тип text, мы могли бы добавить его к тексту "Day ". Однако конкатенация возвращает ошибку:
Рис. 5.39. Хотя столбцу приписан Тext.Type, значения по-прежнему имеют тип number
В этом примере столбец помечается как имеющий определенный типа без выполнения дополнительных проверок.
Понимание приписывания полезно по нескольким причинам:
- Производительность. Приписывание не требует, чтобы движок сканировал все данные. Объявляя тип, Power Query доверяет его правильности. Тем самым повышается производительность, поскольку движок обходит расширенную проверку типов данных.
- Управление рисками. Приписыванию присущ риск. Если присвоить тип данных столбцу, содержащему значения, несовместимые с этим типом, во время загрузки данных в приложение могут возникать ошибки. Например, Power BI в процессе загрузки ожидает, что данные будут соответствовать назначенным типам, и может возвращать ошибки при наличии несоответствий.
- Интеграция с хост-приложениями. Присвоение типов особенно актуально при загрузке данные в место назначения (Power BI или Excel). Эти приложения могут использовать информацию о типе данных для обработки и хранения. Неправильная атрибуция может привести к проблемам совместимости и ошибкам при загрузке данных в эти приложения.
Какие функции могут присвоить тип данных значению?
Функции, поддерживающие присвоение типов
Записи в Power Query могут иметь несколько полей, каждое из которых характеризуется типом данных. Для определения типов такие функции, как Record.FromList, поддерживают пользовательский тип записи. Включение пользовательского типа записи предоставляет функции как имена настраиваемых полей, так и соответствующие им типы:
1 2 3 4 5 6 7 8 |
Запрос 5.21 let SpecifyCustomTableType = Record.FromList( {2024, "M-Language", true}, type [Year = number, Topic = text, LearningM = logical] ) in SpecifyCustomTableType |
Record.FromList создает запись, где Year – число, Topic – текст, а LearningM – логическое значение.
Некоторые функции позволяют создавать таблицы с пользовательскими типами данных. Наиболее распространенными из них являются #table, Table.FromRecords, Table.FromList, Table.FromColumns, Table.FromRows и Table.FromValue. Следующий код создает таблицу, указывая имена и значения столбцов:
1 2 |
Код 5.22 #table({"BookID", "Title"}, {{1, "Animal Farm"}, {2, "Brave New World"}}) |
Столбцам таблицы автоматически присваивается тип any. Т.е., указывать типы столбцов не обязательно. Однако, когда это требуется, можно предоставить функции #table пользовательский тип таблицы. Например, можно приписать столбцу BookID тип Int64.Type, а Title – text:
1 2 3 4 5 6 7 8 |
Код 5.23 let SpecifyCustomTableType = #table( type table[ BookID = Int64.Type, Title = text ], { { 1, "Animal Farm" }, { 2, "Brave New World" } } ) in SpecifyColumnNames |
Рис. 5.40. Таблицы, в левой указаны только имена столбцов, в правой – еще и типы
Присвоение типов при изменении таблиц
Для изменения таблиц наиболее часто используют Table.TransformColumns, Table.AddColumn и Table.Group.
Рассмотрим таблицу, подобную приведенной выше, в которой столбец Title содержит текстовые значения. Чтобы изменить столбец с помощью Table.TransformColumns, можно использовать пользовательский интерфейс. Выберите столбец Title и пройдите Преобразование –> Формат –> ВЕРХНИЙ РЕГИСТР. Добавится шаг:
1 2 3 4 5 |
Код 5.23 Table.TransformColumns( Source, {{"Title", Text.Upper, type text}} ) |
Первый аргумент обращается к таблице, которую нужно преобразовать. Второй аргумент – список с описанием операции, выполненной над столбцом, в формате {«имя столбца», преобразование, новый тип столбца}.
Присвоение типов любому значению
Функция Value.ReplaceType предназначена для замены типа данных значения. В первом аргументе указывается изменяемое значение, во втором – тип, который вы хотите присвоить:
1 |
Value.ReplaceType( 5, type number ) |
Здесь значению 5 присвоен тип number. Но функция не ограничивается приписыванием только примитивных типов. Она также может работать с более сложными пользовательскими типами:
1 |
Value.ReplaceType( { "a", "b", "c" }, type { text } ) |
Пока все это кажется предсказуемым. Но давайте внесем коррективы, которые могут стать сюрпризом. Рассмотрим следующие примеры:
1 2 |
Value.ReplaceType( { "a", "b", "c" }, type { number } ) Value.ReplaceType( [ a = 1, b = true ], type [ a = date, b = text ] ) |
Обратите внимание, что в первой строке список текстовых элементов получает тип данных, описывающий список чисел. Во второй строке записи присваивается пользовательский тип записи, но опять же с несовпадающими типами данных. Пользовательский интерфейс Power Query не возвращает ошибку.
Рассмотрев эти примеры, вы можете подумать, что можно приписать любой выбранный тип значению. Однако это не всегда возможно. Существуют сценарии, в которых попытка присвоить тип может привести к ошибкам. В следующем разделе мы изучим ситуации, в которых приписывание типов может работать не так, как задумано, и привести к ошибке.
Ошибки в приписывании типов
Приписывание является распространенной причиной ошибок в Power Query, но ошибки возникают не во всех ситуациях. Так почему же в одних случаях мы получаем ошибки, а в других нет? Существует три ситуации, когда присвоение типа значению может привести к ошибке:
- Базовый тип приписываемого типа несовместим со значением, которому он присвоен.
- Базовый тип совместим, но само утверждение типа не соответствует значению.
- Приписываемый тип применяется к структурированному значению, содержащему значения, несовместимые с типом.
Рассмотрим каждую из этих причин подробнее.
Базовый тип несовместим со значением
Всякий раз, когда вы приписываете Type Claim значению, базовый тип должен быть совместим со значением, с которым вы работаете. Другими словами, структура вашего значения и тип должны быть одинаковыми. Если эти два параметра несовместимы, вы получите сообщение об ошибке. Общие сведения об утверждениях типов и их базовых типах приведены на рис. 5.35. Вот несколько примеров, которые выдают ошибку из-за несовместимого типа:
1 2 3 |
Value.ReplaceType( "myString", type number ) Value.ReplaceType ( #date( 2024, 12, 31 ), type logical ) Value.ReplaceType( 123, type text ) |
Первый пример возвращает ошибку:
Рис. 5.41. При присвоении типа его базовое значение должно быть совместимо со значением
Изменение типа данных на совместимый со структурой значения устранит эти ошибки.
Утверждение типа не соответствует значению
Вы также можете столкнуться с ошибками, когда приписываете тип, в котором базовый тип соответствует значению, а утверждение типа – нет. Например, у вас есть десятичное число. Power Query позволяет присвоить этому значению Int64.Type:
Рис. 5.42. Редактор Power Query не ругнулся на несовместимость утверждения типа и значения
Кажется, что базовое значение и утверждение типа совместимым, так как Power Query не выдает ошибку. Важно отметить, что Power Query во время приписывания проверяет лишь соответствие базового типа значения приписываемому типу (см. рис. 5.41). Сложности возникают при загрузке запроса в приложение, которое проверяет сведения об утверждении типа и пытается обработать значения как целые числа. Power BI вернет ошибку, а Excel – пустую таблицу:
Рис. 5.43. Приписывание утверждения типа, несовместимого со значением, возвращает ошибку приложения: слева Power BI, справа – Excel
Если в Power BI нажать на кнопку Просмотреть ошибки, появляется окно с сообщением: Не удалось получить ошибки для ваших запросов. Обновите запросы и повторите попытку.
Этот сценарий может сбивать с толку, так как Power BI выдает ошибку, которая не отображается в редакторе Power Query. Если вы столкнулись с ошибкой в Power BI, а Power Query не показывает ошибку, внимательно изучите атрибутирование столбцов. Весьма вероятно, что вы приписали неверный тип.
Приписывание несовместимых типов структурированным значениям
Ошибки в Power Query также могут возникать из-за неправильного назначения типов элементам в структурированных значениях. Эти ошибки проявляются только после загрузки данных в приложение.
Утверждения типов можно присвоить структурированным значениям: спискам, записям, таблицам и функциям. Power Query оценивает, соответствует ли значение базовому типу приписываемое утверждение типа. Если определенные значения не соответствуют базовому типу утверждения типа, возвращается ошибка:
1 2 |
Value.ReplaceType( [A = 1, B = 2 ], Int64.Type ) Value.ReplaceType( {1, 2, 3}, type text ) |
В обоих выражениях базовые типы несовместимы с базовыми значениями. Запись не является целым числом, а cписок – текстовым значением. Настройка типов сделает оба выражения допустимыми:
1 2 |
Value.ReplaceType( [A = 1, B = 2 ], type [ A = number, B = number ] ) Value.ReplaceType( {1, 2, 3} , type { number } ) |
В данном случае мы предоставили правильные типы записи и списку.
Однако важно понимать, что при присвоении типа структурированному значению, сами значения не проверяются на совместимость. Это означает, что вы можете приписать пользовательский тип списка значению списка, и редактор Power Query на этом завершит проверку. При этом пользовательские типы внутри списка не будут соответствовать значениям списка. Например, следующее выражение не выдаст ошибку в редакторе Power Query:
1 |
Value.ReplaceType( { 1, 2, 3} , type { date } ) |
Несмотря на то, что список содержит только числовые значения, вы приписали элементам списка тип date. В редакторе Power Query вы не получите ошибку. При загрузите запрос в Power BI, приложение получает сведения о типе, и использует их для приема и сжатия данных. Если сведения о типе конфликтуют с фактическими значениями, Power BI возвращает ошибку.
У меня запрос в Power BI Desktop не вернул ошибку, отформатировав числа как текст:
Рис. 5.43а. Запрос в Power Query и Power BI Desktop
То же самое происходит с записями, таблицами и функциями. В утверждении типа можно указать, что ожидается значение определенного типа, но Power Query не проверяет, так ли это:
Рис. 5.44. Table.AddColumn позволяет присвоить тип, несовместимый со значениями столбца
Приписывание типа в этом выражении удобно для исключения дополнительных шагов. Просто помните, что присвоение типа столбца структурированному значению (например, таблице) пропускает проверку соответствия базовых значений. Движок проверяет, соответствует ли базовый тип значению. При отправке этого запроса в Power BI вы столкнетесь со следующей ошибкой:
Рис. 5.45. При загрузке таблицы с несовместимым типом в Power BI появляется ошибка
При нажатии кнопки Просмотреть ошибки редактор Power Query откроется на запросе показывающем ошибки. Только после исправления ошибочно присвоенного типа столбца можно будет успешно обновить запрос.
Вот другой пример, в котором возникает похожая проблема:
Рис. 5.46. Набор данных, который мы будем использовать для суммирования значений
Если вы хотите суммировать таблицу по неделям года, выберите столбец Week of Year, пройдите Главная –> Группировать по, и в открывшемся окне укажите:
Рис. 5.46а. Настройка группировки
Нажмите OK. Получите:
Рис. 5.47. Результат группировки данных
Операция группировки жестко кодирует пользовательский тип таблицы внутри выражения. Так как типы соответствуют существующим значениям, проблем не возникает.
Создайте дубль запроса Ascribing Incompatible Types when Grouping, оставьте три первых шага: Source, #"Changed Type" и #"Inserted Week of Year". Как и ранее добавьте столбец Начало недели (см. рис. 5.46), но теперь вручную измените тип столбца Начало недели с date на text:
Было:
1 2 |
= Table.AddColumn(#"Inserted Week of Year", "Начало недели", each Date.StartOfWeek([Date]), type date) |
Стало:
1 2 |
= Table.AddColumn(#"Inserted Week of Year", "Начало недели", each Date.StartOfWeek([Date]), type text) |
Выполните операцию группировки, как на рис. 5.46а. Вот тут-то и начинаются неприятности. Операция группировки ссылается на столбец Начало недели, и жестко закодировала тип данных для него – text. Далее при развертывании столбцу присвоится тип (text), который больше не соответствует его значениям (date). Загрузка данных в Power BI приведет к ошибке.
Решить эту проблему можно несколькими путями. Прежде всего, вы можете настроить функцию Table.Group так, чтобы она отражала правильный тип данных (date, как на рис. 5.47). Это ручная задача, при которой есть риск забыть изменить тип таблицы. Кроме того, можно динамически получить тип таблицы предыдущего шага с помощью функции Value.Type. Следующее выражение извлекает тип таблицы с предыдущего шага под названием Inserted Start of Week:
1 2 3 4 5 6 |
Код 5.24 Table.Group( #"Inserted Start of Week", {"Week of Year"}, {{"Details", each _, Value.Type(#"Inserted Start of Week")}} ) |
Любые будущие изменения, которые возникают раньше в запросе, автоматически учитываются, гарантируя, что в вашей таблице всегда будут правильные связанные с ней типы данных.
Так как мы вручную изменили тип столбца Начало недели на неправильный, функция Value.Type использует именно этот тип. Не понятно, как авторы с помощью этого хотели решить проблему. Хотя сам по себе метод интересен.
Итак, хотя присвоение типов помогает классифицировать значения, оно должно выполняться с осторожностью, чтобы предотвратить ошибки.
Теперь, когда вы знаете, как присваивать значения, давайте посмотрим, как проверить, равны ли типы или совместимы ли они.
Эквивалентность, соответствие и утверждение типов
Знание того, является ли значение равным или совместимым с другим типом, полезно для ряда операций. Например, можно настроить проверку с помощью условных операторов.
Равенство типов
Допустим, вы хотите проверить, одинаковы ли два типа данных. Рассмотрим следующее сравнение:
1 |
type text = type text // вернет true |
Тут все очевидно. Однако сравнение более сложных типов может дать другой результат:
1 |
type [ a = text ] = type [ a = text ] // вернет false |
Сравниваемое содержимое кажется идентичным. Почему же возвращается false? Причина в том, что согласованность или предсказуемость результатов сравнения типов в Power Query не гарантируется. Это связано с тем, что эквивалентность типов не определена в самом языке M. Различные реализации M могут применять собственные правила при сравнении типов. Такая вариативность означает, что сравнение одних и тех же типов может давать разные результаты в зависимости от среды, например в Excel или Power BI. Несмотря на то, что текущие версии программ могут согласиться с тем, что некие сравнения возвращают true, отсутствие формального определения эквивалентности типов в языке означает, что это поведение может измениться в новых версиях.
Вместо того, чтобы напрямую сравнивать значения типов, язык M предлагает множество библиотечных функций, предназначенных для обработки значений типов. Например, функция Type.Is. Она проверяет, совместим ли тип, указанный в качестве первого аргумента, с типом, указанным в качестве второго аргумента:
1 2 3 |
Type.Is(type date, type nullable date) // true Type.Is(type nullable date, type date) // false Type.Is(type text, type number) // false |
Функция Type.Is может принимать любой тип, включая пользовательские, в качестве первого аргумента. Однако второй аргумент должен быть примитивным типом, допускающим значение null. Например:
1 2 |
Type.Is(type [ A = number], type [ A = number] ) // false Type.Is(type [ A = number], type record ) // true |
В первом примере пользовательский тип в качестве второго аргумента приводит к false. Во втором примере, с синтаксисом все Ok – второй аргумент относится к nullable primitive type. А так как пользовательский тип записи [ A = number] по своей природе соответствуют примитивному типу записи (type record), сравнение возвращает true.
Итак, с помощью Type.Is можно проверить, соответствует ли пользовательский тип определенному базовому типу.
Заметим, что в языке M в настоящее время отсутствует встроенная функция, предназначенная для оценки совместимости стандартного и пользовательского типа. Вместе с тем библиотека M включает функции, которые могут идентифицировать конкретные характеристики пользовательского типа, что позволяет создавать тесты совместимости. Примеры таких функций:
1 2 3 |
Type.IsNullable( type nullable text ) // возвращает true Type.NonNullable( type nullable text ) // возвращает false Type.ListItem( type { number } ) // возвращает type number |
Всего в языке M 22 функции типов. Мы рекомендуем ознакомиться с доступными вариантами и их синтаксисом на сайте powerquery.how.
Помимо проверки того, что один тип равен другому, есть случаи, когда полезно проверить, соответствует ли значение типу. Рассмотрим, как это сделать.
Соответствие типам
Если вы хотите определить, согласуется ли значение с типом данных, можете использовать функцию Value.Is. В качестве аргументов она принимает любое значение и любой тип данных и возвращает true/false. Такая проверка помогает предотвращать ошибки и поддерживать согласованность запросов.
1 2 |
Синтаксис 5.25 Value.Is(value as any, type as type) as logical |
Например:
1 2 3 |
Value.Is(9, Number.Type) // возвращает true Value.Is("126", Number.Type) // возвращает false Value.Is(36, Value.Type(36))// возвращает true |
Вместо Value.Is проверить, соответствует ли значение типу можно с помощью оператора is:
1 |
49 is number |
Оператор is работает только с примитивными типами. Его нельзя использовать с утверждениями типов, такими как Number.Type или Int64.Type. Например:
1 2 3 |
Value.Is( "Hello", Text.Type) // вернет true "Hello" is text // вернет true "Hello" is Text.Type // вернет ошибку Expression.SyntaxError: Недопустимый идентификатор типа |
Если вы знаете, соответствует ли значение определенному типу, вы можете фильтровать данные по условию (true/false).
В других ситуациях, если вы хотите принудительно задать тип данных, вы также можете использовать утверждение типа, о котором мы поговорим далее.
Утверждение типа
Утверждение типов в Power Query осуществляется с помощью функции Value.As. Эта функция используется для явного применения конкретного типа данных к заданному значению. Она действует как контрольная точка для проверки соответствия значения требуемому типу данных, предоставляя вам дополнительную возможность обеспечения целостности данных в запросах.
Функция Value.As принимает два параметра: значение любого типа и примитивный тип данных для проверки. Функция возвращает исходное значение, если оно соответствует указанному типу, или ошибку, если значение не соответствует типу.
1 2 |
Синтаксис 5.26 Value.As(value as any, type as type) as any |
Вы можете применять функцию Value.As различными способами, например:
1 2 3 |
Value.As(36, Number.Type) // 36 Value.As("36", Text.Type) // 36 Value.As(36, Text.Type) // Expression.Error: Невозможно преобразовать значение 36 в тип Text |
Упрощенный синтаксис:
1 |
36 as number |
Полезной особенностью функции Value.As является то, что ошибки она возвращает как значения. Это позволяет разрабатывать сложные стратегии обработки ошибок, что необходимо, когда значения не соответствуют ожидаемым типам. Используя эту функцию, вы можете создать пользовательскую логику для управления ошибками. Например, ведение журнала ошибок или предоставление резервных значений, гарантируя, что обработка данных остается непрерывной. Подробное о технике обработки ошибок мы поговорим в главы 12.
Саммари
В завершение этой главы давайте подведем итоги того, что мы узнали о типах данных в языке M. Мы начали с введения в основную концепцию типов данных, что важно для эффективного понимания и использования языка M.
Мы рассмотрели примитивные и пользовательские типы данных. Мы узнали, как разные функции обрабатывают эти типы данных, отмечая отличия в работе функций, которые допускают nullable типы, или требуют более строгого соответствия типу.
Мы углубились в подводные камни, связанные с типами данных, увидели, как типы данных могут приводить к различным ошибкам. В то время как некоторые ошибки, например преобразования, видны сразу, другие более тонкие. Особенно сложными для выявления и исправления могут быть ошибки, возникающие из-за присвоения несовместимых типов.
Обладая базовыми знаниями о типах данных, вы сможете более эффективно интегрировать типы в запросы. Хотя эта глава предоставила конкретные примеры, это лишь поверхностное знакомство с тем, что возможно делать с типами данных в M. В следующих главах мы расширим эти концепции, предлагая дополнительные примеры того, как их использовать при работе со структурированными значениями, при создании пользовательских функций и как они полезны при написании эффективного кода с использованием List.Generate.