簡介
概觀
Microsoft Power Query 包含多項功能,可提供強大的「資料取得」體驗。 而 Power Query 的核心功能為篩選與合併,也就是能從支援的資料來源,將一或多個龐大集合中的資料「混搭」。 任何這類的資料混搭,均使用 Power Query 公式語言加以表示 (一般稱為 "M")。 Power Query 會將 M 檔內嵌在廣泛的 Microsoft 產品中,包括 Excel、Power BI、Analysis Services 和 Dataverse,以啟用可重複的資料混搭。
本文件提供有關 M 的規格。在稍加簡介對該語言一開始的直覺感與熟悉度基礎之後,此文件會以幾個漸進式步驟,精確地涵蓋此語言各部分:
「語彙結構」可定義一組對詞彙而這有效的文字。
值、運算式、環境與變數、識別碼以及評估模型,皆可構成該語言的「基本概念」。
值的詳細規格 (不論基本或結構化),可定義該語言的目標領域。
具備「類型」的值,本身就屬於一種特殊種類的值,其不僅描述值基本種類的特性,同時也包含結構化值圖形所特有的其他中繼資料。
在 M 中,一組「運算子」可定義運算式所構成的種類。
「函式」另一種特殊值,其為 M 提供豐富的標準程式庫基礎,並允許加入新的抽象概念。
若在評估運算式時套用運算子或函式,可能會發生「錯誤」。 當錯誤並非值時,有一些方法可以「處理錯誤」,將錯誤對應回值。
透過「Let 運算式」,可利用較少的步驟,採取輔助定義來用於建置複雜的運算式。
「If 運算式」支援條件式評估。
「區段」提供簡單的模組性機制。 (Power Query 尚未運用區段。)
最後,「合併文法」會將此文件中所有其他區段的文法片段,收集成單一個完整的定義。
給電腦語言理論家的話:本文件中所指定的公式語言,大部分都較單純且高階,同時為動態類型且部分惰性的函式語言。
運算式與值
M 的建構中心為「運算式」。 運算式可進行評估 (計算),而產生單一的「值」。
雖然許多值都可以直接在字面上撰寫成運算式,但值不是運算式。 例如,運算式 1
會評估為值 1,運算式 1+1
則會評估為值 2。 這種差異很輕微,但很重要。 運算式是評估的配方;值則是評估的結果。
下列範例將說明在 M 中可用的不同種類的值。照慣例而言,若使用常值結構撰寫值,出現在運算式中時,就會評估為該值。 (請注意,//
表示從此處開始到該行句尾皆為註解。)
基本值是只有一個部分的值,例如數字、邏輯、文字或 null。 null 值可以用來表示沒有任何資料。
123 // A number true // A logical "abc" // A text null // null value
「清單」值是排序過的一系列值。 M 支援無限的清單,但如果寫為常值,則清單要有固定的長度。 大括號字元
{
與}
表示清單的開頭與結尾。{123, true, "A"} // list containing a number, a logical, and // a text {1, 2, 3} // list of three numbers
「記錄」是一組「欄位」。 欄位是成對的名稱與值,其中名稱是欄位記錄中不重複的文字值。 而記錄值的常數語法,允許在撰寫名稱時不用使用引號,此結構也稱為「識別碼」。 下列顯示包含三個名為 "
A
"、"B
" 和 "C
" 欄位的記錄,其中包含值1
、2
與3
。[ A = 1, B = 2, C = 3 ]
「資料表」是一組整理成資料行 (依名稱加以識別) 與資料列的值。 建立資料表沒有任何常值語法,但有一些可用來從清單或記錄建立資料表的標準函式。
例如:
#table( {"A", "B"}, { {1, 2}, {3, 4} } )
如此便會建立如下排列的資料表:
「函式」是一個值,其會在使用引數叫用函式時,產生新的值。 函式的寫法是在括弧中先列出函式的「參數」,後面接著向右箭號的符號
=>
,最後是定義函式的運算式。 該運算式通常會參考參數 (依據名稱)。(x, y) => (x + y) / 2`
評估
M 語言的評估模型是以試算表中常見的評估模型為基礎,而計算的順序則取決於儲存格中各個公式之間的相依性。
若曾在 Excel 等試算表中撰寫過公式,您可能會知道在計算時,左側公式會產生右側的值:
在 M 中,部分運算式可以透過名稱來參考運算式的其他部分,且評估程序會自動判斷所參考運算式的計算順序。
您可以使用記錄來產生相當於上述試算表範例的運算式。 初始化欄位的值時,您可以透過使用欄位名稱的方法,參考該記錄中的其他欄位,如下所示:
[
A1 = A2 * 2,
A2 = A3 + 1,
A3 = 1
]
上列運算式與下列相等 (兩者都評估為等值):
[
A1 = 4,
A2 = 2,
A3 = 1
]
記錄可以包含在其他記錄中,也可以「巢狀」結構包含在其他記錄中。 您可以使用「查閱運算子」 ([]
) 來透過名稱存取記錄欄位。 例如,下列記錄有包含一筆記錄而名為 Sales
的欄位,以及會存取記錄 Total
的欄位 FirstHalf
與 SecondHalf
且名為 Sales
的欄位:
[
Sales = [ FirstHalf = 1000, SecondHalf = 1100 ],
Total = Sales[FirstHalf] + Sales[SecondHalf]
]
上述運算式在進行評估時,等同於下列運算式:
[
Sales = [ FirstHalf = 1000, SecondHalf = 1100 ],
Total = 2100
]
記錄也可以包含在清單中。 您可以使用「位置索引運算子」({}
),依據清單中項目的數字索引,存取清單中的項目。 清單內值可使用以零為基礎的索引,從清單的開頭參考。 例如,會使用索引 0
與 1
,參考以下清單中的第一個與第二個項目:
[
Sales =
{
[
Year = 2007,
FirstHalf = 1000,
SecondHalf = 1100,
Total = FirstHalf + SecondHalf // 2100
],
[
Year = 2008,
FirstHalf = 1200,
SecondHalf = 1300,
Total = FirstHalf + SecondHalf // 2500
]
},
TotalSales = Sales{0}[Total] + Sales{1}[Total] // 4600
]
列出並記錄成員運算式 (以及 let 運算式) 會使用「惰性評估」進行評估,這表示只有在需要時才會評估運算式。 所有其他的運算式都使用「即刻評估」進行評估,這表示在評估程序期間遇到這些運算式時,就會立即進行評估。 使用這方法的好處是記住在評估清單或記錄運算式時,會傳回清單或記錄的值,而該值會記住在執行要求 (依據查閱或索引運算子) 時,該清單項目或記錄欄位要如何計算。
函式
在 M 中,「函式」是從一組輸入值到單一輸出值的對應。 函式的寫法先命名所需的輸入值集合 (函式的參數),然後提供運算式並遵循向右箭頭的符號 (=>
),使用這些輸入值 (函式的主體) 來計算函式結果。 例如:
(x) => x + 1 // function that adds one to a value
(x, y) => x + y // function that adds two values
函式是一個值,就像是數字或是文字值。 下列範例顯示值為一個 Add 欄位的函式,此欄位會從其他一些欄位「叫用」或執行。 叫用函式時,會指定一組值,這些值會在函式主體運算式內以邏輯方式替換成必要的的輸入值組。
[
Add = (x, y) => x + y,
OnePlusOne = Add(1, 1), // 2
OnePlusTwo = Add(1, 2) // 3
]
程式庫
M 包含一組常用的定義,可從稱為「標準程式庫」 (或簡稱為「程式庫」) 的運算式中使用。 這些定義由一組已命名的值所組成。 而程式庫提供之值的名稱可用於運算式中,且不須由運算式明確地定義。 例如:
Number.E // Euler's number e (2.7182...)
Text.PositionOf("Hello", "ll") // 2
操作員
M 包含一組可用於運算式中的運算子。
運算子會套用在運算元上,以形成符號運算式。 例如,在運算式 1 + 2
中,數字 1
與 2
為運算元,而運算子則為加法運算子 (+
)。
依據各種運算元值的不同,運算子的意義也可能有所不同。 例如,加號運算子可以搭配其他種類的值使用,而不只是數字:
1 + 2 // numeric addition: 3
#time(12,23,0) + #duration(0,0,2,0)
// time arithmetic: #time(12,25,0)
另一種運算子會依運算元而有不同意義的範例,是組合運算子 (&
):
"A" & "BC" // text concatenation: "ABC"
{1} & {2, 3} // list concatenation: {1, 2, 3}
[ a = 1 ] & [ b = 2 ] // record merge: [ a = 1, b = 2 ]
請注意,某些運算子並不支援所有的值組合。 例如:
1 + "2" // error: adding number and text isn't supported
中繼資料
「中繼資料」是與某值相關聯的值相關資訊。 以記錄值表示的中繼資料,稱為「中繼資料記錄」。 中繼資料記錄的欄位,可用於儲存值的中繼資料。
每個值都有中繼資料記錄。 若未指定中繼資料記錄的值,則中繼資料記錄為空白 (沒有欄位)。
中繼資料記錄提供了低調的方法,將其他資訊與任一類型的值建立關聯。 將中繼資料記錄與值建立關聯,並不會變更該值或其行為。
中繼資料記錄值 y
使用語法 x
與現有值 x meta y
相關聯。 例如,下列程式碼會將中繼資料記錄 (含 Rating
和 Tags
欄位) 與文字值 "Mozart"
建立關聯:
"Mozart" meta [ Rating = 5, Tags = {"Classical"} ]
對於已經有非空白中繼資料記錄的值,套用中繼的結果就是會計算現有中繼資料記錄與新中繼資料記錄的記錄合併。 例如,下列兩個運算式彼此相等,也等同於前一個運算式:
("Mozart" meta [ Rating = 5 ]) meta [ Tags = {"Classical"} ]
"Mozart" meta ([ Rating = 5 ] & [ Tags = {"Classical"} ])
您可以使用 Value.Metadata 函式來存取指定值的元數據記錄。 在下列範例中,ComposerRating
欄位中的運算式會存取 Composer
欄位內值的中繼資料記錄,然後存取中繼資料記錄的 Rating
欄位。
[
Composer = "Mozart" meta [ Rating = 5, Tags = {"Classical"} ],
ComposerRating = Value.Metadata(Composer)[Rating] // 5
]
Let 運算式
目前為止所顯示的許多範例,都在運算式的結果中,包含了該運算式的所有常值。
let
運算式可以先計算一組值,再指派名稱,然後用於 in
之後的運算式中。 例如,在我們的銷售資料範例中,您可以執行下列動作:
let
Sales2007 =
[
Year = 2007,
FirstHalf = 1000,
SecondHalf = 1100,
Total = FirstHalf + SecondHalf // 2100
],
Sales2008 =
[
Year = 2008,
FirstHalf = 1200,
SecondHalf = 1300,
Total = FirstHalf + SecondHalf // 2500
]
in Sales2007[Total] + Sales2008[Total] // 4600
上述運算式的結果,是從與名稱為 4600
和 Sales2007
的值所計算得來的數值 (Sales2008
)。
If 運算式
if
運算式會根據邏輯條件,在兩個運算式之間進行選取。 例如:
if 2 > 1 then
2 + 2
else
1 + 1
若邏輯運算式 (2 + 2
) 為 true,會選取第一個運算式 (2 > 1
);而若為 false,則會選取第二個運算式 (1 + 1
)。 選取的運算式 (在此例中為 2 + 2
) 會進行評估,並會得出 if
運算式的結果 (4
)。
錯誤
「錯誤」是指出評估運算式的程序無法產生值的指示。
錯誤是由遇到錯誤條件的運算子與函式所引發,或是因為使用錯誤的運算式而引發。 錯誤會使用 try
運算式加以處理。 引發錯誤時,會指定一個值,其用來指出發生錯誤的原因。
let Sales =
[
Revenue = 2000,
Units = 1000,
UnitPrice = if Units = 0 then error "No Units"
else Revenue / Units
],
UnitPrice = try Number.ToText(Sales[UnitPrice])
in "Unit Price: " &
(if UnitPrice[HasError] then UnitPrice[Error][Message]
else UnitPrice[Value])
以上範例會存取 Sales[UnitPrice]
欄位,且格式化值會產生結果:
"Unit Price: 2"
若 Units
欄位為零,則 UnitPrice
欄位會引發錯誤,並會由 try
進行處理。 結果值將可能會是:
"No Units"
try
運算式會將適當的值與錯誤轉換為記錄值,以指出 try
運算式是否已處理好錯誤,以及在處理錯誤時,運算式所擷取的適當值或錯誤記錄。 例如,請考慮下列引發錯誤,接著便立即進行處理的運算式:
try error "negative unit count"
此運算式會評估為下列巢狀結構的記錄值,解釋先前單價範例中的 [HasError]
、[Error]
及 [Message]
欄位查閱。
[
HasError = true,
Error =
[
Reason = "Expression.Error",
Message = "negative unit count",
Detail = null
]
]
常見的案例是使用預設值取代錯誤。 使用 try
運算式時,可以搭配選擇性的 otherwise
子句,以更精簡的形式達到相同目的:
try error "negative unit count" otherwise 42
// 42