Compartir a través de


Guía de programación de U-SQL: UDT y UDAGG

Usar tipos definidos por el usuario: UDT

Los tipos definidos por el usuario o UDT son otra característica de programación de U-SQL. U-SQL UDT actúa como un tipo normal definido por el usuario de C#. C# es un lenguaje fuertemente tipado que permite el uso de tipos integrados y personalizados definidos por el usuario.

U-SQL no puede serializar ni deserializar implícitamente UDT arbitrarios cuando el UDT se pasa en conjuntos de datos entre vértices. Esto significa que el usuario tiene que proporcionar un formateador explícito mediante la interfaz IFormatter. Esto proporciona a 'U-SQL' los métodos de serialización y deserialización para el 'UDT'.

Nota:

Actualmente, los extractores y outputters integrados de U-SQL no pueden serializar ni deserializar datos UDT en o desde archivos incluso con el conjunto IFormatter. Por lo tanto, al escribir datos UDT en un archivo con la instrucción OUTPUT o leerlos con un extractor, debe pasarlos como una cadena o matriz de bytes. A continuación, llame explícitamente al código de serialización y deserialización (es decir, al método ToString() de UDT. Por otro lado, los extractores y generadores de salida definidos por el usuario pueden leer y escribir UDT.

Si intentamos usar UDT en EXTRACTOR o OUTPUTTER (fuera del SELECT ANTERIOR), como se muestra aquí:

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

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

Recibimos el siguiente 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

Para trabajar con UDT en outputter, tenemos que serializarlo en una cadena con el método ToString() o crear un outputter personalizado.

Actualmente, los UDT no se pueden usar en GROUP BY. Si UDT se usa en GROUP BY, se produce el siguiente error:

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 un UDT, debemos:

  1. Agregue los siguientes espacios de nombres:
using Microsoft.Analytics.Interfaces
using System.IO;
  1. Agregue Microsoft.Analytics.Interfaces, que es necesario para las interfaces UDT. Además, es posible que se necesite System.IO para definir la interfaz IFormatter.

  2. Defina un tipo definido por el uso con el atributo SqlUserDefinedType.

sqlUserDefinedType se usa para marcar una definición de tipo en un ensamblado como un tipo definido por el usuario (UDT) en U-SQL. Las propiedades del atributo reflejan las características físicas del UDT. Esta clase no se puede heredar.

SqlUserDefinedType es un atributo necesario para la definición de UDT.

Constructor de la clase :

  • SqlUserDefinedTypeAttribute (formateador de tipo)

  • Formateador de tipos: parámetro necesario para definir un formateador UDT, específicamente, el tipo de la interfaz de IFormatter debe pasarse aquí.

[SqlUserDefinedType(typeof(MyTypeFormatter))]
public class MyType
{ … }
  • El UDT típico también requiere la definición de la interfaz IFormatter, como se muestra en el ejemplo siguiente:
public class MyTypeFormatter : IFormatter<MyType>
{
    public void Serialize(MyType instance, IColumnWriter writer, ISerializationContext context)
    { … }

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

La interfaz IFormatter serializa y deserializa un gráfico de objetos con el tipo raíz de <typeparamref name="T">.

<typeparam name="T">El tipo raíz del gráfico de objetos para serializar y deserializar.

  • Deserializar: des serializa los datos en la secuencia proporcionada y reconstituye el gráfico de objetos.

  • Serializar: serializa un objeto o un grafo de objetos, con la raíz dada al flujo proporcionado.

MyType instancia: instancia del tipo . IColumnWriter escritor/lector de IColumnReader: flujo de columna subyacente. ISerializationContext contexto: enumeración que define un conjunto de marcas que especifica el contexto de origen o destino de la secuencia durante la serialización.

  • intermedio: especifica que el contexto de origen o destino no es un almacén persistente.

  • Persistencia: Especifica que el contexto de origen o destino es un almacén persistente.

Como un tipo regular de C#, una definición de UDT en U-SQL puede incluir sobrescrituras para operadores como +/==/!=. También puede incluir métodos estáticos. Por ejemplo, si vamos a usar este UDT como parámetro para una función de agregado MIN de U-SQL, tenemos que definir la sobrecarga del operador <.

Anteriormente en esta guía, se mostró un ejemplo para la identificación del período fiscal a partir de la fecha específica en el formato Qn:Pn (Q1:P10). En el ejemplo siguiente se muestra cómo definir un tipo personalizado para los valores de período fiscal.

A continuación se muestra un ejemplo de una sección de código subyacente con la interfaz 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;
        }
    }
}

El tipo definido incluye dos números: trimestre y mes. Los operadores ==/!=/>/< y el método estático ToString() se definen aquí.

Como se mencionó anteriormente, UDT se puede usar en expresiones SELECT, pero no se puede usar en OUTPUTTER/EXTRACTOR sin serialización personalizada. Debe serializarse como una cadena con ToString() o usarse con un OUTPUTTER/EXTRACTOR personalizado.

Ahora vamos a analizar el uso de UDT. En una sección de código subyacente, cambiamos la función GetFiscalPeriod a lo siguiente:

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 puede ver, devuelve el valor de nuestro tipo FiscalPeriod.

Aquí se proporciona un ejemplo de cómo usarlo más en el script base de U-SQL. En este ejemplo se muestran diferentes formas de invocación UDT del 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();

Este es un ejemplo de una sección completa de código subyacente:

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

Uso de agregados definidos por el usuario: UDAGG

Los agregados definidos por el usuario son funciones relacionadas con la agregación que no se incluyen de fábrica con U-SQL. El ejemplo puede ser un agregado para realizar cálculos matemáticos personalizados, concatenaciones de cadenas, manipulaciones con cadenas, etc.

La definición de clase base de agregado definida por el usuario es la siguiente:

    [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 el tipo debe registrarse como agregado definido por el usuario. Esta clase no se puede heredar.

El atributo SqlUserDefinedType es opcional para la definición de UDAGG.

La clase base permite pasar tres parámetros abstractos: dos como parámetros de entrada y uno como resultado. Los tipos de datos son variables y deben definirse durante la herencia de clase.

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 invoca una vez para cada grupo durante el cálculo. Proporciona una rutina de inicialización para cada grupo de agregación.
  • acumula se ejecuta una vez para cada valor. Proporciona la funcionalidad principal para el algoritmo de agregación. Se puede usar para agregar valores con varios tipos de datos definidos durante la herencia de clases. Puede aceptar dos parámetros de tipos de datos variables.
  • Finalizar se ejecuta una vez por grupo de agregación al final del procesamiento para generar el resultado de cada grupo.

Para declarar los tipos de datos de entrada y salida correctos, use la definición de clase como se indica a continuación:

public abstract class IAggregate<T1, T2, TResult> : IAggregate
  • T1: Primer parámetro que se va a acumular
  • T2: segundo parámetro que se va a acumular
  • TResult: tipo de valor devuelto de terminate

Por ejemplo:

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

o

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

Uso de UDAGG en U-SQL

Para usar UDAGG, primero defínalo en código detrás o haga referencia a él desde la DLL de programabilidad existente, como se explicó anteriormente.

A continuación, use la sintaxis siguiente:

AGG<UDAGG_functionname>(param1,param2)

Este es un ejemplo 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;
    }

}

Y el script base de U-SQL:

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

En este escenario de uso, concatenamos los GUID de clase para los usuarios específicos.

Pasos siguientes