Connect(); 2016

第 31 卷,第 12 期

本文章是由機器翻譯。

Connect(); 智慧型應用程式 - U-SQL 巨量資料應用程式的擴充性

Michael Rys;2016

傳統的著重於解決大 V 的巨量資料 — 磁碟區、 速度和各種不同 — 期間巨量資料處理具有主要集中提供可擴充的平台,可處理的資料量、 新增近乎即時的處理能力以及能夠處理各種不同的輸入的資料格式,透過自訂的二進位格式的 JSON csv 供應項目。一個時,通常是有點後期的各種不同是自訂資料處理相關聯的各種 — 不只是根據格式,但是也更容易擴充您自訂演算法的分析,同時保留宣告性質的查詢語言經驗的能力。

有些新型的巨量資料處理和查詢語言已經開始解決此問題。特別是,U SQL 是全新的設計 SQL 語言的宣告式的強大功能結合使用現有的程式碼程式庫和開發新的自訂演算法的彈性。

在前一篇文章中 (bit.ly/1OtXM2K),我介紹 U SQL,並示範如何使用 Microsoft.NET Framework 類型系統,以及 C#-U SQL 中的根據的運算式語言可順暢地擴充您的分析,以自訂程式碼運算式。我說明了如何使用 C# 組件來定義使用者定義函數 (Udf) 並將其用於 U SQL 查詢指令碼。

U SQL 不僅可讓您新增您自己自訂的 C# 函式,但它也提供一種架構,您可以在其中加入您的使用者定義的運算子 (Udo),例如您自己的擷取器、 outputters,以及資料列集運算子,例如處理器、 appliers、 縮減及自訂 combiners。此架構包含兩個部分︰

  1. .NET 介面,可讓您建置的方式,您可以向外延展執行留給 U SQL 專注於程式碼,這些運算子的合約。請注意,實際的商務邏輯程式碼不需要實作在.NET 中,我會在稍後說明。
  2. 擷取及減少叫用自訂運算子,並加以執行大規模的資料,例如 U SQL 運算式。

在本文中,我建置在前一篇文章,說明如何使用 U SQL 擴充性機制來處理各種不同的資料範圍是從 JSON 到影像資料。我也將顯示如何新增自己的運算子。

管理 U SQL 中的自訂程式碼

我開始的一些範例之前,讓我們深入了解 U SQL 如何使用自訂程式碼。

如前所述,U SQL 接著 C# 進行其純量運算式語言,例如 U SQL 述詞的位置和 select 子句中的運算式中使用。您的自訂程式碼 U SQL 編譯器出來,程式碼必須封裝到 U SQL 指令碼必須參考的.NET 組件中。若要能夠參考的組件,它必須先前已註冊中使用 CREATE ASSEMBLY 陳述式的 U SQL 中繼資料服務。

註冊和參考 U SQL 組件我建議使用 Azure 資料湖 Tools for Visual Studio (aka.ms/adltoolsvs),輕鬆地建置並註冊可搭配 U SQL 組件。如果您在 「 類別庫 (適用於 U SQL 應用程式) 」 專案中撰寫自訂程式碼 (請參閱**[圖 1**),您可以再撰寫程式碼和建置專案並直接註冊產生的組件 DLL 檔案按一下滑鼠右鍵 (請參閱**[圖 2**)。

類別庫 (適用於 U SQL 應用程式) 專案
[圖 1 類別 (如 U SQL 應用程式) 的程式庫專案

註冊 U SQL 組件
[圖 2 註冊 U SQL 組件

您只需要在 U SQL 指令碼是使 U SQL 指令碼中使用的公用類別和方法的參考組件陳述式中所示**[圖 3**。

[圖 3 從自訂組件參考的使用者定義函式

REFERENCE ASSEMBLY master.TweetAnalysis;
USING tweet_fns = TweetAnalysis.Udfs;
@t =
  EXTRACT date string,
          time string,
          author string,
          tweet string
  FROM "/Samples/Data/Tweets/Tweets.csv"
  USING Extractors.Csv();
// Get the mentions from the tweet string
@m =
  SELECT origin
       , tweet_fns.get_mentions(tweet) AS mentions
       , author AS mentioned_by
FROM @t;
...

使用現有的程式碼搭配 U SQL 組件通常您會想要使用現有的程式碼程式庫或甚至非.NET 程式碼。如果您想要使用非.NET 程式碼 — 比方說,原生程式庫或甚至像是 Python 或 JavaScript 完全不同的語言執行階段 — 您必須在元件之間的資料封送處理和實作 UDO 介面合約包裝與 C# 的互通性圖層,會呼叫 U sql,並接著呼叫非.NET 程式碼的非.NET 程式碼。在此情況下,非.NET 程式碼成品,例如原生的.dll 檔或不同的執行階段檔案需要加入其他檔案的檔案。這可以完成其他檔案選項中的組件註冊。這些檔案會自動部署的每個節點的.NET 組件指令碼中參考,並進行時使用.NET 組件的工作目錄本機到該節點。

若要使用現有的.NET 程式庫,您需要為受管理的相依性的現有程式碼程式庫註冊您自己的組件或 — 如果您重複使用程式庫可讓您直接在 U SQL — 註冊直接在 U SQL 資料庫中。在任一情況下,指令碼必須參考指令碼所需的所有.NET 組件。

我將示範這些註冊選項的一些範例文章的其餘部分中,我討論了某些自訂程式碼案例,因此必須使用的擴充性模型。這些案例包括︰ 合併自訂歸納器具有重疊範圍、 處理 JSON 文件、 處理影像資料和處理空間資料。我會依序討論每個。

合併自訂歸納器具有重疊範圍

讓我們假設您有追蹤使用者與您的服務互動時的記錄檔。此外,我們假設使用者可以與您的服務有多個互動 (例如,藉由執行來從多個裝置或瀏覽器視窗的 Bing 搜尋)。您準備供稍後分析的記錄檔的 U SQL 工作的一部分,您會想要合併範圍重疊。

例如,如果輸入的記錄檔看起來像是**[圖 4**,那麼您想要合併到每個使用者的重疊範圍**[圖 5**。

[圖 4 的記錄檔以重疊的時間範圍

開始時間  結束時間  使用者名稱
上午 5:00  上午 6:00  ABC
上午 5:00  上午 6:00  XYZ
上午 8:00  上午 9:00  ABC
上午 8:00  上午 10:00  ABC
上午 10:00  下午 2:00  ABC
上午 7:00  上午 11:00  ABC
上午 9:00  上午 11:00  ABC
上午 11:00  上午 11:30  ABC
下午 11:40  11:59 PM  FOO
下午 11:50  0:40 AM  FOO

[圖 5 合併重疊的時間範圍後的記錄檔

開始時間  結束時間  使用者名稱
上午 5:00  上午 6:00  ABC
上午 5:00  上午 6:00  XYZ
上午 7:00  下午 2:00  ABC
下午 11:40  0:40 AM  FOO

如果您看看此問題,您會先發現您想要定義類似結合重疊的時間間隔的使用者定義彙總。不過,如果您查看輸入資料時,您會發現,因為未排序資料,您可能必須維護狀態的所有可能的間隔,並出現橋接的間隔,或您需要預購的間隔,讓您更輕鬆的時間間隔合併的每個使用者名稱,然後合併不相鄰的間隔。

已排序的彙總至向外擴充比較簡單,不過 U SQL 並不提供排序的使用者定義彙總工具 (UDAGGs)。此外,UDAGGs 通常會產生一個資料列,每個群組,而在此情況下,如果範圍是不相鄰的範圍,我可以有多個資料列,每個群組。

U SQL 幸運的是,提供可擴充 UDO 呼叫減壓器 (bit.ly/2evGsDA),可彙總一組資料列群組的金鑰組,使用自訂程式碼為基礎。

讓我們先寫入其中 ReduceSample.RangeReducer 是我們使用者定義減壓器 (減壓器 UDO) 從 RangeReducer 的組件,而記錄檔資料位於檔案 /Samples/Blogs/MRys/Ranges/ranges.txt U SQL 邏輯 (bit.ly/2eseZyw),並使用"-"做為資料行分隔符號。請看看下面這個程式碼:

REFERENCE ASSEMBLY RangeReducer;
@in = EXTRACT start DateTime, end DateTime, user string
FROM "/Samples/Blogs/MRys/Ranges/ranges.txt"
USING Extractors.Text(delimiter:'-');
@r =  REDUCE @in PRESORT start ON user
      PRODUCE start DateTime, end DateTime, user string
      READONLY user
      USING new ReduceSample.RangeReducer();
OUTPUT @r
TO "/temp/result.csv"
USING Outputters.Csv();

減少運算式會採用資料列集 @ 中做為輸入,它會根據使用者資料行的資料分割 presorts 開始資料行中的值為基礎的資料分割,並套用 RangeReducer,產生的輸出上相同的資料列集結構描述。歸納器只會調整的範圍從開始到結束,因為它實際上並未接觸使用者的資料行,以便將它標示為唯讀。這讓歸納器架構將資料傳遞自動為該資料行的權限,並回報,允許用於積極地最佳化周圍唯讀資料行,例如推播歸納器前面的唯讀資料行上的述詞 U SQL 查詢處理器。

撰寫減壓器的方式是實作 Microsoft.Analytics.Interfaces.IReducer 的執行個體。在此情況下,您不需要提供任何參數,因為您只需要覆寫該抽象方法,減少。您可以複製 U SQL 到 C# 程式庫的程式碼,並註冊組件 RangeReducer 稍早所述。[圖 6顯示 RangeReducer 的實作。(請注意正常的程式碼縮排作法有由於空間限制變更一些程式碼範例中)。

[圖 6 C# 實作的 RangeReducer

using Microsoft.Analytics.Interfaces;
using Microsoft.Analytics.Types.Sql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ReduceSample
{
  public class RangeReducer : IReducer
  {
    public override IEnumerable<IRow> Reduce(
      IRowset input, IUpdatableRow output)
    {
      // Init aggregation values
      bool first_row_processed = false;
      var begin = DateTime.MaxValue;
      var end = DateTime.MinValue;
      // Requires that the reducer is PRESORTED on begin and
      // READONLY on the reduce key.
      foreach (var row in input.Rows)
      {
        // Initialize the first interval with the first row if i is 0
       if (!first_row_processed)
        {
         first_row_processed = true; // Mark that the first row was handled
          begin = row.Get<DateTime>("start");
          end = row.Get<DateTime>("end");
          // If the end is just a time and not a date, it can be earlier
          // than the begin, indicating it is on the next day;
          // this let's you fix up the end to the next day in that case
          if (end < begin) { end = end.AddDays(1); }
        }
        else // Handle the remaining rows
        {
          var b = row.Get<DateTime>("start");
          var e = row.Get<DateTime>("end");
          // Fix up the date if end is earlier than begin
          if (e < b) { e = e.AddDays(1); }
          // If begin time is still inside the interval,
          // increase the interval if it is longer
          if (b <= end)
          {
            // If the new end time is later than the current,
            // extend the interval
            if (e > end) { end = e; }
          }
          else // Output the previous interval and start a new one
          {
            output.Set<DateTime>("start", begin);
            output.Set<DateTime>("end", end);
            yield return output.AsReadOnly();
            begin = b; end = e;
          } // if
        } // if
      } // foreach
      // Now output the last interval
      output.Set<DateTime>("start", begin);
      output.Set<DateTime>("end", end);
      yield return output.AsReadOnly();
    } // Reduce
  } // RangeReducer
} // ReduceSample

減少 U SQL 運算式將會套用 Reduce 方法,針對每個不同的資料分割索引鍵,以平行方式執行一次。輸入的參數因此只會包含指定群組的資料列,並實作可傳回多個資料列做為輸出的零。

由於 PRESORT 子句可保證資料列排序,內部邏輯可以假設排序資料,而且使用者資料行標示為唯讀,欄將會透過自動傳送,把焦點放在您想要轉換的資料行只是更普遍撰寫程式碼 UDO。

如果您現在將套用歸納器上的大量資料,而某些使用者可能會使用您的系統通常會比其他,如果您將會遇到所謂資料扭曲其中部分使用者具有大型的資料分割和其他小的資料分割。保證歸納器的合約會以查看該資料分割的所有資料,因為必須打散到該節點並讀取一次呼叫中的所有資料。由於此需求,最理想的情況扭曲這類資料可能會導致採取遠超過其他人進行處理時,某些資料分割和最糟的情況可能會導致某些縮減用盡可用的記憶體和時間的資源 (U SQL 頂點會執行約 5 小時之後逾時)。

如果減壓器語意 (semantics) 是關聯式和交換式,而且它的輸出結構描述相同其輸入的結構描述,然後歸納器可標示為遞迴,可讓查詢引擎將大型群組分割成較小的子群組和以遞迴方式套用歸納器上這些子群組來計算最終的結果。這個遞迴應用程式可讓歸納器更佳的平衡,並有誤差的資料平行處理。減壓器使用屬性註解標示為遞迴 SqlUserDefinedReducer(IsRecursive = true):

namespace ReduceSample
{
  [SqlUserDefinedReducer(IsRecursive = true)]
  public class RangeReducer : IReducer
  {
    public override IEnumerable<IRow> Reduce(
      IRowset input, IUpdatableRow output)
    {
      // Insert the code from Figure 6 here
    } // Reduce
  } // RangeReducer
} // ReduceSample

在我們的案例,歸納器可標示為遞迴來改善延展性和效能,假設處理將會保留每個遞迴引動過程中的資料列排序。

您可以找到在我們的 GitHub 儲存機制,在此範例的 Visual Studio 專案bit.ly/2ecLe5B

在處理 JSON 文件

其中一個以逗號分隔的文字檔之後最常見的資料格式為 JSON。不同於 CSV 檔案格式,U SQL 並未提供內建的 JSON 抽選程式。不過,U SQL 社群提供的範例組件,在bit.ly/2d9O4va ,提供支援,讓您擷取和處理 JSON 和 XML 文件。

這個解決方案使用的 Newtonsoft Json.NET 程式庫 (bit.ly/2evWJbz) 大量 JSON 提起和 System.XML 的 XML 處理。組件可以從使用 JsonExtractor JSON 文件中擷取資料 (bit.ly/2dPARsM)、 JSON 文件,將它分割為 SqlMap,以允許瀏覽和分解 JsonTuple 函式使用 JSON 文件 (bit.ly/2e8tSuX),最後將資料列集轉換成 JSON 格式的檔案與 JSONOutputter (bit.ly/2e4uv3W)。

請注意組件設計為一般的 JSON 處理器,這表示它不會對做出任何假設 JSON 文件結構,而且必須能夠靈活應變 JSON,包括 heterogeneously 的半結構化本質型別 (純量與結構化、 不同資料類型相同的元素、 遺漏項目,依此類推) 的項目。如果您知道您 JSON 文件符合特定結構描述,您也許可以建立更有效率的 JSON 抽選程式。

不同於歸納器範例中更早版本,其中您撰寫自己的組件,然後您將部署,在此情況下解決方案是立即可用。您可以從我們的 GitHub 儲存機制載入 Visual Studio 方案,建置並部署您自己或您可以在方案的 bin\Debug 目錄中找到 Dll。

如先前所述,非系統相依性會要求 Samples.Format 和 Json.NET 的組件必須註冊在 U SQL 中繼資料存放區 (您可以選取 Newtonsoft 組件為受管理的相依性註冊使用 Visual Studio 工具的格式組件時),都需要參考,如果您想要處理 JSON 文件。在 JSONBlog U SQL 資料庫中,假設您已安裝組件 JSON 名稱 [Microsoft.Analytics.Samples.Formats] 和 [NewtonSoft.Json] 下在 U SQL 目錄中 (請參閱**[圖 7**),您可以使用的組件所參考的指令碼的開頭︰

REFERENCE ASSEMBLY JSONBlog.[NewtonSoft.Json];
REFERENCE ASSEMBLY JSONBlog.[Microsoft.Analytics.Samples.Formats];

在 Visual Studio 登錄格式組件
[圖 7 註冊 Visual Studio 中的格式組件

JSON 抽選程式實作 U SQL IExtractor 介面。因為需要完全以確定它們是語式正確剖析 JSON 文件,包含單一的 JSON 文件的檔案必須處理在單一抽選程式端點。因此,您可以指示抽選程式需要 AtomicFileProcessing 屬性設定為 true,以查看完整的檔案內容 (請參閱**[圖 8**)。擷取程式可以呼叫具有選擇性參數呼叫 rowpath,可讓我們來識別每一個會對應至使用 JSONPath 運算式的資料列的 JSON 物件 (bit.ly/1EmvgKO)。

[圖 8 JSON 擷取程式

[SqlUserDefinedExtractor(AtomicFileProcessing = true)]
public class JsonExtractor : IExtractor
{
  private string rowpath;            
  public JsonExtractor(string rowpath = null)
  {
    this.rowpath = rowpath;
  }
  public override IEnumerable<IRow> Extract(
    IUnstructuredReader input, IUpdatableRow output)
  {
    // Json.NET
    using (var reader = new JsonTextReader(
      new StreamReader(input.BaseStream)))
    {
      // Parse Json
      var root = JToken.ReadFrom(reader);
      // Rows
      // All objects are represented as rows
      foreach (JObject o in SelectChildren(root, this.rowpath))
      {
        // All fields are represented as columns
        this.JObjectToRow(o, output);
        yield return output.AsReadOnly();
      }
    }
  }
}

擷取程式 」 實作會傳 U SQL 抽選程式 framework 摘要的輸入資料流到擷取程式 Json.NET JsonTextReader。然後它會使用 rowpath 來取得對應到資料列使用 SelectChildren 的樹狀子目錄。JSON 物件可以是異質性的因為程式碼會傳回一般的 JObject 而不是位置 JArray 或純量值。

請注意,這此抽選程式載入到記憶體中的 JSON 文件。如果您的文件太大,它可能會造成記憶體不足狀況。在此情況下,您必須撰寫您自己的抽選程式透過文件的資料流,而不需要完整的文件載入記憶體。

現在讓我們使用 JSON 抽選程式和 JSON tuple 函式來剖析複雜的 JSON 文件從 /Samples/Blogs/MRys/JSON/complex.json (bit.ly/2ekwOEQ) 中提供**[圖 9**。

圖 9 JSON 範例文件

[{
  "person": {
    "personid": 123456,
    "name": "Person 1",
    "addresses": {
      "address": [{
        "addressid": "2",
        "street": "Street 2",
        "postcode": "1234 AB",
        "city": "City 1"
      }, {
        "addressid": "2",
        "street": "Street 2",
        "postcode": "5678 CD",
        "city": "City 2"
      }]
    }
  }
}, {
     "person": {
     "personid": 798,
     "name": "Person 2",
     "addresses": {
       "address": [{
         "addressid": "1",
         "street": "Street 1",
         "postcode": "1234 AB",
         "city": "City 1"
     }, {
         "addressid": "4",
         "street": "Street 7",
         "postcode": "98799",
         "city": "City 3"
     }]
   }
  }
}]

格式為 person 「 物件 」 (技術上具有一個人鍵每個物件),而包含某些人屬性及處理物件的陣列。U SQL 指令碼中的圖 10擷取每人/位址組合的資料列。

[圖 10 U SQL 指令碼處理的範例 JSON 文件,從 [圖 9

DECLARE @input string = "/Samples/Blogs/MRys/JSON/complex.json";
REFERENCE ASSEMBLY JSONBlog.[Newtonsoft.Json];
REFERENCE ASSEMBLY JSONBlog.[Microsoft.Analytics.Samples.Formats];
USING Microsoft.Analytics.Samples.Formats.Json;
@json =
  EXTRACT personid int,
          name string,
          addresses string
  FROM @input
  USING new JsonExtractor("[*].person");
@person =
  SELECT personid,
         name,
         JsonFunctions.JsonTuple(
           addresses, "address")["address"] AS address_array
  FROM @json;
@addresses =
  SELECT personid,
         name,
         JsonFunctions.JsonTuple(address) AS address
  FROM @person
       CROSS APPLY
         EXPLODE (JsonFunctions.JsonTuple(address_array).Values)
           AS A(address);
@result =
  SELECT personid,
         name,
         address["addressid"]AS addressid,
         address["street"]AS street,
         address["postcode"]AS postcode,
         address["city"]AS city
  FROM @addresses;
OUTPUT @result
TO "/output/json/persons.csv"
USING Outputters.Csv();

請注意指令碼會將 JSONPath 運算式 [*].person 傳遞給抽選程式,以產生最上層的陣列中的每一個人欄位的資料列。擷取結構描述正由抽選程式進入資料行產生的物件屬性。由於位址欄位本身的巢狀的 JSON 文件,JsonTuple 函式的第一個引動過程會建立對應,其包含的位址物件,然後對應至位址與跨套用切割運算式,每一個資料列。最後,所有地址內容從地圖資料投影類型,讓您的資料列集,如所示**[圖 11**。

[圖 11 處理 JSON 文件從 [圖 9 所產生的資料列集

123456 使用者 1 2 -街 2 1234 AB 縣 (市) 1
123456 使用者 1 2 -街 2 5678 CD 縣 (市) 2
798 使用者 2 1 街道 1 1234 AB 縣 (市) 1
798 使用者 2 4 街道 7 98799 縣 (市) 3

您可以找到 Visual Studio 專案中的範例和其他 JSON 處理案例中,包括多個 JSON 文件內的檔案,在我們的 GitHub 儲存機制,在bit.ly/2dzceLv

處理影像資料

在此範例中,我要處理一些較大型的非結構化的資料︰ 映像。特別是,我想要處理 JPEG 圖片和擷取部分 JPEG EXIF 屬性,以及建立影像的縮圖。幸運的是,.NET 提供各種不同的處理能力 System.Drawing 類別中的映像。所以我必須是組建 U SQL 擴充程式函式和運算子,委派的 JPEG 處理這些類別。

有許多方式可以執行此工作。第一次嘗試可能會做為位元組陣列中載入資料列集的所有映像和中所示,然後套用個別的使用者定義函式,來擷取每個屬性,並建立縮圖,圖 12

[圖 12 所載入的資料列中的映像處理 U SQL 中的映像

REFERENCE ASSEMBLY Images;
USING Images;
@image_data =
  EXTRACT image_data byte[]  // Max size of row is 4MB!
        , name string
        , format string
  FROM @"/Samples/Data/Images/{name}.{format}"
  USING new ImageExtractor();
// Use UDFs
@image_properties =
  SELECT ImageOps.getImageProperty(image_data, ImageProperties.copyright)
         AS image_copyright,
         ImageOps.getImageProperty(image_data, ImageProperties.equipment_make)
         AS image_equipment_make,
         ImageOps.getImageProperty(image_data, ImageProperties.equipment_model)
         AS image_equipment_model,
         ImageOps.getImageProperty(image_data, ImageProperties.description)
         AS image_description
  FROM @image_data
  WHERE format IN ("JPEG", "jpeg", "jpg", "JPG");

不過,這個方法會有一些缺點︰

  • U SQL 資料列上限為 4 MB 的大小,以減少人為 4 MB 大小的影像 (減去其他資料行的大小) 的解決方案。
  • 每個函式引動過程可以加入記憶體不足的壓力,並且需要傳送的位元組陣列,透過 U SQL 處理。

因此,較好的方法是直接在自訂抽選程式屬性擷取和縮圖建立。[圖 13顯示修改過的 U SQL 指令碼。

[圖 13 所擷取的功能,其中的解壓縮處理 U SQL 中的映像

REFERENCE ASSEMBLY Images;
@image_features =
  EXTRACT copyright string,
          equipment_make string,
          equipment_model string,
          description string,
          thumbnail byte[],
          name string,
          format string
  FROM @"/Samples/Data/Images/{name}.{format}"
  USING new Images.ImageFeatureExtractor(scaleWidth:500, scaleHeight:300);
@image_features =
  SELECT *
  FROM @image_features
  WHERE format IN ("JPEG", "jpeg", "jpg", "JPG");
OUTPUT @image_features
TO @"/output/images/image_features.csv"
USING Outputters.Csv();
@scaled_image =
  SELECT thumbnail
  FROM @image_features
  WHERE name == "GT4";
OUTPUT @scaled_image
TO "/output/images/GT4_thumbnail_2.jpg"
USING new Images.ImageOutputter();

此指令碼會從檔案組模式所指定的映像擷取屬性和縮圖 (bit.ly/2ektTY6): / 範例/資料/影像 / {name}。 {格式}。然後 SELECT 陳述式會使用述詞,則不會有來自 (最佳化工具只會套用抽選程式到滿足述詞格式的資料行上的檔案) 擷取的所有非 JPEG 檔案格式資料行上只限制 JPEG 檔案解壓縮。擷取程式提供的選項來指定縮圖的維度。指令碼再輸出至 CSV 檔案的功能,並使用簡單的位元組資料流層級 outputter 建立一個規模的映像的縮圖檔。

[圖 14顯示抽選程式的實作。

[圖 14 映像功能擷取程式

using Microsoft.Analytics.Interfaces;
using Microsoft.Analytics.Types.Sql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
namespace Images
{
  public static class UpdatableRowExtensions
  {
    public static void SetColumnIfExists<T>(this IUpdatableRow source
                                           , string colName, T value)
    {
      var colIdx = source.Schema.IndexOf(colName);
      if (colIdx != -1)
      { source.Set<T>(colIdx, value); }
    }
  }
  [SqlUserDefinedExtractor(AtomicFileProcessing = true)]
  public class ImageFeatureExtractor : IExtractor
  {
    private int _scaleWidth, _scaleHeight;
    public ImageFeatureExtractor(int scaleWidth = 150, int scaleHeight = 150)
    { _scaleWidth = scaleWidth; _scaleHeight = scaleHeight; }
    public override IEnumerable<IRow> Extract(IUnstructuredReader input
                                             , IUpdatableRow output)
    {
      byte[] img = ImageOps.GetByteArrayforImage(input.BaseStream);
      using (StreamImage inImage = new StreamImage(img))
      {
        output.SetColumnIfExists("image", img);
        output.SetColumnIfExists("equipment_make",
          inImage.getStreamImageProperty(ImageProperties.equipment_make));
        output.SetColumnIfExists("equipment_model",
          inImage.getStreamImageProperty(ImageProperties.equipment_model));
        output.SetColumnIfExists("description",
          inImage.getStreamImageProperty(ImageProperties.description));
        output.SetColumnIfExists("copyright",
          inImage.getStreamImageProperty(ImageProperties.copyright));
        output.SetColumnIfExists("thumbnail",
          inImage.scaleStreamImageTo(this._scaleWidth, this._scaleHeight));
      }
      yield return output.AsReadOnly();
    }
  }
}

擷取程式一次需要看到整個檔案,而且在輸入上運作。但現在在記憶體中,不像中的指令碼建立只有一個映像的 BaseStream圖 12。擷取程式也會檢查每個要求的資料行,只會處理要求的資料行名稱使用擴充方法 SetColumnIfExists 的資料。

如需詳細資訊,請參閱 Visual Studio 專案於 GitHub 網站上bit.ly/2dngXCE

處理空間資料

在此範例中,我要示範如何使用 SQL Server 空間類型組件 Microsoft.SqlServer.Types.dll U SQL 中。特別是,我要 U SQL 指令碼中使用的空間程式庫函式,做為使用者定義函式。如同先前所討論的 JSON 抽選程式的情況,這表示您想要註冊 U SQL 中已存在的組件,而不需要撰寫自己的組件。

首先,您必須下載並安裝組件從 SQL Server 2016 功能套件 (bit.ly/2dZTw1k)。選取 64 位元版本的安裝程式 (ENU\x64\SQLSysClrTypes.msi) 以確保您有 64 位元版本的程式庫。

安裝程式會安裝至 C:\Program Files (x86) \Microsoft SQL Server\130\SDK\Assemblies 和原生組件 SqlServerSpatial130.dll 到 \Windows\System32\ Microsoft.SqlServer.Types.dll 的 managed 組件。(例如,插入名為 /upload/asm/spatial 的資料夾),接下來上, 傳至 Azure 資料湖存放區的組件。因為安裝程式已安裝到系統資料夾 c:\Windows\System32 的原生程式庫,您必須確定您複製 SqlServerSpatial130.dll 該資料夾上傳之前,或請確定您使用此工具不會執行檔案系統重新導向 (bit.ly/1TYm9YZ) 系統資料夾。比方說,如果您想要使用目前 Visual Studio ADL 檔案總管將它上傳,您必須將檔案複製到另一個目錄,否則為-撰寫本文時,就會顯示上傳 (由於 Visual Studio 是 32 位元應用程式,並在其 ADL 上傳檔案選取範圍] 視窗中的檔案系統重新導向) 的 32 位元版本當您執行 U SQL 指令碼中呼叫原生組件時,就會出現下列錯誤 (內部) 在執行階段︰ 「 使用者運算式的內部例外狀況︰ 嘗試載入格式錯誤的程式。(HRESULT: 0x8007000B)。 」

上傳兩個組件檔案之後,它們在資料庫中登錄這個指令碼名為 SQLSpatial:

DECLARE @ASSEMBLY_PATH string = "/upload/asm/spatial/";
DECLARE @SPATIAL_ASM string = @ASSEMBLY_PATH+"Microsoft.SqlServer.Types.dll";
DECLARE @SPATIAL_NATIVEDLL string = @ASSEMBLY_PATH+"SqlServerSpatial130.dll";
CREATE DATABASE IF NOT EXISTS SQLSpatial;
USE DATABASE SQLSpatial;
DROP ASSEMBLY IF EXISTS SqlSpatial;
CREATE ASSEMBLY SqlSpatial
FROM @SPATIAL_ASM
WITH ADDITIONAL_FILES =
  (
    @SPATIAL_NATIVEDLL
  );

您只能註冊一個 U SQL 組件,並包含 U SQL 組件的強式相依性的原生組件,請注意在此情況下。若要使用的空間的組件,您需要參考 U SQL 組件和其他檔案將自動進行組件。[圖 15顯示使用空間的組件的簡單的範例指令碼。

[圖 15 U SQL 中使用的空間功能

REFERENCE SYSTEM ASSEMBLY [System.Xml];
REFERENCE ASSEMBLY SQLSpatial.SqlSpatial;
USING Geometry = Microsoft.SqlServer.Types.SqlGeometry;
USING Geography = Microsoft.SqlServer.Types.SqlGeography;
USING SqlChars = System.Data.SqlTypes.SqlChars;
@spatial =
    SELECT * FROM (VALUES
                   // The following expression is not using the native DDL
                   ( Geometry.Point(1.0,1.0,0).ToString()),   
                   // The following expression is using the native DDL
                   ( Geometry.STGeomFromText(
                     new SqlChars("LINESTRING (100 100, 20 180, 180 180)"),
                     0).ToString())
                  ) AS T(geom);
OUTPUT @spatial
TO "/output/spatial.csv"
USING Outputters.Csv();

SQL 型別程式庫會相依於 System.Xml 組件,因此您必須參考它。此外,某些方法使用 System.Data.SqlTypes 類型而不內建 C# 類型。因為 System.Data 已經包含預設情況下,您可以只參考所需的 SQL 類型。中的程式碼圖 15於 GitHub 網站上可用的bit.ly/2dMSBm9

總結︰ 一些秘訣和最佳作法 Udo

本文中,同時只粗淺 U SQL 的強大的擴充功能已顯示如何 U SQL 擴充性機制可讓您擴充出處理一般的巨量資料磁碟區使用 U SQL 擴充架構時重複使用現有的網域特定程式碼。

但這種功能強大的工具也容易誤用,有一些秘訣和最佳作法建議。

雖然通常需要自訂的資料格式的自訂抽選程式,且可能 outputter,其中一個應該非常仔細考慮如果可以擷取的資料格式,以平行方式 (例如 CSV 類型格式) 或處理程序需要看到單一運算子執行個體中的所有資料。此外,進行不足,無法處理只會發生,如果要求特定的資料行的運算子泛型時,也可能可以改善效能。

在考慮 Udo,例如處理器、 縮減、 combiners 和 appliers,它具有強烈建議您先考慮單純的 U SQL 解決方案利用內建運算子。例如,稍早討論的範圍歸納器指令碼無法實際寫入稍加靈活運用的視窗化與陣序規範的函式。以下是為什麼您仍可能要考慮 Udo 的一些原因︰

  • 邏輯需要動態地存取輸入或輸出正在處理的資料列集的結構描述。比方說,事先已知資料行不是其中的資料列中建立資料的 JSON 文件。
  • SELECT 運算式中使用數個使用者定義函式的解決方案建立太多記憶體不足的壓力,而且您可以撰寫您的程式碼會比較記憶體-有效率 UDO 的處理器。
  • 您需要已排序的彙總或彙總器所產生的每個群組的多個資料列,您無法寫入與視窗化函數。

當您使用 Udo 時,請務必記住保留下列秘訣︰

  • 使用 READONLY 子句允許將推送透過 Udo 的述詞。
  • 使用必要子句,以允許透過 Udo 發送的資料行剪除。
  • 提示您使用 UDO 的查詢運算式基數,查詢最佳化工具應該選擇錯誤的計劃。

Michael Rys是 Microsoft 的首席程式經理。 他一直在進行資料處理和查詢語言自 1980 年代。他有代表 Microsoft 在 XQuery 和 SQL 的設計委員會,並且發生 SQL Server 關聯式與 XML、 地理空間及語意搜尋的其他功能。目前他負責巨量資料查詢語言,例如範圍和 U SQL 時,他不喜歡時間與他的家人提或 autocross。在 Twitter 上追蹤他︰ @MikeDoesBigData

感謝下列 Microsoft 技術專家來檢閱這份文件︰ Clemens Szyperski、 Ed Triou、 Saveen Reddy 和 Michael Kadaner
Clemens Szyperski 是 Microsoft 的主要群組工程經理。數十年,他的熱情已經特製化的語言、 工具和方法,可簡化複雜的軟體系統的建構。除非他離開 sailing 與他的家人,他目前會導致 Azure 資料湖 U SQL 和領域的團隊。在 Twitter 上追蹤他︰ @ClemensSzy

Ed Triou 是 microsoft 主要開發負責人。過去 20 年來他已經著重在資料可程式性 (ODBC、 OLEDB、 ADO.NET、 JDBC、 PHP 和 EDM),在編譯器和查詢語言 (IDL TSQl,LINQ to SQL/實體,eSQL 範圍和 U SQL) 的特製化。  他目前會導致 U SQL 編譯器和語言團隊嘗試超前一步,我們每天取決於 ADL 和 Cosmos exabyte 大規模的外部和內部企業的前面。

Saveen Reddy 是著重於設計和建置的元件的 Azure 資料湖平台-Microsoft 的首席程式經理和體驗支援所有 Microsoft 的巨量資料的雲端服務。Saveen 保存 100%完成分級金屬齒輪實線︰ 虛設的痛苦。在 Twitter 上追蹤他︰ @saveenr

Michael Kadaner 是 Microsoft 的主要軟體工程師。數十年的經驗,在電腦科學和軟體開發的許多地方,儘管他宣告,撰寫程式是精確的作品,軟體可能會無錯誤的。他,則為 true 的熱情解決複雜的演算法和工程問題,他會正確地設計的簡潔且精緻的程式碼中實作解決方案在將閒暇之間讀取和自己動手撰寫專案。


討論 MSDN Magazine 論壇的文章