M distinguishes values from types. The expression 2.1 Primitive TypesNumbers in M may represent integers, decimals, currency values, or percentages. Text is enclosed in double quotation marks. A double quotation mark inside text is represented by two quotation marks: "The customer said ""approved""" Logical values are lowercase: [Amount] > 1000 and [Status] = "Approved" Dates and related types use constructor functions: #date(2025, 4, 1) #datetime(2025, 4, 1, 9, 30, 0) #duration(0, 2, 30, 0)
2.2 Type Declarations and ConversionA type annotation can communicate the intended type of a value or column. Common conversion functions include: Number.From("125.50")
Text.From(125.5)
Date.From("2025-04-01")
Logical.From("true")For table columns, use functions such as: Table.TransformColumnTypes(
Source,
{{"OrderDate", type date}, {"Quantity", Int64.Type}, {"Revenue", type number}}
)Conversion may fail if the source contains incompatible text, invalid dates, or locale-specific formats. A robust solution validates assumptions about source data rather than silently forcing every value into a type. 3. Operators and ExpressionsOperators combine or compare values.
Parentheses should be used when they make precedence clear: ([Revenue] - [Cost]) / [Revenue] This expression calculates a margin ratio, provided that Conditional expressions use if [Revenue] = null then "Missing" else if [Revenue] >= 10000 then "High" else "Standard" Because missing values can behave differently from ordinary values, it is often preferable to test for null explicitly using 4. ListsA list is an ordered sequence enclosed in braces: {10, 20, 30, 40}List positions are zero-based. Therefore: {10, 20, 30}{0}returns Useful list functions include: List.Count({10, 20, 30})
List.Sum({10, 20, 30})
List.Max({10, 20, 30})
List.Transform({1, 2, 3}, each _ * 10)
List.Select({1, 2, 3, 4}, each _ > 2)The underscore Lists can be generated using ranges: {1..5}This produces 5. RecordsA record is a collection of named fields enclosed in square brackets: [Product="Laptop", Quantity=3, UnitPrice=850] Fields can be accessed using bracket notation: Order[Quantity] A record can be expanded or modified with functions such as: Record.Field(Order, "Quantity") Record.AddField(Order, "Revenue", Order[Quantity] * Order[UnitPrice]) Record.FieldNames(Order) Records are central to row-level operations. In a table transformation such as: Table.AddColumn(
Sales,
"Revenue",
each [Quantity] * [UnitPrice],
type number
)
6. Tables and Structured DataA table is a structured value consisting of rows and columns. Conceptually, each row is a record and the complete table is a collection of records with a defined schema. A small table can be constructed directly: #table(
{"Product", "Quantity", "Price"},
{{"A", 2, 10.5}, {"B", 3, 8.0}}
)Common table operations include: Table.SelectRows(Source, each [Quantity] > 0)
Table.SelectColumns(Source, {"Product", "Quantity"})
Table.RenameColumns(Source, {{"Qty", "Quantity"}})
Table.AddColumn(Source, "Value", each [Quantity] * [Price], type number)
Table.Sort(Source, {{"Value", Order.Descending}})
Table.Group(Source, {"Product"}, {{"Total", each List.Sum([Value]), type number}})The expression Table transformations are usually immutable: a function returns a new table rather than changing the source table. This supports a clear transformation pipeline and makes dependencies easier to audit. 6.1 Table Schema and MetadataA table’s schema includes column names and types. Type information affects sorting, filtering, aggregation, and relationships in the data model. For example, sorting text values Analysts should set types early enough to support valid transformations, but should also consider whether source data is complete and reliable before conversion. A type step is not a substitute for data-quality investigation. 7. Functions and ParametersA function maps input values to an output value. Its general form is: (parameter1, parameter2) => expression For example: AddTax = (Amount as number, Rate as number) as number => Amount * (1 + Rate) The function can be called as follows: AddTax(100, 0.2) which returns A function can be applied to a table column: Table.AddColumn(Sales, "GrossAmount", each AddTax([NetAmount], 0.2), type number) Function parameters and return types are optional but valuable. Type declarations document intent and can expose invalid inputs earlier. M includes built-in functions in namespaces such as 8. Errors and Error HandlingAn M error is a value representing a failure during evaluation. Typical causes include division by zero, invalid type conversion, missing fields, unavailable files, and out-of-range list positions. The try Number.From([AmountText]) A successful result has fields such as Table.AddColumn(
Source,
"ParsedAmount",
each try Number.From([AmountText]) otherwise null,
type number
)The Table.AddColumn(
Source,
"AmountResult",
each try Number.From([AmountText])
)The resulting record can then be expanded or inspected to distinguish valid and invalid rows.
if [Quantity] < 0 then error "Quantity cannot be negative" else [Quantity] Explicit errors are appropriate when continuing would produce misleading data. Error handling should therefore balance operational resilience with transparency. 9. Worked Example: Building a Reliable Sales TransformationAssume a source table contains let
Source = #table(
{"OrderID", "OrderDateText", "QuantityText", "UnitPriceText", "Status"},
{
{"O-100", "2025-01-05", "2", "125.50", "Approved"},
{"O-101", "2025-01-06", "bad", "80.00", "Approved"},
{"O-102", "2025-01-07", "3", "50.00", "Cancelled"}
}
),
Parsed = Table.AddColumn(
Source,
"Parsed",
each [
OrderDate = try Date.From([OrderDateText]),
Quantity = try Int64.From([QuantityText]),
UnitPrice = try Number.From([UnitPriceText])
]
),
Expanded = Table.ExpandRecordColumn(
Parsed,
"Parsed",
{"OrderDate", "Quantity", "UnitPrice"},
{"OrderDate", "Quantity", "UnitPrice"}
),
Valid = Table.SelectRows(
Expanded,
each [Quantity][HasError] = false and [UnitPrice][HasError] = false
),
Values = Table.TransformColumns(
Valid,
{
{"Quantity", each [Value], Int64.Type},
{"UnitPrice", each [Value], type number},
{"OrderDate", each [Value], type date}
}
),
Approved = Table.SelectRows(Values, each [Status] = "Approved"),
WithRevenue = Table.AddColumn(
Approved,
"Revenue",
each [Quantity] * [UnitPrice],
type number
)
in
WithRevenueConceptually, the query separates parsing, validation, filtering, and calculation. The invalid quantity in order In a production solution, the error records should generally be written to a separate quality-report query rather than discarded without explanation. 10. Applied Case: Regional Sales Data IntegrationBackgroundAn illustrative retail organisation receives monthly sales files from four regions. Each file was prepared by a different team. Dates use inconsistent formats, some quantity fields contain text such as ProblemThe organisation needs a refreshable Power BI dataset with consistent columns, transparent data-quality reporting, and a standard revenue calculation. Manual correction is slow and cannot be reliably repeated each month. AnalysisThe underlying issue is not merely missing data. It is a combination of schema variation, type inconsistency, and unclear error treatment. A transformation that simply removes invalid rows would produce totals without showing how much data was excluded. Approach and SolutionA reusable function is created to:
The main query combines the standardized outputs using OutcomeThe process becomes repeatable, refreshable, and auditable. Business users receive a consistent sales table, while data stewards receive a targeted exception report. The use of a function also reduces duplicated logic across regional queries. Learning Points
11. Best Practices, Governance, and LimitationsReadability and maintenanceUse descriptive step names, keep transformations logically grouped, and avoid excessively long expressions. Comments can explain unusual business rules: // Exclude cancelled orders from recognised revenue Use the Advanced Editor to inspect the complete query, but make small changes and refresh frequently. A change that is syntactically valid may still be logically incorrect. Privacy and source governancePower Query may combine information from multiple sources. Privacy-level settings and organisational policies can affect how sources are combined and may prevent unsafe data leakage. Credentials, file paths, and connection details should not be hard-coded into shared code when parameters or managed connections are available. Query foldingFor some relational sources, Power Query can translate transformations into source-native operations. This is called query folding. Folding can improve performance, but it depends on the connector, source system, transformation, and sequence of operations. Analysts should not assume that every M function folds. Filtering early, selecting only required columns, and checking native query diagnostics where available are sensible practices. Limits of MM is highly effective for extraction and transformation, but it is not a replacement for a governed data warehouse, a semantic model, or a programming language designed for large-scale distributed computation. Very complex logic may be better implemented upstream, especially when it must be shared across many reports or controlled centrally. Contemporary RelevanceModern data preparation increasingly involves cloud storage, APIs, dataflows, and automated quality checks. M remains important because it provides a portable transformation layer across many Power Query-enabled products. AI-assisted code generation can suggest M expressions, but generated code must be reviewed for privacy, type correctness, query-folding behaviour, error handling, and business-rule accuracy. An apparently plausible expression is not evidence that the transformation is valid. Chapter SummaryM is the expression language behind Power Query. Its central structure is Operators support arithmetic, comparison, logical tests, concatenation, and collection access. Lists provide ordered collections, records provide named fields, and tables provide structured rows and columns. Functions allow reusable and parameterized logic, while The Key TermsM: The functional expression language used by Power Query. Expression: A construct that evaluates to a value.
Identifier: A name used to refer to a value or step. Value: The result produced by evaluating an expression. Type: A description of the kind of value, such as number, text, date, or table. Null: A value representing absence or missingness. List: An ordered collection of values. Record: A collection of named fields. Table: A structured collection of rows and columns. Function: A value that accepts arguments and returns a result. Parameter: An input supplied to a function or query.
Schema: The names and types that describe a table.
Error value: A value representing a failed computation. Query folding: Translation of eligible Power Query operations to the source system. Lazy evaluation: Evaluation of an expression when its result is required. Data lineage: The ability to trace data from its source through transformations. Review QuestionsShort Answer Questions
Long Answer Questions
Analytical / Application Questions
Multiple Choice Questions with AnswersQuestion 1Which part of a A. The first step in the B. The final expression after C. The first identifier in the query D. The last comma in the query Correct Answer: B. The final expression after Question 2Which M value represents an ordered collection? A. Record B. Table C. List D. Type Correct Answer: C. List Question 3What does A. 10 B. 20 C. 30 D. An error because indexing begins at one Correct Answer: A. 10 Question 4Which notation accesses the A. {Quantity} B. (Quantity) C. [Quantity] D. <Quantity> Correct Answer: C. [Quantity] Question 5What is the main purpose of A. To sort a table B. To capture a failure and provide an alternative result C. To convert every value to text D. To force query folding Correct Answer: B. To capture a failure and provide an alternative result Question 6What does the expression A. A table schema B. A list range C. A function D. A record field Correct Answer: C. A function Question 7Which function is most directly suited to adding a calculated column to a table? A. Table.AddColumn B. List.Count C. Record.FieldNames D. Text.Combine Correct Answer: A. Table.AddColumn Question 8What is a principal danger of replacing all errors with A. The query becomes case-sensitive B. Data-quality problems may be concealed C. Lists become one-based D. Dates automatically become numbers Correct Answer: B. Data-quality problems may be concealed Question 9What does query folding mean? A. Compressing an M script into fewer lines B. Translating eligible transformations to the source system C. Converting records into lists D. Encrypting source credentials Correct Answer: B. Translating eligible transformations to the source system Question 10In the expression A. 20 B. 100.20 C. 120 D. 200 Correct Answer: C. 120
Data Sources & Power Query
|
Chapter 9 of 14 |