備註
隱私權層級目前在 Power Platform 資料流程中不可用,但產品小組正在努力啟用此功能。
如果您曾經有一段時間使用 Power Query,您可能會遇到這種情況。 當你在查詢時,突然收到一個錯誤,再多的在線搜索、查詢調整或鍵盤抨擊也無法補救。 錯誤如下:
Formula.Firewall: Query 'Query1' (step 'Source') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.
或者也許:
Formula.Firewall: Query 'Query1' (step 'Source') is accessing data sources that have privacy levels which cannot be used together. Please rebuild this data combination.
這些 Formula.Firewall 錯誤是 Power Query 的數據隱私防火牆(也稱為防火牆)的結果,有時它似乎只是為了讓世界各地的數據分析師感到沮喪。 然而,不管你信不信,防火牆有一個重要的目的。 在本文中,我們將深入研究幕後工作,以更好地了解其工作原理。 有了更深入的了解,您有望在未來更好地診斷和修復防火牆錯誤。
這是什麼?
資料隱私防火牆的目的很簡單:它的存在是為了防止 Power Query 無意中在來源之間洩漏資料。
為何需要這個? 我的意思是,您當然可以撰寫一些 M 來將 SQL 值傳遞給 OData 提要。 但這將是故意的數據洩露。 混搭作者會(或至少應該)知道他們正在這樣做。 那麼為什麼需要防止意外資料外洩呢?
答案是什麼? 折疊。
折疊?
摺疊 是一個術語,指的是將 M 中的運算式(例如篩選、重新命名、聯結等)轉換為針對原始資料來源(例如 SQL、OData 等)的運算。 Power Query 的很大一部分功能來自於這樣一個事實:Power Query 可以將用戶通過其用戶界面執行的操作轉換為複雜的 SQL 或其他後端數據源語言,而無需用戶知道上述語言。 使用者可獲得原生資料來源作業的效能優勢,以及 UI 的易用性,其中所有資料來源都可以使用一組通用命令進行轉換。
在摺疊過程中,Power Query 有時可能會判斷執行指定混搭的最有效方式是從一個來源取得資料,並將其傳遞至另一個來源。 例如,如果您要將小型 CSV 檔案聯結至大型 SQL 資料表,您可能不希望 Power Query 讀取 CSV 檔案、讀取整個 SQL 資料表,然後在本機電腦上將它們聯結在一起。 您可能想要 Power Query 將 CSV 資料內嵌至 SQL 陳述式,並要求 SQL 資料庫執行聯結。
這就是意外資料外洩的發生方式。
想像一下,如果您將包含員工社會安全號碼的 SQL 資料與外部 OData 摘要的結果結合,並且您突然發現 SQL 中的社會安全號碼正在發送到 OData 服務。 壞消息,對吧?
這是防火牆旨在防止的情況。
其運作方式為何?
防火牆的存在是為了防止來自一個來源的資料無意中傳送到另一個來源。 很簡單。
那麼它是如何完成這項任務的呢?
它會將 M 查詢劃分為稱為分割區,然後強制執行下列規則來執行此動作:
- 分割區可以存取相容的資料來源,或參照其他分割區,但不能同時存取兩者。
簡。。。但令人困惑。 什麼是分割區? 是什麼讓兩個資料來源「相容」? 為什麼防火牆應該在意分割區是否想要存取資料來源及參照另一個分割區?
讓我們分解一下,一次一點地看看前面的規則。
什麼是分割區?
在最基本的層級上,分割區只是一或多個查詢步驟的集合。 最精細的分割區 (至少在目前的實作中) 是單一步驟。 最大的分割區有時可以包含多個查詢。 (稍後會詳細介紹。
如果您不熟悉步驟,您可以在選取查詢之後,在 [ 套用的步驟] 窗格中檢視 [Power Query 編輯器] 視窗右側的步驟。 步驟會追蹤您為將資料轉換為最終形狀所做的一切。
參照其他分割區的分割區
在開啟防火牆的情況下評估查詢時,防火牆會將查詢及其所有相依性分割成分割區 (即步驟群組)。 每當一個分割區引用另一個分割區中的某些內容時,防火牆都會以呼叫名為 Value.Firewall的特殊函數來取代該引用。 換句話說,防火牆不允許分割區直接相互存取。 所有引用皆已修改為通過防火牆。 將防火牆視為看門人。 參照另一個分割區的分割區必須取得防火牆的許可權才能執行此操作,而防火牆會控制是否允許參考的資料進入分割區。
這一切可能看起來很抽象,所以讓我們看一個例子。
假設您有一個名為 Employees 的查詢,它會從 SQL 資料庫提取一些資料。 假設您也有另一個名為 EmployeesReference 的查詢,它只是參考 Employees。
shared Employees = let
Source = Sql.Database(…),
EmployeesTable = …
in
EmployeesTable;
shared EmployeesReference = let
Source = Employees
in
Source;
這些查詢最終分為兩個分割區:一個用於 Employees 查詢,另一個用於 EmployeesReference 查詢(參考 Employees 分割區)。 在開啟防火牆的情況下進行評估時,這些查詢會重寫,如下所示:
shared Employees = let
Source = Sql.Database(…),
EmployeesTable = …
in
EmployeesTable;
shared EmployeesReference = let
Source = Value.Firewall("Section1/Employees")
in
Source;
請注意,對 Employees 查詢的簡單參考會取代為 Value.Firewall呼叫 ,該呼叫會提供 Employees 查詢的完整名稱。
評估 EmployeesReference 時,防火牆會攔截對 Value.Firewall("Section1/Employees")的呼叫,現在有機會控制要求的資料是否 (以及如何) 流入 EmployeesReference 分割區。 它可以執行各種操作:拒絕請求、緩衝請求的資料(從而防止任何進一步的回推至原始資料來源發生)等等。
這就是防火牆保持對分割區之間資料流動的控制的方式。
直接存取資料來源的分割區
假設您定義一個包含一個步驟的查詢 Query1 (請注意,此單一步驟查詢對應至一個防火牆分割區) ,而且此單一步驟會存取兩個資料來源:SQL 資料庫資料表和 CSV 檔案。 防火牆如何處理這個問題,因為沒有分割區引用,所以沒有呼叫 Value.Firewall 來讓它攔截。 讓我們回顧一下前面說的規則:
- 分割區可以存取相容的資料來源,或參照其他分割區,但不能同時存取兩者。
若要允許執行單一分割區但兩個資料來源的查詢,其兩個資料來源必須是「相容」的。換句話說,數據在它們之間需要可以雙向傳遞。 這表示兩個來源的隱私權層級必須是公用,或兩者都是組織,因為這是僅有的兩種允許雙向共用的組合。 如果這兩個來源都標示為 [私人],或一個標示為 [公用],另一個標示為 [組織],或使用其他隱私權層級組合來標示,則不允許雙向共用。 在同一分割區中評估它們兩者並不安全。 這樣做意味著可能會發生不安全的資料外洩(由於折疊),而防火牆將無法阻止它。
如果您嘗試存取相同分割區中不相容的資料來源,會發生什麼情況?
Formula.Firewall: Query 'Query1' (step 'Source') is accessing data sources that have privacy levels which cannot be used together. Please rebuild this data combination.
希望您現在更好地理解本文開頭列出的錯誤消息之一。
此 相容性 需求僅適用於指定的分割區內。 如果分割區參考其他分割區,則參考分割區中的資料來源不需要彼此相容。 這是因為防火牆可以緩衝資料,從而防止針對原始資料來源進行任何進一步的折疊。 資料會載入記憶體,並被視為沒有明確來源。
為什麼不兩者都做呢?
假設您定義了一個具有一個步驟(再次對應於一個分割區)的查詢,該步驟存取另外兩個查詢(即另外兩個分割區)。 如果您想要在同一步驟中直接存取 SQL 資料庫,該怎麼辦? 為什麼分割區不能參考其他分割區並直接存取相容的資料來源?
如您之前所見,當一個分割區引用另一個分割區時,防火牆會充當流入該分割區的所有資料的網守。 為此,它必須能夠控制允許進入哪些數據。 如果分割區內有資料來源正在存取,且資料從其他分割區流入,則它會失去成為閘道管理員的能力,因為流入的資料可能會洩漏至其中一個內部存取的資料來源,而它不知道。 因此,防火牆會防止那些存取其他分割區的分割區被允許直接存取任何資料來源。
那麼,如果一個分割區嘗試引用其他分割區並直接存取資料來源,會發生什麼情況呢?
Formula.Firewall: Query 'Query1' (step 'Source') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.
現在,您希望更好地理解本文開頭列出的其他錯誤消息。
深入探討磁碟分割區
正如您可能從先前的資訊中猜到的那樣,查詢的分割方式最終非常重要。 如果您有一些參考其他查詢的步驟,以及存取資料來源的其他步驟,您現在希望認識到在某些位置繪製分割區界限會導致防火牆錯誤,而在其他地方繪製分割區界限可讓您的查詢正常運行。
那麼查詢到底是如何分割的呢?
本節對於瞭解您看到防火牆錯誤的原因,以及瞭解如何解決這些錯誤 (如果可能) ,可能是最重要的一節。
以下是分割邏輯的高階摘要。
- 初始分區
- 為每個查詢中的每個步驟建立分割區
- 靜態階段
- 此階段不取決於評估結果。 相反地,它依賴於查詢的結構方式。
- 參數修剪
- 修剪參數式分割區,也就是任何符合下列條件的分割區:
- 不參考任何其他分割區
- 不包含任何函式呼叫
- 不是自我循環的(也就是說,它不自我參照)
- 請注意,「移除」分割區會有效地將其包含在參考它的任何其他分割區中。
- 修剪參數分割區可以讓用於資料來源函式呼叫的參數參考正常運作(例如
Web.Contents(myUrl)),避免引發「分割區無法參考資料來源和其他步驟」的錯誤。
- 修剪參數式分割區,也就是任何符合下列條件的分割區:
- 分組 (靜態)
- 分割區會以由下而上的相依性順序合併。 在產生的合併分割區中,下列項目是分開的:
- 不同查詢中的分割區
- 未參考其他分割區的分割區 (因此允許存取資料來源)
- 參考其他分割區的分割區(因此無法存取資料來源)
- 分割區會以由下而上的相依性順序合併。 在產生的合併分割區中,下列項目是分開的:
- 參數修剪
- 此階段不取決於評估結果。 相反地,它依賴於查詢的結構方式。
- 動態階段
- 此階段取決於評估結果,包括各種分割區所存取資料來源的相關資訊。
- 修剪
- 修剪符合下列所有條件的分割區:
- 不存取任何資料來源
- 不參考存取資料來源的任何分割區
- 不是循環的
- 修剪符合下列所有條件的分割區:
- 分組 (動態)
- 現在已修剪不必要的分割區,請嘗試建立盡可能大的來源分割區。 此建立是透過使用上一個靜態分組階段中所述的相同規則合併分割區來完成的。
這一切意味著什麼?
讓我們透過一個範例來說明先前列出的複雜邏輯是如何運作的。
這是一個範例案例。 這是文字檔 (Contacts) 與 SQL 資料庫 (Employees) 相當簡單的合併,其中 SQL Server 是參數 (DbServer)。
三個查詢
以下是此範例中使用的三個查詢的 M 程式碼。
shared DbServer = "MySqlServer" meta [IsParameterQuery=true, Type="Text", IsParameterQueryRequired=true];
shared Contacts = let
Source = Csv.Document(File.Contents(
"C:\contacts.txt"),[Delimiter=" ", Columns=15, Encoding=1252, QuoteStyle=QuoteStyle.None]
),
#"Promoted Headers" = Table.PromoteHeaders(Source, [PromoteAllScalars=true]),
#"Changed Type" = Table.TransformColumnTypes(
#"Promoted Headers",
{
{"ContactID", Int64.Type},
{"NameStyle", type logical},
{"Title", type text},
{"FirstName", type text},
{"MiddleName", type text},
{"LastName", type text},
{"Suffix", type text},
{"EmailAddress", type text},
{"EmailPromotion", Int64.Type},
{"Phone", type text},
{"PasswordHash", type text},
{"PasswordSalt", type text},
{"AdditionalContactInfo", type text},
{"rowguid", type text},
{"ModifiedDate", type datetime}
}
)
in
#"Changed Type";
shared Employees = let
Source = Sql.Databases(DbServer),
AdventureWorks = Source{[Name="AdventureWorks"]}[Data],
HumanResources_Employee = AdventureWorks{[Schema="HumanResources",Item="Employee"]}[Data],
#"Removed Columns" = Table.RemoveColumns(
HumanResources_Employee,
{
"HumanResources.Employee(EmployeeID)",
"HumanResources.Employee(ManagerID)",
"HumanResources.EmployeeAddress",
"HumanResources.EmployeeDepartmentHistory",
"HumanResources.EmployeePayHistory",
"HumanResources.JobCandidate",
"Person.Contact",
"Purchasing.PurchaseOrderHeader",
"Sales.SalesPerson"
}
),
#"Merged Queries" = Table.NestedJoin(
#"Removed Columns",
{"ContactID"},
Contacts,
{"ContactID"},
"Contacts",
JoinKind.LeftOuter
),
#"Expanded Contacts" = Table.ExpandTableColumn(
#"Merged Queries",
"Contacts",
{"EmailAddress"},
{"EmailAddress"}
)
in
#"Expanded Contacts";
這是一個更高層次的視圖,顯示了相依性。
讓我們進行分區
讓我們更仔細地查看,並在圖片中包含步驟,然後開始探討分割邏輯。 以下是三個查詢的圖表,以綠色顯示初始防火牆分割區。 請注意,每個步驟都從自己的分割區開始。
接下來,我們修剪參數分割區。 因此,DbServer 會隱含地包含在來源分割區中。
現在我們執行靜態分組。 此分組會維護個別查詢中分割區之間的分隔 (例如,請注意,Employees 的最後兩個步驟不會與 Contacts 的步驟分組),以及參考其他分割區的分割區 (例如 Employees 的最後兩個步驟) 和未參考的分割區 (例如 Employees 的前三個步驟) 之間的分隔。
現在我們進入動態階段。 在此階段中,會評估上述靜態分割區。 不會存取任何資料來源的分割區會被修剪。 然後將分割區分組,以建立盡可能大的來源分割區。 不過,在此範例案例中,所有剩餘的分割區都會存取資料來源,而且無法完成任何進一步的分組。 因此,我們範例中的分割區在此階段不會變更。
我們假裝一下
不過,為了說明起見,讓我們看看如果 [連絡人] 查詢不是來自文字檔,而是以 M 硬式編碼 (可能透過 [ 輸入資料 ] 對話方塊) ,會發生什麼事。
在此情況下,連絡人查詢不會存取任何資料來源。 因此,它會在動態階段的初段被截短。
移除 [連絡人] 分割區後,Employees 的最後兩個步驟將不再參考任何分割區,但包含 Employees 前三個步驟的分割區除外。 因此,兩個分割區將會分組。
產生的分割區看起來像這樣。
範例:將資料從一個資料來源傳遞至另一個資料來源
好了,抽象的解釋夠多了。 讓我們看看您可能會遇到防火牆錯誤的常見場景以及解決該錯誤的步驟。
假設您想要從 Northwind OData 服務中查詢公司名稱,然後使用公司名稱來執行 Bing 搜尋。
首先,您建立 公司 查詢以擷取公司名稱。
let
Source = OData.Feed(
"https://services.odata.org/V4/Northwind/Northwind.svc/",
null,
[Implementation="2.0"]
),
Customers_table = Source{[Name="Customers",Signature="table"]}[Data],
CHOPS = Customers_table{[CustomerID="CHOPS"]}[CompanyName]
in
CHOPS
接下來,您會建立一個參考公司的搜尋查詢,並將它傳送到 Bing。
let
Source = Text.FromBinary(Web.Contents("https://www.bing.com/search?q=" & Company))
in
Source
這時,你遇到了麻煩。 評估 搜尋 會產生防火牆錯誤。
Formula.Firewall: Query 'Search' (step 'Source') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.
發生此錯誤的原因是 [搜尋 ] 的 [來源] 步驟正在參考資料來源 (bing.com) ,也參考另一個查詢/分割區 (公司)。 它違反了前面提到的規則(“一個分區可以訪問兼容的數據源,或者引用其他分區,但不能同時訪問兩者”)。
怎麼辦? 一種選擇是完全禁用防火牆(通過標有“ 忽略隱私級別並可能提高性能”的“隱私”選項)。 但是,如果您想讓防火牆保持啟用狀態怎麼辦?
若要在不停用防火牆的情況下解決錯誤,您可以將公司和搜尋合併為單一查詢,如下所示:
let
Source = OData.Feed(
"https://services.odata.org/V4/Northwind/Northwind.svc/",
null,
[Implementation="2.0"]
),
Customers_table = Source{[Name="Customers",Signature="table"]}[Data],
CHOPS = Customers_table{[CustomerID="CHOPS"]}[CompanyName],
Search = Text.FromBinary(Web.Contents("https://www.bing.com/search?q=" & CHOPS))
in
Search
現在一切都在 單一 分區內發生。 假設兩個資料來源的隱私權層級相容,防火牆現在應該會滿意,而且您不會再收到錯誤。
這就結束了
雖然關於這個主題還有很多話可以說,但這篇介紹性文章已經足夠長了。 希望它能讓您更好地了解防火牆,並幫助您在將來遇到防火牆錯誤時了解和修復它們。