Guida alla programmabilità di U-SQLU-SQL programmability guide

U-SQL è un linguaggio di query progettato per carichi di lavoro di tipo Big Data.U-SQL is a query language that's designed for big data-type of workloads. Una delle caratteristiche esclusive di U-SQL è la combinazione tra linguaggio dichiarativo di tipo SQL e funzionalità di estendibilità e programmabilità del linguaggio C#.One of the unique features of U-SQL is the combination of the SQL-like declarative language with the extensibility and programmability that's provided by C#. Questa guida è incentrata sulle funzionalità di estendibilità e programmabilità del linguaggio U-SQL supportate da C#.In this guide, we concentrate on the extensibility and programmability of the U-SQL language that's enabled by C#.

RequisitiRequirements

Scaricare e installare Strumenti Azure Data Lake per Visual Studio.Download and install Azure Data Lake Tools for Visual Studio.

Introduzione a U-SQLGet started with U-SQL

Si consideri lo script U-SQL seguente:Look at the following U-SQL script:

@a  = 
  SELECT * FROM 
    (VALUES
       ("Contoso",   1500.0, "2017-03-39"),
       ("Woodgrove", 2700.0, "2017-04-10")
    ) AS D( customer, amount );

@results =
  SELECT
    customer,
    amount,
    date
  FROM @a;    

Lo script definisce due set di righe: @a e @results.This script defines two RowSets: @a and @results. La definizione del set di righe @results deriva da @a.RowSet @results is defined from @a.

Tipi ed espressioni C# in uno script U-SQLC# types and expressions in U-SQL script

Un'espressione U-SQL è un'espressione C# combinata con operazioni logiche U-SQL quali AND, OR e NOT.A U-SQL Expression is a C# expression combined with U-SQL logical operations such AND, OR, and NOT. Le espressioni U-SQL possono essere utilizzate con l'istruzione SELECT, EXTRACT, WHERE, HAVING, GROUP BY e DECLARE.U-SQL Expressions can be used with SELECT, EXTRACT, WHERE, HAVING, GROUP BY and DECLARE. Lo script seguente, ad esempio, analizza una stringa come valore DateTime.For example, the following script parses a string as a DateTime value.

@results =
  SELECT
    customer,
    amount,
    DateTime.Parse(date) AS date
  FROM @a;    

Il frammento di codice seguente analizza una stringa come valore DateTime in un'istruzione DECLARE.The following snippet parses a string as DateTime value in a DECLARE statement.

DECLARE @d = DateTime.Parse("2016/01/01");

Usare espressioni C# per conversioni del tipo di datiUse C# expressions for data type conversions

L'esempio seguente illustra come effettuare una conversione di dati di tipo datetime usando espressioni C#.The following example demonstrates how you can do a datetime data conversion by using C# expressions. In questo particolare scenario, i dati della stringa datetime vengono convertiti in datetime standard con la notazione dell'ora 00:00:00.In this particular scenario, string datetime data is converted to standard datetime with midnight 00:00:00 time notation.

DECLARE @dt = "2016-07-06 10:23:15";

@rs1 =
  SELECT 
    Convert.ToDateTime(Convert.ToDateTime(@dt).ToString("yyyy-MM-dd")) AS dt,
    dt AS olddt
  FROM @rs0;

OUTPUT @rs1 
  TO @output_file 
  USING Outputters.Text();

Usare espressioni C# per la data odiernaUse C# expressions for today’s date

Per eseguire il pull della data odierna, è possibile usare l'espressione C# seguente: DateTime.Now.ToString("M/d/yyyy")To pull today's date, we can use the following C# expression: DateTime.Now.ToString("M/d/yyyy")

Di seguito è riportato un esempio di come usare questa espressione in uno script:Here's an example of how to use this expression in a script:

@rs1 =
  SELECT
    MAX(guid) AS start_id,
    MIN(dt) AS start_time,
    MIN(Convert.ToDateTime(Convert.ToDateTime(dt<@default_dt?@default_dt:dt).ToString("yyyy-MM-dd"))) AS start_zero_time,
    MIN(USQL_Programmability.CustomFunctions.GetFiscalPeriod(dt)) AS start_fiscalperiod,
    DateTime.Now.ToString("M/d/yyyy") AS Nowdate,
    user,
    des
  FROM @rs0
  GROUP BY user, des;

Uso di assembly .NETUsing .NET assemblies

Il modello di estendibilità di U-SQL dipende in larga misura dalla possibilità di aggiungere codice personalizzato da assembly .NET.U-SQL’s extensibility model relies heavily on the ability to add custom code from .NET assemblies.

Registrare un assembly .NETRegister a .NET assembly

Usare l'istruzione CREATE ASSEMBLY per inserire un assembly .NET in un database U-SQL.Use the CREATE ASSEMBLY statement to place a .NET assembly into a U-SQL Database. In seguito gli script U-SQL potranno usare tali assembly tramite l'istruzione REFERENCE ASSEMBLY.Afterwards, U-SQL scripts can use those assemblies by using the REFERENCE ASSEMBLY statement.

Nel codice seguente viene illustrato come registrare un assembly:The following code shows how to register an assembly:

CREATE ASSEMBLY MyDB.[MyAssembly]
   FROM "/myassembly.dll";

Nel codice seguente viene illustrato come referenziare un assembly:The following code shows how to reference an assembly:

REFERENCE ASSEMBLY MyDB.[MyAssembly];

Consultare la le istruzioni di registrazione assembly che descrivono nel dettaglio questo argomento.Consult the assembly registration instructions that covers this topic in greater detail.

Usare il controllo delle versioni degli assemblyUse assembly versioning

Attualmente, U-SQL usa .NET Framework versione 4.5.Currently, U-SQL uses the .NET Framework version 4.5. Verificare quindi che i propri assembly siano compatibili con tale versione di runtime.So ensure that your own assemblies are compatible with that version of the runtime.

Come indicato in precedenza, U-SQL esegue il codice in un formato a 64 bit (x64).As mentioned earlier, U-SQL runs code in a 64-bit (x64) format. Verificare quindi che il codice venga compilato per l'esecuzione su x64.So make sure that your code is compiled to run on x64. In caso contrario verrà visualizzato l'errore di formato non corretto riportato sopra.Otherwise you get the incorrect format error shown earlier.

Ogni DLL di assembly e file di risorse caricato (ad esempio un diverso runtime, un assembly nativo o un file di configurazione) può essere al massimo di 400 MB.Each uploaded assembly DLL and resource file, such as a different runtime, a native assembly, or a config file, can be at most 400 MB. Le dimensioni totali delle risorse distribuite, tramite DEPLOY RESOURCE o riferimenti agli assembly e ai relativi file aggiuntivi, non possono superare 3 GB.The total size of deployed resources, either via DEPLOY RESOURCE or via references to assemblies and their additional files, cannot exceed 3 GB.

Si noti infine che ogni database U-SQL può contenere solo una versione di un determinato assembly.Finally, note that each U-SQL database can only contain one version of any given assembly. Se sono necessarie entrambe le versioni 7 e 8 della libreria NewtonSoft Json.Net, si devono registrare in due database diversi.For example, if you need both version 7 and version 8 of the NewtonSoft Json.Net library, you need to register them in two different databases. Ogni script, inoltre, può fare riferimento a una sola versione di una determinata DLL di assembly.Furthermore, each script can only refer to one version of a given assembly DLL. A tale riguardo, U-SQL segue la semantica di gestione e controllo delle versioni degli assembly di C#.In this respect, U-SQL follows the C# assembly management and versioning semantics.

Usare funzioni definite dall'utente (UDF)Use user-defined functions: UDF

Le funzioni definite dall'utente (UDF) di U-SQL sono routine di programmazione che accettano parametri, eseguono un'azione, ad esempio un calcolo complesso, e restituiscono il risultato di tale azione come valore.U-SQL user-defined functions, or UDF, are programming routines that accept parameters, perform an action (such as a complex calculation), and return the result of that action as a value. Il valore restituito della funzione UDF può essere solo un valore scalare singolo.The return value of UDF can only be a single scalar. Una funzione UDF di U-SQL può essere chiamata nello script di base di U-SQL come qualsiasi altra funzione scalare di C#.U-SQL UDF can be called in U-SQL base script like any other C# scalar function.

È consigliabile inizializzare le funzioni definite dall'utente di U-SQL come public e static.We recommend that you initialize U-SQL user-defined functions as public and static.

public static string MyFunction(string param1)
{
    return "my result";
}

Per prima cosa verrà esaminato il semplice esempio della creazione di una funzione definita dall'utente.First let’s look at the simple example of creating a UDF.

Nello scenario di questo caso d'uso, è necessario determinare il periodo fiscale, ovvero il trimestre fiscale e il mese fiscale, del primo accesso dell'utente specifico.In this use-case scenario, we need to determine the fiscal period, including the fiscal quarter and fiscal month of the first sign-in for the specific user. In questo scenario, il primo mese dell'anno fiscale è giugno.The first fiscal month of the year in our scenario is June.

Per calcolare il periodo fiscale, si introduce la funzione C# seguente:To calculate fiscal period, we introduce the following C# function:

public static string GetFiscalPeriod(DateTime dt)
{
    int FiscalMonth=0;
    if (dt.Month < 7)
    {
    FiscalMonth = dt.Month + 6;
    }
    else
    {
    FiscalMonth = dt.Month - 6;
    }

    int FiscalQuarter=0;
    if (FiscalMonth >=1 && FiscalMonth<=3)
    {
    FiscalQuarter = 1;
    }
    if (FiscalMonth >= 4 && FiscalMonth <= 6)
    {
    FiscalQuarter = 2;
    }
    if (FiscalMonth >= 7 && FiscalMonth <= 9)
    {
    FiscalQuarter = 3;
    }
    if (FiscalMonth >= 10 && FiscalMonth <= 12)
    {
    FiscalQuarter = 4;
    }

    return "Q" + FiscalQuarter.ToString() + ":P" + FiscalMonth.ToString();
}

In questo modo vengono semplicemente calcolati il mese e il trimestre fiscali e viene restituito un valore stringa.It simply calculates fiscal month and quarter and returns a string value. Per giugno (primo mese del primo trimestre fiscale), si usa "Q1:P1",For June, the first month of the first fiscal quarter, we use "Q1:P1". per luglio "Q1:P2" e così via.For July, we use "Q1:P2", and so on.

Si tratta di una normale funzione C# che verrà usata nel progetto U-SQL.This is a regular C# function that we are going to use in our U-SQL project.

Di seguito è illustrato l'aspetto della sezione code-behind in questo scenario:Here is how the code-behind section looks in this scenario:

using Microsoft.Analytics.Interfaces;
using Microsoft.Analytics.Types.Sql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace USQL_Programmability
{
    public class CustomFunctions
    {
        public static string GetFiscalPeriod(DateTime dt)
        {
            int FiscalMonth=0;
            if (dt.Month < 7)
            {
                FiscalMonth = dt.Month + 6;
            }
            else
            {
                FiscalMonth = dt.Month - 6;
            }

            int FiscalQuarter=0;
            if (FiscalMonth >=1 && FiscalMonth<=3)
            {
                FiscalQuarter = 1;
            }
            if (FiscalMonth >= 4 && FiscalMonth <= 6)
            {
                FiscalQuarter = 2;
            }
            if (FiscalMonth >= 7 && FiscalMonth <= 9)
            {
                FiscalQuarter = 3;
            }
            if (FiscalMonth >= 10 && FiscalMonth <= 12)
            {
                FiscalQuarter = 4;
            }

            return "Q" + FiscalQuarter.ToString() + ":" + FiscalMonth.ToString();
        }
    }
}

Ora si procede a chiamare la funzione dallo script U-SQL di base.Now we are going to call this function from the base U-SQL script. A tale scopo, è necessario specificare un nome completo per la funzione, includendo lo spazio dei nomi, in questo caso SpazioDeiNomi.Classe.Funzione(parametro).To do this, we have to provide a fully qualified name for the function, including the namespace, which in this case is NameSpace.Class.Function(parameter).

USQL_Programmability.CustomFunctions.GetFiscalPeriod(dt)

Di seguito è riportato l'effettivo script U-SQL di base:Following is the actual U-SQL base script:

DECLARE @input_file string = @"\usql-programmability\input_file.tsv";
DECLARE @output_file string = @"\usql-programmability\output_file.tsv";

@rs0 =
    EXTRACT
            guid Guid,
        dt DateTime,
            user String,
            des String
    FROM @input_file USING Extractors.Tsv();

DECLARE @default_dt DateTime = Convert.ToDateTime("06/01/2016");

@rs1 =
    SELECT
        MAX(guid) AS start_id,
    MIN(dt) AS start_time,
        MIN(Convert.ToDateTime(Convert.ToDateTime(dt<@default_dt?@default_dt:dt).ToString("yyyy-MM-dd"))) AS start_zero_time,
        MIN(USQL_Programmability.CustomFunctions.GetFiscalPeriod(dt)) AS start_fiscalperiod,
        user,
        des
    FROM @rs0
    GROUP BY user, des;

OUTPUT @rs1 
    TO @output_file 
    USING Outputters.Text();

Di seguito è riportato il file di output dell'esecuzione dello script:Following is the output file of the script execution:

0d8b9630-d5ca-11e5-8329-251efa3a2941,2016-02-11T07:04:17.2630000-08:00,2016-06-01T00:00:00.0000000,"Q3:8","User1",""

20843640-d771-11e5-b87b-8b7265c75a44,2016-02-11T07:04:17.2630000-08:00,2016-06-01T00:00:00.0000000,"Q3:8","User2",""

301f23d2-d690-11e5-9a98-4b4f60a1836f,2016-02-11T09:01:33.9720000-08:00,2016-06-01T00:00:00.0000000,"Q3:8","User3",""

In questo esempio viene illustrato un uso semplificato della funzione UDF inline in U-SQL.This example demonstrates a simple usage of inline UDF in U-SQL.

Mantenere lo stato tra chiamate di funzioni definite dall'utenteKeep state between UDF invocations

Gli oggetti di programmabilità C# in U-SQL possono essere resi più sofisticati introducendo l'interattività tramite variabili code-behind globali.U-SQL C# programmability objects can be more sophisticated, utilizing interactivity through the code-behind global variables. Verrà esaminato lo scenario del caso d'uso aziendale descritto di seguito.Let’s look at the following business use-case scenario.

Nelle grandi organizzazioni gli utenti possono accedere a svariate applicazioni interne,In large organizations, users can switch between varieties of internal applications. ad esempio Microsoft Dynamics CRM, Power BI e così via.These can include Microsoft Dynamics CRM, PowerBI, and so on. I clienti potrebbero voler applicare un'analisi dei dati di telemetria relativamente al modo in cui gli utenti passano da un'applicazione all'altra, alle tendenze di utilizzo e così via.Customers might want to apply a telemetry analysis of how users switch between different applications, what the usage trends are, and so on. L'obiettivo per l'azienda è ottimizzare l'utilizzo delle applicazioniThe goal for the business is to optimize application usage. ed eventualmente combinare anche diverse applicazioni o specifiche routine di accesso.They also might want to combine different applications or specific sign-on routines.

Per raggiungere tale obiettivo, è necessario determinare gli ID sessione e l'intervallo di tempo rispetto all'ultima sessione eseguita.To achieve this goal, we have to determine session IDs and lag time between the last session that occurred.

È necessario trovare un accesso precedente e quindi assegnare tale accesso a tutte le sessioni generate per la stessa applicazione.We need to find a previous sign-in and then assign this sign-in to all sessions that are being generated to the same application. Il primo problema è che lo script U-SQL di base non consente di applicare calcoli sulle colonne già calcolate con la funzione LAG.The first challenge is that U-SQL base script doesn't allow us to apply calculations over already-calculated columns with LAG function. Il secondo problema è che è necessario mantenere la sessione specifica per tutte le sessioni incluse nello stesso periodo di tempo.The second challenge is that we have to keep the specific session for all sessions within the same time period.

Per risolvere questo problema, viene usata una variabile globale all'interno di una sezione code-behind: static public string globalSession;.To solve this problem, we use a global variable inside a code-behind section: static public string globalSession;.

Questa variabile globale viene applicata all'intero set di righe durante l'esecuzione dello script.This global variable is applied to the entire rowset during our script execution.

Di seguito è riportata la sezione code-behind del programma U-SQL:Here is the code-behind section of our U-SQL program:

using Microsoft.Analytics.Interfaces;
using Microsoft.Analytics.Types.Sql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace USQLApplication21
{
    public class UserSession
    {
        static public string globalSession;
        static public string StampUserSession(string eventTime, string PreviousRow, string Session)
        {

            if (!string.IsNullOrEmpty(PreviousRow))
            {
                double timeGap = Convert.ToDateTime(eventTime).Subtract(Convert.ToDateTime(PreviousRow)).TotalMinutes;
                if (timeGap <= 60) {return Session;}
                else {return Guid.NewGuid().ToString();}
            }
            else {return Guid.NewGuid().ToString();}

        }

        static public string getStampUserSession(string Session)
        {
            if (Session != globalSession && !string.IsNullOrEmpty(Session)) { globalSession = Session; }
            return globalSession;
        }

    }
}

Questo esempio illustra la variabile globale static public string globalSession; usata nella funzione getStampUserSession e reinizializzata a ogni modifica del parametro di sessione.This example shows the global variable static public string globalSession; used inside the getStampUserSession function and getting reinitialized each time the Session parameter is changed.

Lo script U-SQL di base è il seguente:The U-SQL base script is as follows:

DECLARE @in string = @"\UserSession\test1.tsv";
DECLARE @out1 string = @"\UserSession\Out1.csv";
DECLARE @out2 string = @"\UserSession\Out2.csv";
DECLARE @out3 string = @"\UserSession\Out3.csv";

@records =
    EXTRACT DataId string,
            EventDateTime string,           
            UserName string,
            UserSessionTimestamp string

    FROM @in
    USING Extractors.Tsv();

@rs1 =
    SELECT 
        EventDateTime,
        UserName,
    LAG(EventDateTime, 1) 
        OVER(PARTITION BY UserName ORDER BY EventDateTime ASC) AS prevDateTime,          
        string.IsNullOrEmpty(LAG(EventDateTime, 1) 
        OVER(PARTITION BY UserName ORDER BY EventDateTime ASC)) AS Flag,           
        USQLApplication21.UserSession.StampUserSession
           (
            EventDateTime,
            LAG(EventDateTime, 1) OVER(PARTITION BY UserName ORDER BY EventDateTime ASC),
            LAG(UserSessionTimestamp, 1) OVER(PARTITION BY UserName ORDER BY EventDateTime ASC)
           ) AS UserSessionTimestamp
    FROM @records;

@rs2 =
    SELECT 
        EventDateTime,
        UserName,
        LAG(EventDateTime, 1) 
        OVER(PARTITION BY UserName ORDER BY EventDateTime ASC) AS prevDateTime,
        string.IsNullOrEmpty( LAG(EventDateTime, 1) OVER(PARTITION BY UserName ORDER BY EventDateTime ASC)) AS Flag,
        USQLApplication21.UserSession.getStampUserSession(UserSessionTimestamp) AS UserSessionTimestamp
    FROM @rs1
    WHERE UserName != "UserName";

OUTPUT @rs2
    TO @out2
    ORDER BY UserName, EventDateTime ASC
    USING Outputters.Csv();

La funzione USQLApplication21.UserSession.getStampUserSession(UserSessionTimestamp) viene chiamata durante il secondo calcolo del set di righe della memoria.Function USQLApplication21.UserSession.getStampUserSession(UserSessionTimestamp) is called here during the second memory rowset calculation. Passa la colonna UserSessionTimestamp e restituisce il valore finché non viene modificato UserSessionTimestamp.It passes the UserSessionTimestamp column and returns the value until UserSessionTimestamp has changed.

Il file di output è il seguente:The output file is as follows:

"2016-02-19T07:32:36.8420000-08:00","User1",,True,"72a0660e-22df-428e-b672-e0977007177f"
"2016-02-17T11:52:43.6350000-08:00","User2",,True,"4a0cd19a-6e67-4d95-a119-4eda590226ba"
"2016-02-17T11:59:08.8320000-08:00","User2","2016-02-17T11:52:43.6350000-08:00",False,"4a0cd19a-6e67-4d95-a119-4eda590226ba"
"2016-02-11T07:04:17.2630000-08:00","User3",,True,"51860a7a-1610-4f74-a9ea-69d5eef7cd9c"
"2016-02-11T07:10:33.9720000-08:00","User3","2016-02-11T07:04:17.2630000-08:00",False,"51860a7a-1610-4f74-a9ea-69d5eef7cd9c"
"2016-02-15T21:27:41.8210000-08:00","User3","2016-02-11T07:10:33.9720000-08:00",False,"4d2bc48d-bdf3-4591-a9c1-7b15ceb8e074"
"2016-02-16T05:48:49.6360000-08:00","User3","2016-02-15T21:27:41.8210000-08:00",False,"dd3006d0-2dcd-42d0-b3a2-bc03dd77c8b9"
"2016-02-16T06:22:43.6390000-08:00","User3","2016-02-16T05:48:49.6360000-08:00",False,"dd3006d0-2dcd-42d0-b3a2-bc03dd77c8b9"
"2016-02-17T16:29:53.2280000-08:00","User3","2016-02-16T06:22:43.6390000-08:00",False,"2fa899c7-eecf-4b1b-a8cd-30c5357b4f3a"
"2016-02-17T16:39:07.2430000-08:00","User3","2016-02-17T16:29:53.2280000-08:00",False,"2fa899c7-eecf-4b1b-a8cd-30c5357b4f3a"
"2016-02-17T17:20:39.3220000-08:00","User3","2016-02-17T16:39:07.2430000-08:00",False,"2fa899c7-eecf-4b1b-a8cd-30c5357b4f3a"
"2016-02-19T05:23:54.5710000-08:00","User3","2016-02-17T17:20:39.3220000-08:00",False,"6ca7ed80-c149-4c22-b24b-94ff5b0d824d"
"2016-02-19T05:48:37.7510000-08:00","User3","2016-02-19T05:23:54.5710000-08:00",False,"6ca7ed80-c149-4c22-b24b-94ff5b0d824d"
"2016-02-19T06:40:27.4830000-08:00","User3","2016-02-19T05:48:37.7510000-08:00",False,"6ca7ed80-c149-4c22-b24b-94ff5b0d824d"
"2016-02-19T07:27:37.7550000-08:00","User3","2016-02-19T06:40:27.4830000-08:00",False,"6ca7ed80-c149-4c22-b24b-94ff5b0d824d"
"2016-02-19T19:35:40.9450000-08:00","User3","2016-02-19T07:27:37.7550000-08:00",False,"3f385f0b-3e68-4456-ac74-ff6cef093674"
"2016-02-20T00:07:37.8250000-08:00","User3","2016-02-19T19:35:40.9450000-08:00",False,"685f76d5-ca48-4c58-b77d-bd3a9ddb33da"
"2016-02-11T09:01:33.9720000-08:00","User4",,True,"9f0cf696-c8ba-449a-8d5f-1ca6ed8f2ee8"
"2016-02-17T06:30:38.6210000-08:00","User4","2016-02-11T09:01:33.9720000-08:00",False,"8b11fd2a-01bf-4a5e-a9af-3c92c4e4382a"
"2016-02-17T22:15:26.4020000-08:00","User4","2016-02-17T06:30:38.6210000-08:00",False,"4e1cb707-3b5f-49c1-90c7-9b33b86ca1f4"
"2016-02-18T14:37:27.6560000-08:00","User4","2016-02-17T22:15:26.4020000-08:00",False,"f4e44400-e837-40ed-8dfd-2ea264d4e338"
"2016-02-19T01:20:31.4800000-08:00","User4","2016-02-18T14:37:27.6560000-08:00",False,"2136f4cf-7c7d-43c1-8ae2-08f4ad6a6e08"

Questo esempio illustra lo scenario di un caso d'uso più complesso in cui si usa una variabile globale in una sezione code-behind applicata all'intero set di righe della memoria.This example demonstrates a more complicated use-case scenario in which we use a global variable inside a code-behind section that's applied to the entire memory rowset.

Usare tipi definiti dall'utente (UDT)Use user-defined types: UDT

I tipi definiti dall'utente (UDT) sono un'altra funzionalità di programmabilità di U-SQL.User-defined types, or UDT, is another programmability feature of U-SQL. L'UDT U-SQL funziona come un normale tipo definito dall'utente C#.U-SQL UDT acts like a regular C# user-defined type. C# è un linguaggio fortemente tipizzato che consente l'uso di tipi incorporati e personalizzati definiti dall'utente.C# is a strongly typed language that allows the use of built-in and custom user-defined types.

U-SQL non può serializzare o deserializzare implicitamente tipi definiti dall'utente arbitrari quando il tipo definito dall'utente viene passato tra i vertici nei set di righe.U-SQL cannot implicitly serialize or de-serialize arbitrary UDTs when the UDT is passed between vertices in rowsets. Di conseguenza, l'utente deve specificare un formattatore esplicito usando l'interfaccia IFormatter.This means that the user has to provide an explicit formatter by using the IFormatter interface. Questo fornisce a U-SQL i metodi di serializzazione e deserializzazione per il tipo definito dall'utente.This provides U-SQL with the serialize and de-serialize methods for the UDT.

Nota

Gli outputter e gli estrattori predefiniti di U-SQL non possono attualmente serializzare o deserializzare i dati UDT da o verso i file anche con l'impostazione di IFormatter.U-SQL’s built-in extractors and outputters currently cannot serialize or de-serialize UDT data to or from files even with the IFormatter set. Di conseguenza, quando si scrivono dati UDT in un file con l'istruzione OUTPUT o si leggono tali dati con un estrattore, è necessario passare i dati come stringa o matrice di byte.So when you're writing UDT data to a file with the OUTPUT statement, or reading it with an extractor, you have to pass it as a string or byte array. Si chiama quindi in modo esplicito il codice di serializzazione o deserializzazione, ossia il metodo ToString() del tipo definito dall'utente.Then you call the serialization and deserialization code (that is, the UDT’s ToString() method) explicitly. Gli outputter e gli estrattori definiti dall'utente, invece, possono leggere e scrivere i tipi definiti dall'utente.User-defined extractors and outputters, on the other hand, can read and write UDTs.

Se si prova a usare il tipo definito dall'utente in EXTRACTOR o OUTPUTTER, fuori dall'istruzione SELECT precedente, come illustrato di seguito:If we try to use UDT in EXTRACTOR or OUTPUTTER (out of previous SELECT), as shown here:

@rs1 =
    SELECT 
        MyNameSpace.Myfunction_Returning_UDT(filed1) AS myfield
    FROM @rs0;

OUTPUT @rs1 
    TO @output_file 
    USING Outputters.Text();

viene visualizzato l'errore seguente:We receive the following error:

Error   1   E_CSC_USER_INVALIDTYPEINOUTPUTTER: Outputters.Text was used to output column myfield of type
MyNameSpace.Myfunction_Returning_UDT.

Description:

Outputters.Text only supports built-in types.

Resolution:

Implement a custom outputter that knows how to serialize this type, or call a serialization method on the type in
the preceding SELECT.   C:\Users\sergeypu\Documents\Visual Studio 2013\Projects\USQL-Programmability\
USQL-Programmability\Types.usql 52  1   USQL-Programmability

Per usare il tipo definito dall'utente nell'outputter, è necessario serializzarlo in stringa con il metodo ToString() e creare un outputter personalizzato.To work with UDT in outputter, we either have to serialize it to string with the ToString() method or create a custom outputter.

Al momento non è possibile usare UDT in GROUP BY.UDTs currently cannot be used in GROUP BY. Se si usa l'UDT in GROUP BY, viene generato l'errore seguente:If UDT is used in GROUP BY, the following error is thrown:

Error   1   E_CSC_USER_INVALIDTYPEINCLAUSE: GROUP BY doesn't support type MyNameSpace.Myfunction_Returning_UDT
for column myfield

Description:

GROUP BY doesn't support UDT or Complex types.

Resolution:

Add a SELECT statement where you can project a scalar column that you want to use with GROUP BY.
C:\Users\sergeypu\Documents\Visual Studio 2013\Projects\USQL-Programmability\USQL-Programmability\Types.usql
62  5   USQL-Programmability

Per definire un UDT, è necessario:To define a UDT, we have to:

  • Aggiungere gli spazi dei nomi seguenti:Add the following namespaces:
using Microsoft.Analytics.Interfaces
using System.IO;
  • Aggiungere Microsoft.Analytics.Interfaces, obbligatorio per le interfacce per i tipi definiti dall'utente.Add Microsoft.Analytics.Interfaces, which is required for the UDT interfaces. Per definire l'interfaccia IFormatter potrebbe essere necessario anche System.IO.In addition, System.IO might be needed to define the IFormatter interface.

  • Definire un tipo definito dall'utente con l'attributo SqlUserDefinedType.Define a used-defined type with SqlUserDefinedType attribute.

SqlUserDefinedType è usato per contrassegnare la definizione di un tipo in un assembly come tipo definito dall'utente (UDT) in U-SQL.SqlUserDefinedType is used to mark a type definition in an assembly as a user-defined type (UDT) in U-SQL. Le proprietà dell'attributo corrispondono alle caratteristiche fisiche dell'UDT.The properties on the attribute reflect the physical characteristics of the UDT. Questa classe non può essere ereditata.This class cannot be inherited.

SqlUserDefinedType è un attributo obbligatorio per la definizione dell'UDT.SqlUserDefinedType is a required attribute for UDT definition.

Il costruttore della classe:The constructor of the class:

  • SqlUserDefinedTypeAttribute (formattatore di tipo)SqlUserDefinedTypeAttribute (type formatter)

  • Formattatore di tipo: parametro obbligatorio per definire un formattatore UDT. Nello specifico, qui deve essere passato il tipo dell'interfaccia IFormatter.Type formatter: Required parameter to define an UDT formatter--specifically, the type of the IFormatter interface must be passed here.

[SqlUserDefinedType(typeof(MyTypeFormatter))]
public class MyType
{ … }
  • Un tipo definito dall'utente richiede in genere anche la definizione dell'interfaccia IFormatter, come illustrato nell'esempio seguente:Typical UDT also requires definition of the IFormatter interface, as shown in the following example:
public class MyTypeFormatter : IFormatter<MyType>
{
    public void Serialize(MyType instance, IColumnWriter writer, ISerializationContext context)
    { … }

    public MyType Deserialize(IColumnReader reader, ISerializationContext context)
    { … }
}

L'interfaccia IFormatter serializza e deserializza un oggetto grafico con il tipo radice <typeparamref name="T">.The IFormatter interface serializes and de-serializes an object graph with the root type of <typeparamref name="T">.

<typeparam name="T"> il tipo radice per l'oggetto grafico da serializzare e deserializzare.<typeparam name="T">The root type for the object graph to serialize and de-serialize.

  • Deserialize: deserializza i dati nel flusso fornito e ricostruisce il grafico degli oggetti.Deserialize: De-serializes the data on the provided stream and reconstitutes the graph of objects.

  • Serialize: serializza un oggetto o un grafico di oggetti con la radice specificata nel flusso fornito.Serialize: Serializes an object, or graph of objects, with the given root to the provided stream.

MyType instance: istanza del tipo.MyType instance: Instance of the type.
IColumnWriter writer/IColumnReader reader: flusso di colonna sottostante.IColumnWriter writer / IColumnReader reader: The underlying column stream.
ISerializationContext context: enumerazione che definisce un set di flag che specifica il contesto di origine o di destinazione per il flusso durante la serializzazione.ISerializationContext context: Enum that defines a set of flags that specifies the source or destination context for the stream during serialization.

  • Intermediate: specifica che il contesto di origine o di destinazione non è un archivio permanente.Intermediate: Specifies that the source or destination context is not a persisted store.

  • Persistence: specifica che il contesto di origine o di destinazione è un archivio permanente.Persistence: Specifies that the source or destination context is a persisted store.

Come un normale tipo C#, la definizione di un tipo definito dall'utente di U-SQL può includere override per operatori come +/==/!= e così via.As a regular C# type, a U-SQL UDT definition can include overrides for operators such as +/==/!=. Può anche includere metodi statici.It can also include static methods. Ad esempio, se si intende usare il tipo definito dall'utente come parametro per una funzione di aggregazione MIN U-SQL, è necessario definire l'override dell'operatore <.For example, if we are going to use this UDT as a parameter to a U-SQL MIN aggregate function, we have to define < operator override.

In precedenza in questa guida è stato illustrato un esempio di identificazione del periodo fiscale dalla data specifica nel formato Qn:Pn (Q1:P10).Earlier in this guide, we demonstrated an example for fiscal period identification from the specific date in the format Qn:Pn (Q1:P10). Nell'esempio seguente viene illustrato come definire un tipo personalizzato per i valori del periodo fiscale.The following example shows how to define a custom type for fiscal period values.

Di seguito è riportato un esempio di sezione code-behind con tipo definito dall'utente e interfaccia IFormatter personalizzati:Following is an example of a code-behind section with custom UDT and IFormatter interface:

[SqlUserDefinedType(typeof(FiscalPeriodFormatter))]
public struct FiscalPeriod
{
    public int Quarter { get; private set; }

    public int Month { get; private set; }

    public FiscalPeriod(int quarter, int month):this()
    {
    this.Quarter = quarter;
    this.Month = month;
    }

    public override bool Equals(object obj)
    {
    if (ReferenceEquals(null, obj))
    {
        return false;
    }

    return obj is FiscalPeriod && Equals((FiscalPeriod)obj);
    }

    public bool Equals(FiscalPeriod other)
    {
return this.Quarter.Equals(other.Quarter) && this.Month.Equals(other.Month);
    }

    public bool GreaterThan(FiscalPeriod other)
    {
return this.Quarter.CompareTo(other.Quarter) > 0 || this.Month.CompareTo(other.Month) > 0;
    }

    public bool LessThan(FiscalPeriod other)
    {
return this.Quarter.CompareTo(other.Quarter) < 0 || this.Month.CompareTo(other.Month) < 0;
    }

    public override int GetHashCode()
    {
    unchecked
    {
        return (this.Quarter.GetHashCode() * 397) ^ this.Month.GetHashCode();
    }
    }

    public static FiscalPeriod operator +(FiscalPeriod c1, FiscalPeriod c2)
    {
return new FiscalPeriod((c1.Quarter + c2.Quarter) > 4 ? (c1.Quarter + c2.Quarter)-4 : (c1.Quarter + c2.Quarter), (c1.Month + c2.Month) > 12 ? (c1.Month + c2.Month) - 12 : (c1.Month + c2.Month));
    }

    public static bool operator ==(FiscalPeriod c1, FiscalPeriod c2)
    {
    return c1.Equals(c2);
    }

    public static bool operator !=(FiscalPeriod c1, FiscalPeriod c2)
    {
    return !c1.Equals(c2);
    }
    public static bool operator >(FiscalPeriod c1, FiscalPeriod c2)
    {
    return c1.GreaterThan(c2);
    }
    public static bool operator <(FiscalPeriod c1, FiscalPeriod c2)
    {
    return c1.LessThan(c2);
    }
    public override string ToString()
    {
    return (String.Format("Q{0}:P{1}", this.Quarter, this.Month));
    }

}

public class FiscalPeriodFormatter : IFormatter<FiscalPeriod>
{
    public void Serialize(FiscalPeriod instance, IColumnWriter writer, ISerializationContext context)
    {
    using (var binaryWriter = new BinaryWriter(writer.BaseStream))
    {
        binaryWriter.Write(instance.Quarter);
        binaryWriter.Write(instance.Month);
        binaryWriter.Flush();
    }
    }

    public FiscalPeriod Deserialize(IColumnReader reader, ISerializationContext context)
    {
    using (var binaryReader = new BinaryReader(reader.BaseStream))
    {
var result = new FiscalPeriod(binaryReader.ReadInt16(), binaryReader.ReadInt16());
        return result;
    }
    }
}

Il tipo definito include due numeri, corrispondenti a trimestre e mese.The defined type includes two numbers: quarter and month. Qui sono definiti gli operatori ==/!=/>/< e il metodo statico ToString().Operators ==/!=/>/< and static method ToString() are defined here.

Come indicato in precedenza, il tipo definito dall'utente può essere usato nelle espressioni SELECT, ma non in OUTPUTTER/EXTRACTOR senza serializzazione personalizzata.As mentioned earlier, UDT can be used in SELECT expressions, but cannot be used in OUTPUTTER/EXTRACTOR without custom serialization. Deve essere serializzato come stringa con ToString() oppure deve essere usato con un elemento OUTPUTTER/EXTRACTOR personalizzato.It either has to be serialized as a string with ToString() or used with a custom OUTPUTTER/EXTRACTOR.

Ora esaminiamo l'uso dell'UDT.Now let’s discuss usage of UDT. In una sezione code-behind, la funzione GetFiscalPeriod è stata modificata come segue:In a code-behind section, we changed our GetFiscalPeriod function to the following:

public static FiscalPeriod GetFiscalPeriodWithCustomType(DateTime dt)
{
    int FiscalMonth = 0;
    if (dt.Month < 7)
    {
    FiscalMonth = dt.Month + 6;
    }
    else
    {
    FiscalMonth = dt.Month - 6;
    }

    int FiscalQuarter = 0;
    if (FiscalMonth >= 1 && FiscalMonth <= 3)
    {
    FiscalQuarter = 1;
    }
    if (FiscalMonth >= 4 && FiscalMonth <= 6)
    {
    FiscalQuarter = 2;
    }
    if (FiscalMonth >= 7 && FiscalMonth <= 9)
    {
    FiscalQuarter = 3;
    }
    if (FiscalMonth >= 10 && FiscalMonth <= 12)
    {
    FiscalQuarter = 4;
    }

    return new FiscalPeriod(FiscalQuarter, FiscalMonth);
}

Come si può notare, restituisce il valore del tipo FiscalPeriod.As you can see, it returns the value of our FiscalPeriod type.

Di seguito è riportato un esempio dell'ulteriore uso nello script U-SQL di base.Here we provide an example of how to use it further in U-SQL base script. Questo esempio illustra diverse forme di chiamata del tipo definito dall'utente dallo script U-SQL.This example demonstrates different forms of UDT invocation from U-SQL script.

DECLARE @input_file string = @"c:\work\cosmos\usql-programmability\input_file.tsv";
DECLARE @output_file string = @"c:\work\cosmos\usql-programmability\output_file.tsv";

@rs0 =
    EXTRACT
        guid string,
        dt DateTime,
        user String,
        des String
    FROM @input_file USING Extractors.Tsv();

@rs1 =
    SELECT 
        guid AS start_id,
        dt,
        DateTime.Now.ToString("M/d/yyyy") AS Nowdate,
        USQL_Programmability.CustomFunctions.GetFiscalPeriodWithCustomType(dt).Quarter AS fiscalquarter,
        USQL_Programmability.CustomFunctions.GetFiscalPeriodWithCustomType(dt).Month AS fiscalmonth,
        USQL_Programmability.CustomFunctions.GetFiscalPeriodWithCustomType(dt) + new USQL_Programmability.CustomFunctions.FiscalPeriod(1,7) AS fiscalperiod_adjusted,
        user,
        des
    FROM @rs0;

@rs2 =
    SELECT 
           start_id,
           dt,
           DateTime.Now.ToString("M/d/yyyy") AS Nowdate,
           fiscalquarter,
           fiscalmonth,
           USQL_Programmability.CustomFunctions.GetFiscalPeriodWithCustomType(dt).ToString() AS fiscalperiod,

       // This user-defined type was created in the prior SELECT.  Passing the UDT to this subsequent SELECT would have failed if the UDT was not annotated with an IFormatter.
           fiscalperiod_adjusted.ToString() AS fiscalperiod_adjusted,
           user,
           des
    FROM @rs1;

OUTPUT @rs2 
    TO @output_file 
    USING Outputters.Text();

Ecco un esempio di una sezione code-behind completa:Here's an example of a full code-behind section:

using Microsoft.Analytics.Interfaces;
using Microsoft.Analytics.Types.Sql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace USQL_Programmability
{
    public class CustomFunctions
    {
        static public DateTime? ToDateTime(string dt)
        {
            DateTime dtValue;

            if (!DateTime.TryParse(dt, out dtValue))
                return Convert.ToDateTime(dt);
            else
                return null;
        }

        public static FiscalPeriod GetFiscalPeriodWithCustomType(DateTime dt)
        {
            int FiscalMonth = 0;
            if (dt.Month < 7)
            {
                FiscalMonth = dt.Month + 6;
            }
            else
            {
                FiscalMonth = dt.Month - 6;
            }

            int FiscalQuarter = 0;
            if (FiscalMonth >= 1 && FiscalMonth <= 3)
            {
                FiscalQuarter = 1;
            }
            if (FiscalMonth >= 4 && FiscalMonth <= 6)
            {
                FiscalQuarter = 2;
            }
            if (FiscalMonth >= 7 && FiscalMonth <= 9)
            {
                FiscalQuarter = 3;
            }
            if (FiscalMonth >= 10 && FiscalMonth <= 12)
            {
                FiscalQuarter = 4;
            }

            return new FiscalPeriod(FiscalQuarter, FiscalMonth);
        }



        [SqlUserDefinedType(typeof(FiscalPeriodFormatter))]
        public struct FiscalPeriod
        {
            public int Quarter { get; private set; }

            public int Month { get; private set; }

            public FiscalPeriod(int quarter, int month):this()
            {
                this.Quarter = quarter;
                this.Month = month;
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj))
                {
                    return false;
                }

                return obj is FiscalPeriod && Equals((FiscalPeriod)obj);
            }

            public bool Equals(FiscalPeriod other)
            {
return this.Quarter.Equals(other.Quarter) &&    this.Month.Equals(other.Month);
            }

            public bool GreaterThan(FiscalPeriod other)
            {
return this.Quarter.CompareTo(other.Quarter) > 0 || this.Month.CompareTo(other.Month) > 0;
            }

            public bool LessThan(FiscalPeriod other)
            {
return this.Quarter.CompareTo(other.Quarter) < 0 || this.Month.CompareTo(other.Month) < 0;
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    return (this.Quarter.GetHashCode() * 397) ^ this.Month.GetHashCode();
                }
            }

            public static FiscalPeriod operator +(FiscalPeriod c1, FiscalPeriod c2)
            {
return new FiscalPeriod((c1.Quarter + c2.Quarter) > 4 ? (c1.Quarter + c2.Quarter)-4 : (c1.Quarter + c2.Quarter), (c1.Month + c2.Month) > 12 ? (c1.Month + c2.Month) - 12 : (c1.Month + c2.Month));
            }

            public static bool operator ==(FiscalPeriod c1, FiscalPeriod c2)
            {
                return c1.Equals(c2);
            }

            public static bool operator !=(FiscalPeriod c1, FiscalPeriod c2)
            {
                return !c1.Equals(c2);
            }
            public static bool operator >(FiscalPeriod c1, FiscalPeriod c2)
            {
                return c1.GreaterThan(c2);
            }
            public static bool operator <(FiscalPeriod c1, FiscalPeriod c2)
            {
                return c1.LessThan(c2);
            }
            public override string ToString()
            {
                return (String.Format("Q{0}:P{1}", this.Quarter, this.Month));
            }

        }

        public class FiscalPeriodFormatter : IFormatter<FiscalPeriod>
        {
public void Serialize(FiscalPeriod instance, IColumnWriter writer, ISerializationContext context)
            {
                using (var binaryWriter = new BinaryWriter(writer.BaseStream))
                {
                    binaryWriter.Write(instance.Quarter);
                    binaryWriter.Write(instance.Month);
                    binaryWriter.Flush();
                }
            }

public FiscalPeriod Deserialize(IColumnReader reader, ISerializationContext context)
            {
                using (var binaryReader = new BinaryReader(reader.BaseStream))
                {
var result = new FiscalPeriod(binaryReader.ReadInt16(), binaryReader.ReadInt16());
                    return result;
                }
            }
        }
    }
}

Usare aggregazioni definite dall'utente (UDAGG)Use user-defined aggregates: UDAGG

Le aggregazioni definite dall'utente sono funzioni correlate all'aggregazione che non sono già incluse in U-SQL.User-defined aggregates are any aggregation-related functions that are not shipped out-of-the-box with U-SQL. Può trattarsi ad esempio di una funzione di aggregazione per eseguire calcoli matematici personalizzati, concatenazioni di stringa o modifiche con stringhe e così via.The example can be an aggregate to perform custom math calculations, string concatenations, manipulations with strings, and so on.

La definizione della classe base delle aggregazioni definite dall'utente è la seguente:The user-defined aggregate base class definition is as follows:

    [SqlUserDefinedAggregate]
    public abstract class IAggregate<T1, T2, TResult> : IAggregate
    {
        protected IAggregate();

        public abstract void Accumulate(T1 t1, T2 t2);
        public abstract void Init();
        public abstract TResult Terminate();
    }

SqlUserDefinedAggregate indica che il tipo deve essere registrato come aggregazione definita dall'utente.SqlUserDefinedAggregate indicates that the type should be registered as a user-defined aggregate. Questa classe non può essere ereditata.This class cannot be inherited.

L'attributo SqlUserDefinedType è facoltativo per la definizione di aggregazioni definite dall'utente.SqlUserDefinedType attribute is optional for UDAGG definition.

La classe base consente di passare tre parametri astratti: due come parametri di input e uno come risultato.The base class allows you to pass three abstract parameters: two as input parameters and one as the result. I tipi di dati sono variabili e devono essere definiti quando viene ereditata la classe.The data types are variable and should be defined during class inheritance.

public class GuidAggregate : IAggregate<string, string, string>
{
    string guid_agg;

    public override void Init()
    { … }

    public override void Accumulate(string guid, string user)
    { … }

    public override string Terminate()
    { … }
}
  • Init esegue la chiamata una volta per ogni gruppo durante il calcolo.Init invokes once for each group during computation. Fornisce la routine di inizializzazione per ogni gruppo di aggregazione.It provides an initialization routine for each aggregation group.
  • Accumulate viene eseguito una volta per ogni valore.Accumulate is executed once for each value. Fornisce la funzionalità principale per l'algoritmo di aggregazione.It provides the main functionality for the aggregation algorithm. Consente di aggregare valori con vari tipi di dati che vengono definiti quando viene ereditata la classe.It can be used to aggregate values with various data types that are defined during class inheritance. Può accettare due parametri di tipi di dati della variabile.It can accept two parameters of variable data types.
  • Terminate viene eseguito una volta per ogni gruppo di aggregazione al termine dell'elaborazione per restituire il risultato per ogni gruppo.Terminate is executed once per aggregation group at the end of processing to output the result for each group.

Per dichiarare tipi di dati di input e output corretti, usare la definizione di classe come segue:To declare correct input and output data types, use the class definition as follows:

public abstract class IAggregate<T1, T2, TResult> : IAggregate
  • T1: primo parametro per AccumulateT1: First parameter to accumulate
  • T2: primo parametro per AccumulateT2: First parameter to accumulate
  • TResult: tipo restituito di TerminateTResult: Return type of terminate

Ad esempio:For example:

public class GuidAggregate : IAggregate<string, int, int>

oppureor

public class GuidAggregate : IAggregate<string, string, string>

Usare aggregazioni definite dall'utente in U-SQLUse UDAGG in U-SQL

Per usare un'aggregazione definita dall'utente, per prima cosa è necessario definirla nel code-behind oppure farvi riferimento dalla DLL di programmabilità esistente, come descritto in precedenza.To use UDAGG, first define it in code-behind or reference it from the existent programmability DLL as discussed earlier.

Usare quindi la sintassi seguente:Then use the following syntax:

AGG<UDAGG_functionname>(param1,param2)

Di seguito è riportato un esempio di aggregazione definita dall'utente:Here is an example of UDAGG:

public class GuidAggregate : IAggregate<string, string, string>
{
    string guid_agg;

    public override void Init()
    {
        guid_agg = "";
    }

    public override void Accumulate(string guid, string user)
    {
        if (user.ToUpper()== "USER1")
        {
        guid_agg += "{" + guid + "}";
        }
    }

    public override string Terminate()
    {
        return guid_agg;
    }

}

Ecco lo script U-SQL di base:And base U-SQL script:

DECLARE @input_file string = @"\usql-programmability\input_file.tsv";
DECLARE @output_file string = @" \usql-programmability\output_file.tsv";

@rs0 =
    EXTRACT
            guid string,
        dt DateTime,
            user String,
            des String
    FROM @input_file 
    USING Extractors.Tsv();

@rs1 =
    SELECT
        user,
        AGG<USQL_Programmability.GuidAggregate>(guid,user) AS guid_list
    FROM @rs0
    GROUP BY user;

OUTPUT @rs1 TO @output_file USING Outputters.Text();

Nello scenario di questo caso d'uso, vengono concatenati GUID di classe per gli utenti specifici.In this use-case scenario, we concatenate class GUIDs for the specific users.

Usare oggetti definiti dall'utente (UDO)Use user-defined objects: UDO

U-SQL consente di definire oggetti di programmabilità personalizzati, denominati oggetti definiti dall'utente (UDO).U-SQL enables you to define custom programmability objects, which are called user-defined objects or UDO.

Di seguito è riportato un elenco degli oggetti definiti dall'utente in U-SQL:The following is a list of UDO in U-SQL:

  • Estrattori definiti dall'utenteUser-defined extractors

    • Estrazione riga per rigaExtract row by row
    • Vengono usati per implementare l'estrazione di dati da file strutturati personalizzatiUsed to implement data extraction from custom structured files
  • Outputter definiti dall'utenteUser-defined outputters

    • Output riga per rigaOutput row by row
    • Vengono usati per tipi di dati di output o formati di file personalizzatiUsed to output custom data types or custom file formats
  • Elaboratori definiti dall'utenteUser-defined processors

    • Viene usato per richiedere una riga e produrre una rigaTake one row and produce one row
    • Vengono usati per ridurre il numero di colonne o produrre nuove colonne con valori derivati da un set di colonne esistenteUsed to reduce the number of columns or produce new columns with values that are derived from an existing column set
  • Oggetti di applicazione definiti dall'utenteUser-defined appliers

    • Serve a richiedere una riga e produrre da 0 a n righeTake one row and produce 0 to n rows
    • Viene usato con OUTER/CROSS APPLYUsed with OUTER/CROSS APPLY
  • Combinatori definiti dall'utenteUser-defined combiners

    • Combinano set di righe (JOIN definiti dall'utente)Combines rowsets--user-defined JOINs
  • Riduttori definiti dall'utenteUser-defined reducers

    • Serve a richiedere n righe e produrre una rigaTake n rows and produce one row
    • Vengono usati per ridurre il numero di righeUsed to reduce the number of rows

Un oggetto definito dall'utente viene in genere chiamato in modo esplicito negli script U-SQL come parte delle istruzioni U-SQL seguenti:UDO is typically called explicitly in U-SQL script as part of the following U-SQL statements:

  • EXTRACTEXTRACT
  • OUTPUTOUTPUT
  • PROCESSPROCESS
  • COMBINECOMBINE
  • REDUCEREDUCE

Nota

Gli oggetti definiti dall'utente sono limitati per occupare 0,5 GB di memoria.UDO’s are limited to consume 0.5Gb memory. Questa limitazione di memoria non è applicabile alle esecuzioni locali.This memory limitation does not apply to local executions.

Usare estrattori definiti dall'utenteUse user-defined extractors

U-SQL consente di importare dati esterni con un'istruzione EXTRACT.U-SQL allows you to import external data by using an EXTRACT statement. L'istruzione EXTRACT consente di usare estrattori UDO predefiniti.An EXTRACT statement can use built-in UDO extractors:

  • Extractors.Text(): consente di estrarre da file di testo delimitati di varie codifiche.Extractors.Text(): Provides extraction from delimited text files of different encodings.

  • Extractors.Csv(): consente di estrarre da file di testo delimitati da virgole (CSV) di varie codifiche.Extractors.Csv(): Provides extraction from comma-separated value (CSV) files of different encodings.

  • Extractors.Tsv(): consente di estrarre da file di testo delimitati da tabulazioni (TSV) di varie codifiche.Extractors.Tsv(): Provides extraction from tab-separated value (TSV) files of different encodings.

Può essere utile per sviluppare un estrattore personalizzato.It can be useful to develop a custom extractor. Questo può essere opportuno durante un'importazione di dati, se si vuole eseguire una o più delle attività seguenti:This can be helpful during data import if we want to do any of the following tasks:

  • Modificare i dati di input suddividendo le colonne e modificando singoli valori.Modify input data by splitting columns and modifying individual values. Per la combinazione di colonne è preferibile la funzionalità PROCESSOR.The PROCESSOR functionality is better for combining columns.
  • Analizzare dati non strutturati, come pagine Web e messaggi di posta elettronica, o dati parzialmente non strutturati, ad esempio XML/JSON.Parse unstructured data such as Web pages and emails, or semi-unstructured data such as XML/JSON.
  • Analizzare dati in una codifica non supportata.Parse data in unsupported encoding.

Per definire un estrattore definito dall'utente (UDE), è necessario creare un'interfaccia IExtractor.To define a user-defined extractor, or UDE, we need to create an IExtractor interface. Tutti i parametri di input per l'estrattore, come i delimitatori di riga/colonna e la codifica, devono essere definiti nel costruttore della classe.All input parameters to the extractor, such as column/row delimiters, and encoding, need to be defined in the constructor of the class. L'interfaccia IExtractor deve contenere anche una definizione per l'override IEnumerable<IRow>, come illustrato di seguito:The IExtractor interface should also contain a definition for the IEnumerable<IRow> override as follows:

[SqlUserDefinedExtractor]
public class SampleExtractor : IExtractor
{
     public SampleExtractor(string row_delimiter, char col_delimiter)
     { … }

     public override IEnumerable<IRow> Extract(IUnstructuredReader input, IUpdatableRow output)
     { … }
}

L'attributo SqlUserDefinedExtractor indica che il tipo deve essere registrato come estrattore definito dall'utente.The SqlUserDefinedExtractor attribute indicates that the type should be registered as a user-defined extractor. Questa classe non può essere ereditata.This class cannot be inherited.

SqlUserDefinedExtractor è un attributo facoltativo per la definizione di UDE,SqlUserDefinedExtractor is an optional attribute for UDE definition. che consente di definire la proprietà AtomicFileProcessing dell'oggetto UDE.It used to define AtomicFileProcessing property for the UDE object.

  • bool AtomicFileProcessingbool AtomicFileProcessing

  • true indica che l'estrattore richiede file di input atomici (JSON, XML e così via)true = Indicates that this extractor requires atomic input files (JSON, XML, ...)

  • false indica che l'estrattore può gestire file suddivisi/distribuiti (CSV, SEQ e così via)false = Indicates that this extractor can deal with split / distributed files (CSV, SEQ, ...)

I principali oggetti di programmabilità UDE sono input e output.The main UDE programmability objects are input and output. L'oggetto di input viene usato per enumerare i dati di input come IUnstructuredReader.The input object is used to enumerate input data as IUnstructuredReader. L'oggetto di output viene usato per impostare i dati di output come risultato dell'attività di estrazione.The output object is used to set output data as a result of the extractor activity.

I dati di input sono accessibili tramite System.IO.Stream e System.IO.StreamReader.The input data is accessed through System.IO.Stream and System.IO.StreamReader.

Per l'enumerazione delle colonne di input, per prima cosa si suddivide il flusso di input con un delimitatore di riga.For input columns enumeration, we first split the input stream by using a row delimiter.

foreach (Stream current in input.Split(my_row_delimiter))
{
…
}

Quindi, la riga di input viene ulteriormente suddivisa nelle parti di una colonna.Then, further split input row into column parts.

foreach (Stream current in input.Split(my_row_delimiter))
{
…
    string[] parts = line.Split(my_column_delimiter);
    foreach (string part in parts)
    { … }
}

Per impostare i dati di output, si usa il metodo output.Set.To set output data, we use the output.Set method.

È importante comprendere che l'estrattore personalizzato restituisce solo le colonne e i valori definiti con l'output.It's important to understand that the custom extractor only outputs columns and values that are defined with the output. il metodo di chiamata output.Set.Set method call.

output.Set<string>(count, part);

L'output effettivo dell'estrattore viene attivato chiamando yield return output.AsReadOnly();.The actual extractor output is triggered by calling yield return output.AsReadOnly();.

Di seguito è riportato l'esempio di estrattore:Following is the extractor example:

[SqlUserDefinedExtractor(AtomicFileProcessing = true)]
public class FullDescriptionExtractor : IExtractor
{
     private Encoding _encoding;
     private byte[] _row_delim;
     private char _col_delim;

    public FullDescriptionExtractor(Encoding encoding, string row_delim = "\r\n", char col_delim = '\t')
    {
         this._encoding = ((encoding == null) ? Encoding.UTF8 : encoding);
         this._row_delim = this._encoding.GetBytes(row_delim);
         this._col_delim = col_delim;

    }

    public override IEnumerable<IRow> Extract(IUnstructuredReader input, IUpdatableRow output)
    {
         string line;
         //Read the input line by line
         foreach (Stream current in input.Split(_encoding.GetBytes("\r\n")))
         {
        using (System.IO.StreamReader streamReader = new StreamReader(current, this._encoding))
         {
             line = streamReader.ReadToEnd().Trim();
             //Split the input by the column delimiter
             string[] parts = line.Split(this._col_delim);
             int count = 0; // start with first column
             foreach (string part in parts)
             {
    if (count == 0)
             {  // for column “guid”, re-generated guid
                 Guid new_guid = Guid.NewGuid();
                 output.Set<Guid>(count, new_guid);
             }
             else if (count == 2)
             {
                 // for column “user”, convert to UPPER case
                 output.Set<string>(count, part.ToUpper());

             }
             else
             {
                 // keep the rest of the columns as-is
                 output.Set<string>(count, part);
             }
             count += 1;
             }

         }
         yield return output.AsReadOnly();
         }
         yield break;
     }
}

Nello scenario di questo caso d'uso, l'estrattore rigenera il GUID per la colonna "guid" e converte i valori della colonna "user" in lettere maiuscole.In this use-case scenario, the extractor regenerates the GUID for “guid” column and converts the values of “user” column to upper case. Gli estrattori personalizzati possono produrre risultati più complessi analizzando e modificando i dati di input.Custom extractors can produce more complicated results by parsing input data and manipulating it.

Di seguito è riportato uno script U-SQL di base che usa un estrattore personalizzato:Following is base U-SQL script that uses a custom extractor:

DECLARE @input_file string = @"\usql-programmability\input_file.tsv";
DECLARE @output_file string = @"\usql-programmability\output_file.tsv";

@rs0 =
    EXTRACT
            guid Guid,
        dt String,
            user String,
            des String
    FROM @input_file
        USING new USQL_Programmability.FullDescriptionExtractor(Encoding.UTF8);

OUTPUT @rs0 TO @output_file USING Outputters.Text();

Usare outputter definiti dall'utenteUse user-defined outputters

L'outputter definito dall'utente è un altro oggetto definito dall'utente di U-SQL che consente di estendere una funzionalità predefinita di U-SQL.User-defined outputter is another U-SQL UDO that allows you to extend built-in U-SQL functionality. Come per l'estrattore, esistono diversi outputter integrati.Similar to the extractor, there are several built-in outputters.

  • Outputters.Text(): scrive i dati in file di testo delimitati di codifiche diverse.Outputters.Text(): Writes data to delimited text files of different encodings.
  • Outputters.Csv(): scrive i dati in file di testo delimitati da virgole (CSV) di codifiche diverse.Outputters.Csv(): Writes data to comma-separated value (CSV) files of different encodings.
  • Outputters.Tsv(): scrive i dati in file di testo delimitati da tabulazioni (TSV) di codifiche diverse.Outputters.Tsv(): Writes data to tab-separated value (TSV) files of different encodings.

L'outputter personalizzato consente di scrivere i dati in un formato definito personalizzato.Custom outputter allows you to write data in a custom defined format. Questo può essere utile per le attività seguenti:This can be useful for the following tasks:

  • Scrittura di i dati in file non strutturati o semistrutturatiWriting data to semi-structured or unstructured files.
  • Scrittura di dati in codifiche non supportateWriting data not supported encodings.
  • Modifica dei dati di output o aggiunta di attributi personalizzatiModifying output data or adding custom attributes.

Per definire outputter definiti dall'utente, è necessario creare l'interfaccia IOutputter.To define user-defined outputter, we need to create the IOutputter interface.

Di seguito è riportata l'implementazione della classe IOutputter di base:Following is the base IOutputter class implementation:

public abstract class IOutputter : IUserDefinedOperator
{
    protected IOutputter();

    public virtual void Close();
    public abstract void Output(IRow input, IUnstructuredWriter output);
}

Tutti i parametri di input per l'outputter, come i delimitatori di riga/colonna, la codifica e così via, devono essere definiti nel costruttore della classe.All input parameters to the outputter, such as column/row delimiters, encoding, and so on, need to be defined in the constructor of the class. L'interfaccia IOutputter deve contenere anche una definizione per l'override void Output.The IOutputter interface should also contain a definition for void Output override. L'attributo [SqlUserDefinedOutputter(AtomicFileProcessing = true) può essere facoltativamente impostato per l'elaborazione di file atomici.The attribute [SqlUserDefinedOutputter(AtomicFileProcessing = true) can optionally be set for atomic file processing. Per altre informazioni, vedere i dettagli riportati di seguito.For more information, see the following details.

[SqlUserDefinedOutputter(AtomicFileProcessing = true)]
public class MyOutputter : IOutputter
{

    public MyOutputter(myparam1, myparam2)
    {
      …
    }

    public override void Close()
    {
      …
    }

    public override void Output(IRow row, IUnstructuredWriter output)
    {
      …
    }
}
  • Output viene chiamato per ogni riga di input.Output is called for each input row. Restituisce il set di righe IUnstructuredWriter output.It returns the IUnstructuredWriter output rowset.
  • La classe del costruttore viene usata per passare parametri all'outputter definito dall'utente.The Constructor class is used to pass parameters to the user-defined outputter.
  • Close viene usato per eseguire facoltativamente l'override per rilasciare uno stato dispendioso o determinare quando è stata scritta l'ultima riga.Close is used to optionally override to release expensive state or determine when the last row was written.

L'attributo SqlUserDefinedOutputter indica che il tipo deve essere registrato come outputter definito dall'utente.SqlUserDefinedOutputter attribute indicates that the type should be registered as a user-defined outputter. Questa classe non può essere ereditata.This class cannot be inherited.

SqlUserDefinedOutputter è un attributo facoltativo per la definizione di un outputter definito dall'utente.SqlUserDefinedOutputter is an optional attribute for a user-defined outputter definition. Viene usato per definire la proprietà AtomicFileProcessing.It's used to define the AtomicFileProcessing property.

  • bool AtomicFileProcessingbool AtomicFileProcessing

  • true indica che l'outputter richiede file di output atomici (JSON, XML e così via)true = Indicates that this outputter requires atomic output files (JSON, XML, ...)

  • false indica che l'outputter può gestire file suddivisi/distribuiti (CSV, SEQ e così via)false = Indicates that this outputter can deal with split / distributed files (CSV, SEQ, ...)

I principali oggetti di programmabilità sono row e output.The main programmability objects are row and output. L'oggetto row viene usato per enumerare i dati di output come interfaccia IRow.The row object is used to enumerate output data as IRow interface. Output viene usato per impostare i dati di output sul file di destinazione.Output is used to set output data to the target file.

I dati di output sono accessibili tramite l'interfaccia IRow.The output data is accessed through the IRow interface. I dati di output vengono trasmessi una riga alla volta.Output data is passed a row at a time.

I singoli valori vengono enumerati chiamando il metodo Get dell'interfaccia IRow:The individual values are enumerated by calling the Get method of the IRow interface:

row.Get<string>("column_name")

I nomi delle singole colonne possono essere determinati chiamando row.Schema:Individual column names can be determined by calling row.Schema:

ISchema schema = row.Schema;
var col = schema[i];
string val = row.Get<string>(col.Name)

Questo approccio consente di compilare un outputter flessibile per qualsiasi schema di metadati.This approach enables you to build a flexible outputter for any metadata schema.

I dati di output vengono scritti in un file usando System.IO.StreamWriter.The output data is written to file by using System.IO.StreamWriter. Il parametro di flusso viene impostato su output.BaseStrea come parte di IUnstructuredWriter output.The stream parameter is set to output.BaseStrea as part of IUnstructuredWriter output.

Si noti che è importante scaricare il buffer dei dati nel file dopo ogni iterazione di riga.Note that it's important to flush the data buffer to the file after each row iteration. L'oggetto StreamWriter, inoltre, deve essere usato con l'attributo Disposable abilitato (impostazione predefinita) e con la parola chiave using:In addition, the StreamWriter object must be used with the Disposable attribute enabled (default) and with the using keyword:

using (StreamWriter streamWriter = new StreamWriter(output.BaseStream, this._encoding))
{
…
}

In alternativa, chiamare il metodo Flush() in modo esplicito dopo ogni iterazione,Otherwise, call Flush() method explicitly after each iteration. come illustrato nell'esempio riportato di seguito.We show this in the following example.

Impostare intestazioni e piè di pagina per l'outputter definito dall'utenteSet headers and footers for user-defined outputter

Per impostare un'intestazione, usare il flusso di esecuzione dell'iterazione singola.To set a header, use single iteration execution flow.

public override void Output(IRow row, IUnstructuredWriter output)
{
 …
if (isHeaderRow)
{
    …                
}

 …
if (isHeaderRow)
{
    isHeaderRow = false;
}
 …
}
}

Il codice nel primo blocco if (isHeaderRow) viene eseguito una sola volta.The code in the first if (isHeaderRow) block is executed only once.

Per il piè di pagina, usare il riferimento all'istanza dell'oggetto System.IO.Stream (output.BaseStream).For the footer, use the reference to the instance of System.IO.Stream object (output.BaseStream). Scrivere il piè di pagina nel metodo Close() dell'interfaccia IOutputter.Write the footer in the Close() method of the IOutputter interface. Per altre informazioni, vedere l'esempio seguente.(For more information, see the following example.)

Di seguito è riportato un esempio di outputter definito dall'utente:Following is an example of a user-defined outputter:

[SqlUserDefinedOutputter(AtomicFileProcessing = true)]
public class HTMLOutputter : IOutputter
{
    // Local variables initialization
    private string row_delimiter;
    private char col_delimiter;
    private bool isHeaderRow;
    private Encoding encoding;
    private bool IsTableHeader = true;
    private Stream g_writer;

    // Parameters definition            
    public HTMLOutputter(bool isHeader = false, Encoding encoding = null)
    {
    this.isHeaderRow = isHeader;
    this.encoding = ((encoding == null) ? Encoding.UTF8 : encoding);
    }

    // The Close method is used to write the footer to the file. It's executed only once, after all rows
    public override void Close().
    {
    //Reference to IO.Stream object - g_writer
    StreamWriter streamWriter = new StreamWriter(g_writer, this.encoding);
    streamWriter.Write("</table>");
    streamWriter.Flush();
    streamWriter.Close();
    }

    public override void Output(IRow row, IUnstructuredWriter output)
    {
    System.IO.StreamWriter streamWriter = new StreamWriter(output.BaseStream, this.encoding);

    // Metadata schema initialization to enumerate column names
    ISchema schema = row.Schema;

    // This is a data-independent header--HTML table definition
    if (IsTableHeader)
    {
        streamWriter.Write("<table border=1>");
        IsTableHeader = false;
    }

    // HTML table attributes
    string header_wrapper_on = "<th>";
    string header_wrapper_off = "</th>";
    string data_wrapper_on = "<td>";
    string data_wrapper_off = "</td>";

    // Header row output--runs only once
    if (isHeaderRow)
    {
        streamWriter.Write("<tr>");
        for (int i = 0; i < schema.Count(); i++)
        {
        var col = schema[i];
        streamWriter.Write(header_wrapper_on + col.Name + header_wrapper_off);
        }
        streamWriter.Write("</tr>");
    }

    // Data row output
    streamWriter.Write("<tr>");                
    for (int i = 0; i < schema.Count(); i++)
    {
        var col = schema[i];
        string val = "";
        try
        {
        // Data type enumeration--required to match the distinct list of types from OUTPUT statement
        switch (col.Type.Name.ToString().ToLower())
        {
            case "string": val = row.Get<string>(col.Name).ToString(); break;
            case "guid": val = row.Get<Guid>(col.Name).ToString(); break;
            default: break;
        }
        }
        // Handling NULL values--keeping them empty
        catch (System.NullReferenceException)
        {
        }
        streamWriter.Write(data_wrapper_on + val + data_wrapper_off);
    }
    streamWriter.Write("</tr>");

    if (isHeaderRow)
    {
        isHeaderRow = false;
    }
    // Reference to the instance of the IO.Stream object for footer generation
    g_writer = output.BaseStream;
    streamWriter.Flush();
    }
}

// Define the factory classes
public static class Factory
{
    public static HTMLOutputter HTMLOutputter(bool isHeader = false, Encoding encoding = null)
    {
    return new HTMLOutputter(isHeader, encoding);
    }
}

Ecco lo script U-SQL di base:And U-SQL base script:

DECLARE @input_file string = @"\usql-programmability\input_file.tsv";
DECLARE @output_file string = @"\usql-programmability\output_file.html";

@rs0 =
    EXTRACT
            guid Guid,
        dt String,
            user String,
            des String
         FROM @input_file
         USING new USQL_Programmability.FullDescriptionExtractor(Encoding.UTF8);

OUTPUT @rs0 
    TO @output_file 
    USING new USQL_Programmability.HTMLOutputter(isHeader: true);

Questo è un outputter HTML che crea un file HTML con dati di tabella.This is an HTML outputter, which creates an HTML file with table data.

Chiamare l'outputter dallo script U-SQL di baseCall outputter from U-SQL base script

Per chiamare un outputter personalizzato dallo script U-SQL di base, è necessario creare la nuova istanza dell'oggetto outputter.To call a custom outputter from the base U-SQL script, the new instance of the outputter object has to be created.

OUTPUT @rs0 TO @output_file USING new USQL_Programmability.HTMLOutputter(isHeader: true);

Per evitare di creare un'istanza dell'oggetto nello script di base, è possibile creare un wrapper di funzione, come illustrato nell'esempio precedente:To avoid creating an instance of the object in base script, we can create a function wrapper, as shown in our earlier example:

        // Define the factory classes
        public static class Factory
        {
            public static HTMLOutputter HTMLOutputter(bool isHeader = false, Encoding encoding = null)
            {
                return new HTMLOutputter(isHeader, encoding);
            }
        }

In questo caso, la chiamata originale si presenta come segue:In this case, the original call looks like the following:

OUTPUT @rs0 
TO @output_file 
USING USQL_Programmability.Factory.HTMLOutputter(isHeader: true);

Usare elaboratori definiti dall'utenteUse user-defined processors

Un elaboratore definito dall'utente (UDP) è un tipo di oggetto definito dall'utente di U-SQL che consente di elaborare le righe in ingresso applicando funzionalità di programmabilità.User-defined processor, or UDP, is a type of U-SQL UDO that enables you to process the incoming rows by applying programmability features. Un elaboratore definito dall'utente consente di combinare colonne, modificare valori e aggiungere nuove colonne, se necessario.UDP enables you to combine columns, modify values, and add new columns if necessary. Essenzialmente, consente di elaborare un set di righe per produrre gli elementi dati necessari.Basically, it helps to process a rowset to produce required data elements.

Per definire un elaboratore definito dall'utente, è necessario creare un'interfaccia IProcessor con l'attributo SqlUserDefinedProcessor, che per gli elaboratori definiti dall'utente è facoltativo.To define a UDP, we need to create an IProcessor interface with the SqlUserDefinedProcessor attribute, which is optional for UDP.

L'interfaccia deve contenere la definizione per l'override dei set di righe dell'interfaccia IRow, come illustrato nell'esempio seguente:This interface should contain the definition for the IRow interface rowset override, as shown in the following example:

[SqlUserDefinedProcessor]
public class MyProcessor: IProcessor
{
public override IRow Process(IRow input, IUpdatableRow output)
 {
        …
 }
}

SqlUserDefinedProcessor indica che il tipo deve essere registrato come Processor definito dall'utente.SqlUserDefinedProcessor indicates that the type should be registered as a user-defined processor. Questa classe non può essere ereditata.This class cannot be inherited.

Per la definizione degli elaboratori definiti dall'utente, l'attributo SqlUserDefinedProcessor è facoltativo.The SqlUserDefinedProcessor attribute is optional for UDP definition.

I principali oggetti di programmabilità sono input e output.The main programmability objects are input and output. L'oggetto di input viene usato per enumerare le colonne di input e l'output e per impostare i dati di output come risultato dell'attività di elaborazione.The input object is used to enumerate input columns and output, and to set output data as a result of the processor activity.

Per l'enumerazione delle colonne di input, si usa il metodo input.Get.For input columns enumeration, we use the input.Get method.

string column_name = input.Get<string>("column_name");

Il parametro per il metodo input.Get è una colonna passata come parte della clausola PRODUCE dell'istruzione PROCESS dello script U-SQL di base.The parameter for input.Get method is a column that's passed as part of the PRODUCE clause of the PROCESS statement of the U-SQL base script. In questo caso è necessario usare il tipo di dati corretto.We need to use the correct data type here.

Per l'output, usare il metodo output.Set.For output, use the output.Set method.

È importante notare che il producer personalizzato restituisce solo le colonne e i valori definiti con la chiamata al metodo output.Set.It's important to note that custom producer only outputs columns and values that are defined with the output.Set method call.

output.Set<string>("mycolumn", mycolumn);

L'output effettivo dell'elaboratore viene attivato chiamando return output.AsReadOnly();.The actual processor output is triggered by calling return output.AsReadOnly();.

Di seguito è riportato un esempio di elaboratore:Following is a processor example:

[SqlUserDefinedProcessor]
public class FullDescriptionProcessor : IProcessor
{
public override IRow Process(IRow input, IUpdatableRow output)
 {
     string user = input.Get<string>("user");
     string des = input.Get<string>("des");
     string full_description = user.ToUpper() + "=>" + des;
     output.Set<string>("dt", input.Get<string>("dt"));
     output.Set<string>("full_description", full_description);
     output.Set<Guid>("new_guid", Guid.NewGuid());
     output.Set<Guid>("guid", input.Get<Guid>("guid"));
     return output.AsReadOnly();
 }
}

Nello scenario di questo caso d'uso, l'elaboratore genera una nuova colonna denominata "full_description" combinando le colonne esistenti, in questo caso "user" in lettere maiuscole e "des".In this use-case scenario, the processor is generating a new column called “full_description” by combining the existing columns--in this case, “user” in upper case, and “des”. Rigenera anche un GUID e restituisce il nuovo valore GUID e quello originale.It also regenerates a GUID and returns the original and new GUID values.

Come si può notare nell'esempio precedente, è possibile chiamare metodi C# durante la chiamata al metodo output.Set.As you can see from the previous example, you can call C# methods during output.Set method call.

Di seguito è riportato un esempio di script U-SQL di base che usa un elaboratore personalizzato:Following is an example of base U-SQL script that uses a custom processor:

DECLARE @input_file string = @"\usql-programmability\input_file.tsv";
DECLARE @output_file string = @"\usql-programmability\output_file.tsv";

@rs0 =
    EXTRACT
            guid Guid,
        dt String,
            user String,
            des String
    FROM @input_file USING Extractors.Tsv();

@rs1 =
     PROCESS @rs0
     PRODUCE dt String,
             full_description String,
             guid Guid,
             new_guid Guid
     USING new USQL_Programmability.FullDescriptionProcessor();

OUTPUT @rs1 TO @output_file USING Outputters.Text();

Usare oggetti di applicazione definiti dall'utenteUse user-defined appliers

Un oggetto di applicazione definito dall'utente di U-SQL consente di richiamare una funzione C# personalizzata per ogni riga restituita dall'espressione di tabella esterna di una query.A U-SQL user-defined applier enables you to invoke a custom C# function for each row that's returned by the outer table expression of a query. L'input di destra viene valutato per ogni riga dell'input di sinistra e le righe prodotte vengono combinate per l'output finale.The right input is evaluated for each row from the left input, and the rows that are produced are combined for the final output. L'elenco delle colonne prodotte dall'operatore APPLY è la combinazione del set di colonne dell'input di destra e di sinistra.The list of columns that are produced by the APPLY operator are the combination of the set of columns in the left and the right input.

Un oggetto di applicazione definito dall'utente viene richiamato come parte dell'espressione SELECT di U-SQL.User-defined applier is being invoked as part of the USQL SELECT expression.

La chiamata tipica all'oggetto di applicazione definito dall'utente si presenta come segue:The typical call to the user-defined applier looks like the following:

SELECT …
FROM …
CROSS APPLYis used to pass parameters
new MyScript.MyApplier(param1, param2) AS alias(output_param1 string, …);

Per altre informazioni sull'uso di oggetti di applicazione in un'espressione SELECT, vedere U-SQL SELECT Selecting from CROSS APPLY and OUTER APPLY (Selezione con SELECT di U-SQL da CROSS APPLY e OUTER APPLY).For more information about using appliers in a SELECT expression, see U-SQL SELECT Selecting from CROSS APPLY and OUTER APPLY.

La definizione della classe base degli oggetti di applicazione definiti dall'utente è la seguente:The user-defined applier base class definition is as follows:

public abstract class IApplier : IUserDefinedOperator
{
protected IApplier();

public abstract IEnumerable<IRow> Apply(IRow input, IUpdatableRow output);
}

Per definire un oggetto di applicazione definito dall'utente, è necessario creare l'interfaccia IApplier con l'attributo [SqlUserDefinedApplier], che per la definizione di un oggetto di applicazione definito dall'utente è facoltativo.To define a user-defined applier, we need to create the IApplier interface with the [SqlUserDefinedApplier] attribute, which is optional for a user-defined applier definition.

[SqlUserDefinedApplier]
public class ParserApplier : IApplier
{
    public ParserApplier()
    {
        …
    }

    public override IEnumerable<IRow> Apply(IRow input, IUpdatableRow output)
    {
        …
    }
}
  • Apply viene chiamato per ogni riga della tabella esterna.Apply is called for each row of the outer table. Restituisce il set di righe di output di IUpdatableRow.It returns the IUpdatableRow output rowset.
  • La classe del costruttore viene usata per passare parametri all'oggetto di applicazione definito dall'utente.The Constructor class is used to pass parameters to the user-defined applier.

SqlUserDefinedApplier indica che il tipo deve essere registrato come oggetto di applicazione definito dall'utente.SqlUserDefinedApplier indicates that the type should be registered as a user-defined applier. Questa classe non può essere ereditata.This class cannot be inherited.

SqlUserDefinedApplier è facoltativo per la definizione di un oggetto di applicazione definito dall'utente.SqlUserDefinedApplier is optional for a user-defined applier definition.

I principali oggetti di programmabilità sono i seguenti:The main programmability objects are as follows:

public override IEnumerable<IRow> Apply(IRow input, IUpdatableRow output)

I set di righe di input vengono passati come input IRow.Input rowsets are passed as IRow input. Le righe di output vengono generate come interfaccia di output IUpdatableRow.The output rows are generated as IUpdatableRow output interface.

I nomi delle singole colonne possono essere determinati chiamando il metodo dello schema IRow.Individual column names can be determined by calling the IRow Schema method.

ISchema schema = row.Schema;
var col = schema[i];
string val = row.Get<string>(col.Name)

Per ottenere i valori di dati effettivi da IRow in ingresso, si usa il metodo Get() dell'interfaccia IRow.To get the actual data values from the incoming IRow, we use the Get() method of IRow interface.

mycolumn = row.Get<int>("mycolumn")

In alternativa, si usa il nome di colonna dello schema:Or we use the schema column name:

row.Get<int>(row.Schema[0].Name)

I valori di output devono essere impostati con l'output di IUpdatableRow:The output values must be set with IUpdatableRow output:

output.Set<int>("mycolumn", mycolumn)

È importante comprendere che gli oggetti di applicazione personalizzati restituiscono solo le colonne o i valori definiti con la chiamata al metodo output.Set.It is important to understand that custom appliers only output columns and values that are defined with output.Set method call.

L'output effettivo viene attivato chiamando yield return output.AsReadOnly();.The actual output is triggered by calling yield return output.AsReadOnly();.

I parametri dell'oggetto di applicazione definito dall'utente possono essere passati al costruttore.The user-defined applier parameters can be passed to the constructor. L'oggetto di applicazione può restituire un numero variabile di colonne da definire durante la chiamata all'oggetto di applicazione nello script U-SQL di base.Applier can return a variable number of columns that need to be defined during the applier call in base U-SQL Script.

new USQL_Programmability.ParserApplier ("all") AS properties(make string, model string, year string, type string, millage int);

Di seguito è riportato un esempio di oggetto di applicazione definito dall'utente:Here is the user-defined applier example:

[SqlUserDefinedApplier]
public class ParserApplier : IApplier
{
private string parsingPart;

public ParserApplier(string parsingPart)
{
    if (parsingPart.ToUpper().Contains("ALL")
    || parsingPart.ToUpper().Contains("MAKE")
    || parsingPart.ToUpper().Contains("MODEL")
    || parsingPart.ToUpper().Contains("YEAR")
    || parsingPart.ToUpper().Contains("TYPE")
    || parsingPart.ToUpper().Contains("MILLAGE")
    )
    {
    this.parsingPart = parsingPart;
    }
    else
    {
    throw new ArgumentException("Incorrect parameter. Please use: 'ALL[MAKE|MODEL|TYPE|MILLAGE]'");
    }
}

public override IEnumerable<IRow> Apply(IRow input, IUpdatableRow output)
{

    string[] properties = input.Get<string>("properties").Split(',');

    //  only process with correct number of properties
    if (properties.Count() == 5)
    {

    string make = properties[0];
    string model = properties[1];
    string year = properties[2];
    string type = properties[3];
    int millage = -1;

    // Only return millage if it is number, otherwise, -1
    if (!int.TryParse(properties[4], out millage))
    {
        millage = -1;
    }

    if (parsingPart.ToUpper().Contains("MAKE") || parsingPart.ToUpper().Contains("ALL")) output.Set<string>("make", make);
    if (parsingPart.ToUpper().Contains("MODEL") || parsingPart.ToUpper().Contains("ALL")) output.Set<string>("model", model);
    if (parsingPart.ToUpper().Contains("YEAR") || parsingPart.ToUpper().Contains("ALL")) output.Set<string>("year", year);
    if (parsingPart.ToUpper().Contains("TYPE") || parsingPart.ToUpper().Contains("ALL")) output.Set<string>("type", type);
    if (parsingPart.ToUpper().Contains("MILLAGE") || parsingPart.ToUpper().Contains("ALL")) output.Set<int>("millage", millage);
    }
    yield return output.AsReadOnly();            
}
}

Di seguito è riportato lo script U-SQL di base per questo oggetto di applicazione definito dall'utente:Following is the base U-SQL script for this user-defined applier:

DECLARE @input_file string = @"c:\usql-programmability\car_fleet.tsv";
DECLARE @output_file string = @"c:\usql-programmability\output_file.tsv";

@rs0 =
    EXTRACT
        stocknumber int,
        vin String,
        properties String
    FROM @input_file USING Extractors.Tsv();

@rs1 =
    SELECT
        r.stocknumber,
        r.vin,
        properties.make,
        properties.model,
        properties.year,
        properties.type,
        properties.millage
    FROM @rs0 AS r
    CROSS APPLY
    new USQL_Programmability.ParserApplier ("all") AS properties(make string, model string, year string, type string, millage int);

OUTPUT @rs1 TO @output_file USING Outputters.Text();

Nello scenario di questo caso d'uso, l'oggetto di applicazione definito dall'utente funge da parser di valori delimitati da virgole per le proprietà del parco auto.In this use case scenario, user-defined applier acts as a comma-delimited value parser for the car fleet properties. Le righe del file di input si presentano come segue:The input file rows look like the following:

103 Z1AB2CD123XY45889   Ford,Explorer,2005,SUV,152345
303 Y0AB2CD34XY458890   Shevrolet,Cruise,2010,4Dr,32455
210 X5AB2CD45XY458893   Nissan,Altima,2011,4Dr,74000

Si tratta di un normale file con valori delimitati da tabulazioni (TSV) con una colonna di proprietà contenente le proprietà delle automobili, come marca e modello.It is a typical tab-delimited TSV file with a properties column that contains car properties such as make and model. Tali proprietà devono essere analizzate rispetto alle colonne della tabella.Those properties must be parsed to the table columns. L'oggetto di applicazione specificato consente di generare un numero dinamico di proprietà nel set di righe, in base al parametro passato.The applier that's provided also enables you to generate a dynamic number of properties in the result rowset, based on the parameter that's passed. È possibile generare tutte le proprietà o solo uno specifico set di proprietà.You can generate either all properties or a specific set of properties only.

…USQL_Programmability.ParserApplier ("all")
…USQL_Programmability.ParserApplier ("make")
…USQL_Programmability.ParserApplier ("make&model")

L'oggetto di applicazione definito dall'utente può essere chiamato come nuova istanza dell'oggetto di applicazione:The user-defined applier can be called as a new instance of applier object:

CROSS APPLY new MyNameSpace.MyApplier (parameter: “value”) AS alias([columns types]…);

In alternativa, è possibile usare la chiamata a un metodo factory wrapper:Or with the invocation of a wrapper factory method:

    CROSS APPLY MyNameSpace.MyApplier (parameter: “value”) AS alias([columns types]…);

Usare combinatori definiti dall'utenteUse user-defined combiners

Un combinatore definito dall'utente (UDC) consente di combinare le righe dei set di sinistra e di destra, in base a logica personalizzata.User-defined combiner, or UDC, enables you to combine rows from left and right rowsets, based on custom logic. Il combinatore definito dall'utente viene usato con l'espressione COMBINE.User-defined combiner is used with COMBINE expression.

Un combinatore viene richiamato con l'espressione COMBINE, che specifica le informazioni necessarie su entrambi i set di righe di input, le colonne di raggruppamento e lo schema dei risultati previsto e informazioni aggiuntive.A combiner is being invoked with the COMBINE expression that provides the necessary information about both the input rowsets, the grouping columns, the expected result schema, and additional information.

Per chiamare un combinatore in uno script U-SQL di base, si usa la sintassi seguente:To call a combiner in a base U-SQL script, we use the following syntax:

Combine_Expression :=
    'COMBINE' Combine_Input
    'WITH' Combine_Input
    Join_On_Clause
    Produce_Clause
    [Readonly_Clause]
    [Required_Clause]
    USING_Clause.

Per altre informazioni, vedere COMBINE Expression (U-SQL) (Espressione COMBINE (U-SQL)).For more information, see COMBINE Expression (U-SQL).

Per definire un combinatore definito dall'utente, è necessario creare l'interfaccia ICombiner con l'attributo [SqlUserDefinedCombiner], che per la definizione di un combinatore definito dall'utente è facoltativo.To define a user-defined combiner, we need to create the ICombiner interface with the [SqlUserDefinedCombiner] attribute, which is optional for a user-defined Combiner definition.

Definizione della classe ICombiner di base:Base ICombiner class definition:

public abstract class ICombiner : IUserDefinedOperator
{
protected ICombiner();
public virtual IEnumerable<IRow> Combine(List<IRowset> inputs,
       IUpdatableRow output);
public abstract IEnumerable<IRow> Combine(IRowset left, IRowset right,
       IUpdatableRow output);
}

L'implementazione personalizzata di un'interfaccia ICombiner deve contenere una definizione per un override IEnumerable<IRow> Combine.The custom implementation of an ICombiner interface should contain the definition for an IEnumerable<IRow> Combine override.

[SqlUserDefinedCombiner]
public class MyCombiner : ICombiner
{

public override IEnumerable<IRow> Combine(IRowset left, IRowset right,
    IUpdatableRow output)
{
    …
}
}

L'attributo SqlUserDefinedCombiner indica che il tipo deve essere registrato come combinatore definito dall'utente.The SqlUserDefinedCombiner attribute indicates that the type should be registered as a user-defined combiner. Questa classe non può essere ereditata.This class cannot be inherited.

SqlUserDefinedCombiner viene usato per definire la proprietà della modalità di combinazione.SqlUserDefinedCombiner is used to define the Combiner mode property. È un attributo facoltativo per la definizione di un combinatore definito dall'utente.It is an optional attribute for a user-defined combiner definition.

Modalità CombinerModeCombinerMode Mode

L'enumerazione CombinerMode può accettare i valori seguenti:CombinerMode enum can take the following values:

  • Full (0): ogni riga di output dipende potenzialmente da tutte le righe di input di sinistra e di destra con lo stesso valore chiave.Full (0) Every output row potentially depends on all the input rows from left and right with the same key value.

  • Left (1): ogni riga di output dipende da una singola riga di input di sinistra e potenzialmente da tutte le righe di destra con lo stesso valore chiave.Left (1) Every output row depends on a single input row from the left (and potentially all rows from the right with the same key value).

  • Right (2): ogni riga di output dipende da una singola riga di input di destra e potenzialmente da tutte le righe di sinistra con lo stesso valore chiave.Right (2) Every output row depends on a single input row from the right (and potentially all rows from the left with the same key value).

  • Inner (3): ogni riga di output dipende da una singola riga di input di sinistra e di destra con lo stesso valore.Inner (3) Every output row depends on a single input row from left and right with the same value.

Esempio: [SqlUserDefinedCombiner(Mode=CombinerMode.Left)]Example: [SqlUserDefinedCombiner(Mode=CombinerMode.Left)]

I principali oggetti di programmabilità sono i seguenti:The main programmability objects are:

    public override IEnumerable<IRow> Combine(IRowset left, IRowset right,
        IUpdatableRow output

I set di righe di input vengono passati come tipo di interfaccia IRowset a sinistra e a destra.Input rowsets are passed as left and right IRowset type of interface. Entrambi i set di righe devono essere enumerati per l'elaborazione.Both rowsets must be enumerated for processing. È possibile enumerare ogni interfaccia una sola volta, quindi deve essere enumerata e memorizzata nella cache, se necessario.You can only enumerate each interface once, so we have to enumerate and cache it if necessary.

Per la memorizzazione nella cache, è possibile creare un tipo di struttura di memoria List<T> come risultato dell'esecuzione di una query LINQ, specificamente List<IRow>.For caching purposes, we can create a List<T> type of memory structure as a result of a LINQ query execution, specifically List<IRow>. Durante l'enumerazione è possibile usare anche il tipo di dati anonimo.The anonymous data type can be used during enumeration as well.

Per altre informazioni su tali query, vedere l'introduzione alle query LINQ (C#). Per altre informazioni sull'interfaccia IEnumerable<T>, vedere Interfaccia IEnumerable<T>.See Introduction to LINQ Queries (C#) for more information about LINQ queries, and IEnumerable<T> Interface for more information about IEnumerable<T> interface.

Per ottenere i valori di dati effettivi da IRowset in ingresso, si usa il metodo Get() dell'interfaccia IRow.To get the actual data values from the incoming IRowset, we use the Get() method of IRow interface.

mycolumn = row.Get<int>("mycolumn")

I nomi delle singole colonne possono essere determinati chiamando il metodo dello schema IRow.Individual column names can be determined by calling the IRow Schema method.

ISchema schema = row.Schema;
var col = schema[i];
string val = row.Get<string>(col.Name)

In alternativa, è possibile usare il nome di colonna dello schema:Or by using the schema column name:

c# row.Get<int>(row.Schema[0].Name)

L'enumerazione generale con LINQ si presenta come segue:The general enumeration with LINQ looks like the following:

var myRowset =
            (from row in left.Rows
                          select new
                          {
                              Mycolumn = row.Get<int>("mycolumn"),
                          }).ToList();

Al termine dell'enumerazione di entrambi i set di righe, si scorreranno in ciclo tutte le righe.After enumerating both rowsets, we are going to loop through all rows. Per ogni riga del set di sinistra si troveranno tutte le righe che soddisfano la condizione del combinatore.For each row in the left rowset, we are going to find all rows that satisfy the condition of our combiner.

I valori di output devono essere impostati con l'output di IUpdatableRow.The output values must be set with IUpdatableRow output.

output.Set<int>("mycolumn", mycolumn)

L'output effettivo viene attivato chiamando yield return output.AsReadOnly();.The actual output is triggered by calling to yield return output.AsReadOnly();.

Di seguito è riportato un esempio di combinatore:Following is a combiner example:

[SqlUserDefinedCombiner]
public class CombineSales : ICombiner
{

public override IEnumerable<IRow> Combine(IRowset left, IRowset right,
    IUpdatableRow output)
{
    var internetSales =
    (from row in left.Rows
          select new
          {
              ProductKey = row.Get<int>("ProductKey"),
              OrderDateKey = row.Get<int>("OrderDateKey"),
              SalesAmount = row.Get<decimal>("SalesAmount"),
              TaxAmt = row.Get<decimal>("TaxAmt")
          }).ToList();

    var resellerSales =
    (from row in right.Rows
     select new
     {
     ProductKey = row.Get<int>("ProductKey"),
     OrderDateKey = row.Get<int>("OrderDateKey"),
     SalesAmount = row.Get<decimal>("SalesAmount"),
     TaxAmt = row.Get<decimal>("TaxAmt")
     }).ToList();

    foreach (var row_i in internetSales)
    {
    foreach (var row_r in resellerSales)
    {

        if (
        row_i.OrderDateKey > 0
        && row_i.OrderDateKey < row_r.OrderDateKey
        && row_i.OrderDateKey == 20010701
        && (row_r.SalesAmount + row_r.TaxAmt) > 20000)
        {
        output.Set<int>("OrderDateKey", row_i.OrderDateKey);
        output.Set<int>("ProductKey", row_i.ProductKey);
        output.Set<decimal>("Internet_Sales_Amount", row_i.SalesAmount + row_i.TaxAmt);
        output.Set<decimal>("Reseller_Sales_Amount", row_r.SalesAmount + row_r.TaxAmt);
        }

    }
    }
    yield return output.AsReadOnly();
}
}

Nello scenario di questo caso d'uso, si crea un report analitico per il rivenditore.In this use-case scenario, we are building an analytics report for the retailer. L'obiettivo è trovare tutti i prodotti che costano più di 20.000 dollari e che in un dato intervallo di tempo vengono venduti più velocemente tramite il sito Web che non tramite il normale rivenditore.The goal is to find all products that cost more than $20,000 and that sell through the website faster than through the regular retailer within a certain time frame.

Di seguito è riportato lo script U-SQL di base,Here is the base U-SQL script. in cui è possibile confrontare la logica di un JOIN regolare e di un combinatore:You can compare the logic between a regular JOIN and a combiner:

DECLARE @LocalURI string = @"\usql-programmability\";

DECLARE @input_file_internet_sales string = @LocalURI+"FactInternetSales.txt";
DECLARE @input_file_reseller_sales string = @LocalURI+"FactResellerSales.txt";
DECLARE @output_file1 string = @LocalURI+"output_file1.tsv";
DECLARE @output_file2 string = @LocalURI+"output_file2.tsv";

@fact_internet_sales =
EXTRACT
    ProductKey int ,
    OrderDateKey int ,
    DueDateKey int ,
    ShipDateKey int ,
    CustomerKey int ,
    PromotionKey int ,
    CurrencyKey int ,
    SalesTerritoryKey int ,
    SalesOrderNumber String ,
    SalesOrderLineNumber  int ,
    RevisionNumber int ,
    OrderQuantity int ,
    UnitPrice decimal ,
    ExtendedAmount decimal,
    UnitPriceDiscountPct float ,
    DiscountAmount float ,
    ProductStandardCost decimal ,
    TotalProductCost decimal ,
    SalesAmount decimal ,
    TaxAmt decimal ,
    Freight decimal ,
    CarrierTrackingNumber String,
    CustomerPONumber String
FROM @input_file_internet_sales
USING Extractors.Text(delimiter:'|', encoding: Encoding.Unicode);

@fact_reseller_sales =
EXTRACT
    ProductKey int ,
    OrderDateKey int ,
    DueDateKey int ,
    ShipDateKey int ,
    ResellerKey int ,
    EmployeeKey int ,
    PromotionKey int ,
    CurrencyKey int ,
    SalesTerritoryKey int ,
    SalesOrderNumber String ,
    SalesOrderLineNumber  int ,
    RevisionNumber int ,
    OrderQuantity int ,
    UnitPrice decimal ,
    ExtendedAmount decimal,
    UnitPriceDiscountPct float ,
    DiscountAmount float ,
    ProductStandardCost decimal ,
    TotalProductCost decimal ,
    SalesAmount decimal ,
    TaxAmt decimal ,
    Freight decimal ,
    CarrierTrackingNumber String,
    CustomerPONumber String
FROM @input_file_reseller_sales
USING Extractors.Text(delimiter:'|', encoding: Encoding.Unicode);

@rs1 =
SELECT
    fis.OrderDateKey,
    fis.ProductKey,
    fis.SalesAmount+fis.TaxAmt AS Internet_Sales_Amount,
    frs.SalesAmount+frs.TaxAmt AS Reseller_Sales_Amount
FROM @fact_internet_sales AS fis
     INNER JOIN @fact_reseller_sales AS frs
     ON fis.ProductKey == frs.ProductKey
WHERE
    fis.OrderDateKey < frs.OrderDateKey
    AND fis.OrderDateKey == 20010701
    AND frs.SalesAmount+frs.TaxAmt > 20000;

@rs2 =
COMBINE @fact_internet_sales AS fis
WITH @fact_reseller_sales AS frs
ON fis.ProductKey == frs.ProductKey
PRODUCE OrderDateKey int,
        ProductKey int,
        Internet_Sales_Amount decimal,
        Reseller_Sales_Amount decimal
USING new USQL_Programmability.CombineSales();

OUTPUT @rs1 TO @output_file1 USING Outputters.Tsv();
OUTPUT @rs2 TO @output_file2 USING Outputters.Tsv();

Un combinatore definito dall'utente può essere chiamato come nuova istanza dell'oggetto di applicazione:A user-defined combiner can be called as a new instance of the applier object:

USING new MyNameSpace.MyCombiner();

In alternativa, è possibile usare la chiamata a un metodo factory wrapper:Or with the invocation of a wrapper factory method:

USING MyNameSpace.MyCombiner();

Usare riduttori definiti dall'utenteUse user-defined reducers

U-SQL consente di scrivere riduttori di set di righe personalizzati in C# usando il framework di estendibilità degli operatori definito dall'utente e implementando un'interfaccia IReducer.U-SQL enables you to write custom rowset reducers in C# by using the user-defined operator extensibility framework and implementing an IReducer interface.

Un riduttore definito dall'utente (UDR) può essere usato per eliminare le righe non necessarie durante l'estrazione (importazione) di dati,User-defined reducer, or UDR, can be used to eliminate unnecessary rows during data extraction (import). nonché per modificare e valutare righe e colonne.It also can be used to manipulate and evaluate rows and columns. In base alla logica di programmabilità, può anche definire le righe da estrarre.Based on programmability logic, it can also define which rows need to be extracted.

Per definire una classe di riduttori definiti dall'utente, è necessario creare un'interfaccia IReducer con l'attributo SqlUserDefinedReducer facoltativo.To define a UDR class, we need to create an IReducer interface with an optional SqlUserDefinedReducer attribute.

Questa interfaccia di classe deve contenere una definizione per l'override dei set di righe dell'interfaccia IEnumerable.This class interface should contain a definition for the IEnumerable interface rowset override.

[SqlUserDefinedReducer]
public class EmptyUserReducer : IReducer
{

    public override IEnumerable<IRow> Reduce(IRowset input, IUpdatableRow output)
    {
        …
    }

}

L'attributo SqlUserDefinedReducer indica che il tipo deve essere registrato come riduttore definito dall'utente.The SqlUserDefinedReducer attribute indicates that the type should be registered as a user-defined reducer. Questa classe non può essere ereditata.This class cannot be inherited. SqlUserDefinedReducer è un attributo facoltativo per la definizione di un riduttore definito dall'utente.SqlUserDefinedReducer is an optional attribute for a user-defined reducer definition. Viene usato per definire la proprietà IsRecursive.It's used to define IsRecursive property.

  • bool IsRecursivebool IsRecursive
  • true = indica se questo Reducer è associativo e commutativotrue = Indicates whether this Reducer is associative and commutative

I principali oggetti di programmabilità sono input e output.The main programmability objects are input and output. L'oggetto di input viene usato per enumerare le righe di input.The input object is used to enumerate input rows. L'output viene usato per impostare le righe di output come risultato dell'attività di riduzione.Output is used to set output rows as a result of reducing activity.

Per l'enumerazione delle righe di input, si usa il metodo Row.Get.For input rows enumeration, we use the Row.Get method.

foreach (IRow row in input.Rows)
{
    row.Get<string>("mycolumn");
}

Il parametro per il metodo Row.Get è una colonna passata come parte della classe PRODUCE dell'istruzione REDUCE dello script U-SQL di base.The parameter for the Row.Get method is a column that's passed as part of the PRODUCE class of the REDUCE statement of the U-SQL base script. Anche qui occorre usare il tipo di dati corretto.We need to use the correct data type here as well.

Per l'output, usare il metodo output.Set.For output, use the output.Set method.

È importante comprendere che il riduttore personalizzato restituisce solo i valori definiti con la chiamata al metodo output.Set.It is important to understand that custom reducer only outputs values that are defined with the output.Set method call.

output.Set<string>("mycolumn", guid);

L'output effettivo del riduttore viene attivato chiamando yield return output.AsReadOnly();.The actual reducer output is triggered by calling yield return output.AsReadOnly();.

Di seguito è riportato un esempio di riduttore:Following is a reducer example:

[SqlUserDefinedReducer]
public class EmptyUserReducer : IReducer
{

    public override IEnumerable<IRow> Reduce(IRowset input, IUpdatableRow output)
    {
    string guid;
    DateTime dt;
    string user;
    string des;

    foreach (IRow row in input.Rows)
    {
        guid = row.Get<string>("guid");
        dt = row.Get<DateTime>("dt");
        user = row.Get<string>("user");
        des = row.Get<string>("des");

        if (user.Length > 0)
        {
        output.Set<string>("guid", guid);
        output.Set<DateTime>("dt", dt);
        output.Set<string>("user", user);
        output.Set<string>("des", des);

        yield return output.AsReadOnly();
        }
    }
    }

}

Nello scenario di questo caso d'uso, il riduttore ignora le righe con nome utente vuoto.In this use-case scenario, the reducer is skipping rows with an empty user name. Per ogni riga nel set, legge ogni colonna obbligatoria e quindi valuta la lunghezza del nome utente.For each row in rowset, it reads each required column, then evaluates the length of the user name. Restituisce la riga effettiva solo se il valore del nome utente ha una lunghezza superiore a 0.It outputs the actual row only if user name value length is more than 0.

Di seguito è riportato uno script U-SQL di base che usa un riduttore personalizzato:Following is base U-SQL script that uses a custom reducer:

DECLARE @input_file string = @"\usql-programmability\input_file_reducer.tsv";
DECLARE @output_file string = @"\usql-programmability\output_file.tsv";

@rs0 =
    EXTRACT
            guid string,
        dt DateTime,
            user String,
            des String
    FROM @input_file 
    USING Extractors.Tsv();

@rs1 =
    REDUCE @rs0 PRESORT guid
    ON guid  
    PRODUCE guid string, dt DateTime, user String, des String
    USING new USQL_Programmability.EmptyUserReducer();

@rs2 =
    SELECT guid AS start_id,
           dt AS start_time,
           DateTime.Now.ToString("M/d/yyyy") AS Nowdate,
           USQL_Programmability.CustomFunctions.GetFiscalPeriodWithCustomType(dt).ToString() AS start_fiscalperiod,
           user,
           des
    FROM @rs1;

OUTPUT @rs2 
    TO @output_file 
    USING Outputters.Text();