U-SQL-programmeringsguide – UDT och UDAGG

Använda användardefinierade typer: UDT

Användardefinierade typer, eller UDT, är en annan programmeringsfunktion i U-SQL. U-SQL UDT fungerar som en vanlig användardefinierad C#-typ. C# är ett starkt skrivet språk som tillåter användning av inbyggda och anpassade användardefinierade typer.

U-SQL kan inte implicit serialisera eller deserialisera godtyckliga UDT när UDT skickas mellan hörn i raduppsättningar. Det innebär att användaren måste ange en explicit formatering med hjälp av IFormatter-gränssnittet. Detta ger U-SQL serialisera och deserialisera metoder för UDT.

Anteckning

U-SQL:s inbyggda extraktorer och utdata kan för närvarande inte serialisera eller deserialisera UDT-data till eller från filer även med IFormatter-uppsättningen. Så när du skriver UDT-data till en fil med output-instruktionen, eller läser den med en extraktor, måste du skicka dem som en sträng- eller bytematris. Sedan anropar du serialiserings- och deserialiseringskoden (dvs. UDT:s ToString()-metod) explicit. Användardefinierade extraktorer och utdata, å andra sidan, kan läsa och skriva UDT.

Om vi försöker använda UDT i EXTRACTOR eller OUTPUTTER (från tidigare SELECT), som du ser här:

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

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

Vi får följande fel:

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

För att arbeta med UDT i utdata måste vi antingen serialisera den till sträng med metoden ToString() eller skapa en anpassad utdata.

UDT:erna kan för närvarande inte användas i GROUP BY. Om UDT används i GROUP BY utlöses följande fel:

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

För att definiera en UDT måste vi:

  1. Lägg till följande namnområden:
using Microsoft.Analytics.Interfaces
using System.IO;
  1. Lägg till Microsoft.Analytics.Interfaces, som krävs för UDT-gränssnitten. Dessutom System.IO kan behövas för att definiera IFormatter-gränssnittet.

  2. Definiera en använd definierad typ med attributet SqlUserDefinedType.

SqlUserDefinedType används för att markera en typdefinition i en sammansättning som en användardefinierad typ (UDT) i U-SQL. Egenskaperna för attributet återspeglar UDT:s fysiska egenskaper. Det går inte att ärva den här klassen.

SqlUserDefinedType är ett obligatoriskt attribut för UDT-definition.

Konstruktorn för klassen:

  • SqlUserDefinedTypeAttribute (typformaterare)

  • Typformater: Obligatorisk parameter för att definiera en UDT-formatering – specifikt måste typen av IFormatter gränssnittet skickas hit.

[SqlUserDefinedType(typeof(MyTypeFormatter))]
public class MyType
{ … }
  • Typisk UDT kräver också definition av IFormatter-gränssnittet, som du ser i följande exempel:
public class MyTypeFormatter : IFormatter<MyType>
{
    public void Serialize(MyType instance, IColumnWriter writer, ISerializationContext context)
    { … }

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

Gränssnittet IFormatter serialiserar och deserialiserar ett objektdiagram med rottypen <typeparamref name="T">.

<typeparam name="T">Rottypen för objektdiagrammet som ska serialiseras och deserialiseras.

  • Deserialisera: Av-serialiserar data på den angivna strömmen och återskapar grafen över objekt.

  • Serialisera: Serialiserar ett objekt eller ett diagram över objekt med den angivna roten till den angivna dataströmmen.

MyType instans: Instans av typen. IColumnWriter writer/ IColumnReader reader: Den underliggande kolumnströmmen. ISerializationContext kontext: Uppräkning som definierar en uppsättning flaggor som anger käll- eller målkontexten för strömmen under serialiseringen.

  • Mellanliggande: Anger att käll- eller målkontexten inte är ett sparat arkiv.

  • Beständighet: Anger att käll- eller målkontexten är ett sparat arkiv.

Som en vanlig C#-typ kan en U-SQL UDT-definition innehålla åsidosättningar för operatorer som +/==/!=. Den kan också innehålla statiska metoder. Om vi till exempel ska använda den här UDT:en som en parameter till en U-SQL MIN-mängdfunktion måste vi definiera < åsidosättning av operatorn.

Tidigare i den här guiden demonstrerade vi ett exempel på identifiering av räkenskapsperiod från det specifika datumet i formatet Qn:Pn (Q1:P10). I följande exempel visas hur du definierar en anpassad typ för räkenskapsperiodvärden.

Följande är ett exempel på ett kod bakom avsnitt med anpassat UDT- och IFormatter-gränssnitt:

[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;
        }
    }
}

Den definierade typen innehåller två tal: kvartal och månad. Operatorer ==/!=/>/< och statisk metod ToString() definieras här.

Som tidigare nämnts kan UDT användas i SELECT-uttryck, men kan inte användas i OUTPUTTER/EXTRACTOR utan anpassad serialisering. Den måste antingen serialiseras som en sträng med ToString() eller användas med en anpassad OUTPUTTER/EXTRACTOR.

Nu ska vi diskutera användningen av UDT. I ett kod bakom-avsnitt ändrade vi funktionen GetFiscalPeriod till följande:

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);
}

Som du ser returneras värdet för vår FiscalPeriod-typ.

Här ger vi ett exempel på hur du använder det ytterligare i U-SQL-basskriptet. Det här exemplet visar olika former av UDT-anrop från U-SQL-skript.

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();

Här är ett exempel på ett fullständigt kod bakom avsnitt:

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;
                }
            }
        }
    }
}

Använda användardefinierade aggregat: UDAGG

Användardefinierade aggregeringar är alla aggregeringsrelaterade funktioner som inte levereras direkt med U-SQL. Exemplet kan vara en aggregering för att utföra anpassade matematiska beräkningar, strängsammanfogningar, manipuleringar med strängar och så vidare.

Den användardefinierade aggregerade basklassdefinitionen är följande:

    [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 anger att typen ska registreras som en användardefinierad aggregering. Det går inte att ärva den här klassen.

SqlUserDefinedType-attributet är valfritt för UDAGG-definition.

Med basklassen kan du skicka tre abstrakta parametrar: två som indataparametrar och en som resultat. Datatyperna är variabel och bör definieras under klassarv.

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 anropar en gång för varje grupp under beräkningen. Den tillhandahåller en initieringsrutin för varje aggregeringsgrupp.
  • Ackumulera körs en gång för varje värde. Den tillhandahåller huvudfunktionerna för aggregeringsalgoritmen. Den kan användas för att aggregera värden med olika datatyper som definieras under klassarv. Den kan acceptera två parametrar för variabeldatatyper.
  • Avsluta körs en gång per aggregeringsgrupp i slutet av bearbetningen för att mata ut resultatet för varje grupp.

Om du vill deklarera rätt typer av indata och utdata använder du klassdefinitionen på följande sätt:

public abstract class IAggregate<T1, T2, TResult> : IAggregate
  • T1: Den första parametern som ska ackumuleras
  • T2: Den andra parametern som ackumuleras
  • TResult: Returtyp för avslutning

Exempel:

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

eller

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

Använda UDAGG i U-SQL

Om du vill använda UDAGG måste du först definiera det i kod bakom eller referera till det från den befintliga programmerings-DLL:en enligt beskrivningen tidigare.

Använd sedan följande syntax:

AGG<UDAGG_functionname>(param1,param2)

Här är ett exempel på 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;
    }

}

Och grundläggande U-SQL-skript:

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();

I det här användningsscenariot sammanfogar vi klass-GUID:erna för de specifika användarna.

Nästa steg