Partilhar via


Guia de programação do U-SQL – UDT e UDAGG

Utilizar tipos definidos pelo utilizador: UDT

Os tipos definidos pelo utilizador, ou UDT, são outra funcionalidade de programação do U-SQL. A UDT do U-SQL age como um tipo normal definido pelo utilizador em C#. O C# é uma linguagem fortemente digitada que permite a utilização de tipos incorporados e personalizados definidos pelo utilizador.

O U-SQL não consegue serializar ou anular implicitamente a serialização de UDTs arbitrários quando o UDT é transmitido entre vértices em conjuntos de linhas. Isto significa que o utilizador tem de fornecer um formatação explícito através da interface IFormatter. Isto fornece U-SQL com os métodos serializar e anular a serialização da UDT.

Nota

Atualmente, os extratores e saídas incorporados do U-SQL não podem serializar ou anular a serialização de dados UDT de/para ficheiros mesmo com o conjunto IFormatter. Por isso, quando estiver a escrever dados UDT num ficheiro com a instrução OUTPUT ou a lê-lo com um extrator, tem de os transmitir como uma cadeia ou matriz de bytes. Em seguida, chama explicitamente o código de serialização e desserialização (ou seja, o método ToString() da UDT. Por outro lado, os extratores e saídas definidos pelo utilizador podem ler e escrever UDTs.

Se tentarmos utilizar o UDT em EXTRACTOR ou OUTPUTTER (fora do SELECT anterior), conforme mostrado aqui:

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

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

Recebemos o seguinte erro:

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

Para trabalhar com o UDT no outputter, temos de serializá-lo para cadeia com o método ToString() ou criar um outputter personalizado.

Atualmente, os UDTs não podem ser utilizados em GROUP BY. Se a UDT for utilizada em GROUP BY, é gerado o seguinte erro:

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

Para definir uma UDT, temos de:

  1. Adicione os seguintes espaços de nomes:
using Microsoft.Analytics.Interfaces
using System.IO;
  1. Adicione Microsoft.Analytics.Interfaces, que é necessário para as interfaces UDT. Além disso, System.IO poderá ser necessário definir a interface IFormatter.

  2. Defina um tipo definido como utilizado com o atributo SqlUserDefinedType.

SqlUserDefinedType é utilizado para marcar uma definição de tipo numa assemblagem como um tipo definido pelo utilizador (UDT) no U-SQL. As propriedades no atributo refletem as características físicas da UDT. Esta classe não pode ser herdada.

SqlUserDefinedType é um atributo necessário para a definição de UDT.

O construtor da classe :

  • SqlUserDefinedTypeAttribute (formador de tipo)

  • Formatação de tipo: parâmetro necessário para definir um formatter UDT - especificamente, o tipo da interface tem de IFormatter ser transmitido aqui.

[SqlUserDefinedType(typeof(MyTypeFormatter))]
public class MyType
{ … }
  • A UDT típica também requer a definição da interface IFormatter, conforme mostrado no exemplo seguinte:
public class MyTypeFormatter : IFormatter<MyType>
{
    public void Serialize(MyType instance, IColumnWriter writer, ISerializationContext context)
    { … }

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

A IFormatter interface serializa e anula a serialização de um grafo de objeto com o tipo de raiz typeparamref <name="T">.

<typeparam name="T">O tipo de raiz para o grafo de objeto serializar e anular a serialização.

  • Anular a serialização: des serializa os dados no fluxo fornecido e reconstitui o gráfico de objetos.

  • Serializar: serializa um objeto, ou gráfico de objetos, com a raiz especificada para o fluxo fornecido.

MyType instância: instância do tipo. IColumnWriter escritor/ IColumnReader leitor: o fluxo de colunas subjacente. ISerializationContext contexto: enumeração que define um conjunto de sinalizadores que especifica o contexto de origem ou destino do fluxo durante a serialização.

  • Intermédio: especifica que o contexto de origem ou destino não é um arquivo persistente.

  • Persistência: especifica que o contexto de origem ou destino é um arquivo persistente.

Como um tipo de C# normal, uma definição U-SQL UDT pode incluir substituições para operadores como +/==/!=. Também pode incluir métodos estáticos. Por exemplo, se vamos utilizar esta UDT como um parâmetro para uma função de agregação U-SQL MIN, temos de definir < a substituição do operador.

Anteriormente neste guia, demonstrámos um exemplo de identificação do período fiscal a partir da data específica no formato Qn:Pn (Q1:P10). O exemplo seguinte mostra como definir um tipo personalizado para valores de período fiscal.

Segue-se um exemplo de uma secção protegida por código com interface UDT e IFormatter personalizada:

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

O tipo definido inclui dois números: trimestre e mês. Os operadores ==/!=/>/< e o método ToString() estático são definidos aqui.

Conforme mencionado anteriormente, o UDT pode ser utilizado em expressões SELECT, mas não pode ser utilizado em OUTPUTTER/EXTRACTOR sem serialização personalizada. Tem de ser serializado como uma cadeia com ToString() ou utilizado com um OUTPUTTER/EXTRACTOR personalizado.

Agora, vamos abordar a utilização da UDT. Numa secção de código pendente, alterámos a nossa função GetFiscalPeriod para o seguinte:

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

Como pode ver, devolve o valor do nosso tipo FiscalPeriod.

Aqui, fornecemos um exemplo de como utilizá-lo ainda mais no script base U-SQL. Este exemplo demonstra diferentes formas de invocação UDT do script U-SQL.

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

Eis um exemplo de uma secção completa de código atrás:

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

Utilizar agregações definidas pelo utilizador: UDAGG

As agregações definidas pelo utilizador são quaisquer funções relacionadas com a agregação que não são enviadas fora da caixa com U-SQL. O exemplo pode ser uma agregação para realizar cálculos matemáticos personalizados, concatenações de cadeias, manipulações com cadeias, etc.

A definição de classe base de agregação definida pelo utilizador é a seguinte:

    [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 que o tipo deve ser registado como um agregado definido pelo utilizador. Esta classe não pode ser herdada.

O atributo SqlUserDefinedType é opcional para a definição UDAGG.

A classe base permite-lhe transmitir três parâmetros abstratos: dois como parâmetros de entrada e um como resultado. Os tipos de dados são variáveis e devem ser definidos durante a herança de classes.

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()
    { … }
}
  • O Init invoca uma vez para cada grupo durante a computação. Fornece uma rotina de inicialização para cada grupo de agregação.
  • A acumulação é executada uma vez para cada valor. Fornece a funcionalidade principal para o algoritmo de agregação. Pode ser utilizado para agregar valores com vários tipos de dados que são definidos durante a herança de classes. Pode aceitar dois parâmetros de tipos de dados variáveis.
  • Terminar é executado uma vez por grupo de agregação no final do processamento para produzir o resultado de cada grupo.

Para declarar os tipos de dados de entrada e saída corretos, utilize a definição de classe da seguinte forma:

public abstract class IAggregate<T1, T2, TResult> : IAggregate
  • T1: Primeiro parâmetro a acumular
  • T2: Segundo parâmetro a acumular
  • TResult: Tipo de devolução de terminar

Por exemplo:

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

ou

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

Utilizar o UDAGG no U-SQL

Para utilizar o UDAGG, primeiro defina-o em code-behind ou referencie-o a partir da DLL de programação existente, conforme abordado anteriormente.

Em seguida, utilize a seguinte sintaxe:

AGG<UDAGG_functionname>(param1,param2)

Eis um exemplo de 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;
    }

}

E script U-SQL base:

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

Neste cenário de caso de utilização, concatenamos GUIDs de classe para os utilizadores específicos.

Passos seguintes