Crea una función escalar definida por el usuario (vista previa)

Aplicable a:✅ Punto final de análisis SQL y Almacén en Microsoft Fabric

Microsoft Fabric Data Warehouse utiliza dos técnicas de inlining para ejecutar funciones escalares definidas por el usuario (UDFs) como parte de consultas distribuidas:

  • La inserción en línea de bloques de expresión admite UDF escalares basadas en cálculos que no hacen referencia a datos en sus cuerpos de función, y admite una amplia variedad de formas de consultas de llamada.
  • La inserción de UDF escalares en Fabric Data Warehouse (versión preliminar) tiene requisitos estrictos tanto para la definición de la función como para la consulta que la invoca. Para más información sobre ambas técnicas y sus requisitos, véase CREATE FUNCTION.

Nota:

Actualmente, las UDF escalares son una característica en versión preliminar de Fabric Data Warehouse.

En este artículo, aprende cómo convertir una UDF no inlineable en una función inlineable o crear una UDF no inlineable.

Convierte una UDF escalar no insertable en línea en insertable en línea

Los cambios en el código T-SQL necesarios para hacer que una UDF escalar sea inlineable dependen de qué constructo haga que su definición sea no inlineable.

Los siguientes ejemplos muestran cómo reescribir múltiples RETURN sentencias, @@ROWCOUNT, y una función dependiente del tiempo en un UDF de acceso a datos.

Reemplazar varias instrucciones RETURN

La siguiente función lee el tipo cliente de una tabla y contiene múltiples RETURN sentencias. Debido a que la función accede a los datos de la tabla, se aplican requisitos de línea UDF escalar. Múltiples RETURN afirmaciones hacen que esta definición no sea inlineable.

Especifica WITH INLINE = AUTO para crear la función mientras te preparas para reescribirla:

CREATE OR ALTER FUNCTION dbo.GetDiscount (@CustomerID INT)
RETURNS DECIMAL(5, 2)
WITH INLINE = AUTO
AS
BEGIN
    DECLARE @CustomerType VARCHAR(20);

    SELECT @CustomerType = CustomerType
    FROM dbo.Customer
    WHERE CustomerID = @CustomerID;

    IF @CustomerType = 'Regular' RETURN 0.05;
    IF @CustomerType = 'Premium' RETURN 0.10;
    IF @CustomerType = 'VIP' RETURN 0.15;
    RETURN 0.01;
END;
GO

Comprueba si la función es inlineable

La sys.sql_modules vista de catálogo proporciona dos propiedades que describen la capacidad de inserción en línea de la UDF escalar:

  • is_inlineable indica si la definición de función es inlineable. Un valor de 1 significa inlineable, y 0 significa no inlineable.
  • inline_eligibility_mask identifica qué técnica de integración puede procesar la definición de la función. Para más información sobre los valores, consulte Comprobar si un UDF escalar se puede insertar en línea.

Utiliza la siguiente consulta para inspeccionar dbo.GetDiscount:

SELECT
    SCHEMA_NAME(o.schema_id) AS function_schema,
    o.name AS function_name,
    m.is_inlineable,
    m.inline_eligibility_mask
FROM sys.sql_modules AS m
INNER JOIN sys.objects AS o
    ON m.object_id = o.object_id
WHERE o.type = 'FN'
    AND SCHEMA_NAME(o.schema_id) = 'dbo'
    AND o.name = 'GetDiscount';
GO

Los metadatos describen la definición de la función. Para la definición inicial de dbo.GetDiscount, is_inlineable es 0.

Reescribe la función para asignar el resultado a una variable local y utiliza una última RETURN sentencia:

CREATE OR ALTER FUNCTION dbo.GetDiscount (@CustomerID INT)
RETURNS DECIMAL(5, 2)
AS
BEGIN
    DECLARE @Discount DECIMAL(5, 2) = 0.01;

    SELECT @Discount =
        CASE CustomerType
            WHEN 'Regular' THEN 0.05
            WHEN 'Premium' THEN 0.10
            WHEN 'VIP' THEN 0.15
            ELSE 0.01
        END
    FROM dbo.Customer
    WHERE CustomerID = @CustomerID;

    RETURN @Discount;
END;
GO

La función revisada tiene una sola instrucción RETURN y cumple los requisitos para la inserción en línea de UDF escalares. Ejecuta de nuevo la consulta de metadatos para verificar que is_inlineable es 1.

Nota:

La inserción en línea de bloques de expresión soporta múltiples RETURN sentencias. Este ejemplo lee datos de tablas y, por tanto, utiliza inlining escalar UDF, que requiere una única RETURN sentencia.

Sustituye @@ROWCOUNT por un valor inicial

La siguiente UDF de acceso a datos usa @@ROWCOUNT para determinar si su consulta encontró un cliente. @@ROWCOUNT no es compatible con la inserción de UDF escalares, por lo que la definición no se puede insertar en línea:

CREATE OR ALTER FUNCTION dbo.CustomerExists (@CustomerID INT)
RETURNS BIT
WITH INLINE = AUTO
AS
BEGIN
    DECLARE @CustomerExists BIT = 1;

    SELECT @CustomerExists = 1
    FROM dbo.Customer
    WHERE CustomerID = @CustomerID;

    IF @@ROWCOUNT = 0 SET @CustomerExists = 0;

    RETURN @CustomerExists;
END;
GO

Inicializa la variable en el resultado para que no haya fila coincidente. La instrucción SELECT cambia el valor solo cuando se encuentra una fila, lo que elimina la necesidad de inspeccionar @@ROWCOUNT:

CREATE OR ALTER FUNCTION dbo.CustomerExists (@CustomerID INT)
RETURNS BIT
AS
BEGIN
    DECLARE @CustomerExists BIT = 0;

    SELECT @CustomerExists = 1
    FROM dbo.Customer
    WHERE CustomerID = @CustomerID;

    RETURN @CustomerExists;
END;
GO

Ejecuta la consulta de metadatos del ejemplo anterior con CustomerExists como nombre de la función. La definición inicial tiene is_inlineable = 0, y la definición revisada tiene is_inlineable = 1.

Pasar un valor dependiente del tiempo a una UDF de acceso a datos

La siguiente función combina el acceso a la tabla con GETUTCDATE(). El acceso a la tabla requiere la inserción en línea de UDF escalares, cuando una función que depende del tiempo en el cuerpo de la UDF hace que la definición no pueda insertarse en línea:

CREATE OR ALTER FUNCTION dbo.GetCustomerTenureDays (@CustomerID INT)
RETURNS INT
WITH INLINE = AUTO
AS
BEGIN
    DECLARE @MemberSince DATE;

    SELECT @MemberSince = MemberSince
    FROM dbo.Customer
    WHERE CustomerID = @CustomerID;

    RETURN DATEDIFF(DAY, @MemberSince, GETUTCDATE());
END;
GO

En su lugar, pasa la fecha y hora actuales a la función como parámetro:

CREATE OR ALTER FUNCTION dbo.GetCustomerTenureDays
(
    @CustomerID INT,
    @AsOfDate DATETIME2(6)
)
RETURNS INT
AS
BEGIN
    DECLARE @MemberSince DATE;

    SELECT @MemberSince = MemberSince
    FROM dbo.Customer
    WHERE CustomerID = @CustomerID;

    RETURN DATEDIFF(DAY, @MemberSince, @AsOfDate);
END;
GO

Evalúa GETUTCDATE() en la consulta de llamada y pasa su valor a la UDF:

SELECT
    CustomerID,
    CustomerName,
    dbo.GetCustomerTenureDays(CustomerID, GETUTCDATE()) AS TenureDays
FROM dbo.Customer
ORDER BY CustomerID;
GO

Haz que una consulta sea inlineable

Los requisitos de estructura de la consulta de esta sección se aplican si al menos una UDF de una consulta depende de la inserción en línea de UDF escalares. Cuando cada UDF en una consulta se maneja mediante inlining de bloques de expresión, estas formas de consulta funcionan sin las reescrituras mostradas aquí.

Los siguientes ejemplos utilizan la versión inlineable de acceso a datos de dbo.GetDiscount de la sección anterior.

Sustituir una expresión de tabla común

Una consulta que utiliza una expresión de tabla común (CTE) no puede insertar en línea una función que depende de la inserción en línea de UDF escalares. La siguiente consulta no se admite:

WITH CustomerDiscounts AS
(
    SELECT
        CustomerID,
        CustomerName,
        dbo.GetDiscount(CustomerID) AS DiscountRate
    FROM dbo.Customer
)
SELECT CustomerID, CustomerName, DiscountRate
FROM CustomerDiscounts;
GO

Materializar el resultado en una tabla temporal y luego consultar la tabla temporal:

DROP TABLE IF EXISTS #CustomerDiscounts;
GO

CREATE TABLE #CustomerDiscounts
(
    CustomerID INT NOT NULL,
    CustomerName VARCHAR(100) NOT NULL,
    DiscountRate DECIMAL(5, 2) NOT NULL
)
WITH (DISTRIBUTION = ROUND_ROBIN);
GO

INSERT INTO #CustomerDiscounts
(
    CustomerID,
    CustomerName,
    DiscountRate
)
SELECT
    CustomerID,
    CustomerName,
    dbo.GetDiscount(CustomerID)
FROM dbo.Customer;
GO

SELECT CustomerID, CustomerName, DiscountRate
FROM #CustomerDiscounts;
GO

Sacar una llamada UDF de GROUP BY

La siguiente consulta llama directamente al UDF en la GROUP BY cláusula y no está soportada para una función que depende del inlining escalar del UDF:

SELECT
    dbo.GetDiscount(CustomerID) AS DiscountRate,
    COUNT(*) AS CustomerCount
FROM dbo.Customer
GROUP BY dbo.GetDiscount(CustomerID);
GO

Calcula el resultado UDF en una tabla derivada y luego agrupa el resultado en la consulta externa:

SELECT
    DiscountRate,
    COUNT(*) AS CustomerCount
FROM
(
    SELECT dbo.GetDiscount(CustomerID) AS DiscountRate
    FROM dbo.Customer
) AS CustomerDiscounts
GROUP BY DiscountRate;
GO

Usa un alias de columna en ORDER BY

Evita llamar directamente a una UDF que depende de la inserción de UDF escalares en la cláusula ORDER BY. Selecciona el resultado una vez y ordena por el alias de la columna o el número ordinal de la columna:

SELECT
    CustomerID,
    CustomerName,
    dbo.GetDiscount(CustomerID) AS DiscountRate
FROM dbo.Customer
ORDER BY DiscountRate;
GO

Crear una UDF escalar no inlineable

Puede que necesites una función para un cálculo independiente incluso cuando su definición no pueda ser en línea. Por ejemplo, una aplicación o informe podría llamar una vez a una UDF para calcular una métrica de negocio.

La siguiente función lee los ingresos acumulados en el año de la tabla de clientes y luego utiliza un WHILE bucle para sumar los ingresos mensuales medios de cada mes restante. Como la función accede a los datos de una tabla, depende de la inserción en línea de UDF escalares, que no admite WHILE. Por lo tanto, la definición de función no es inlineable.

Úsase WITH INLINE = AUTO para permitir la creación de la función aunque su definición no sea inlineable:

CREATE OR ALTER FUNCTION dbo.GetProjectedAnnualRevenue (@MonthsElapsed INT)
RETURNS DECIMAL(18, 2)
WITH INLINE = AUTO
AS
BEGIN
    DECLARE @YearToDateRevenue DECIMAL(38, 6);
    DECLARE @ProjectedRevenue DECIMAL(38, 6);
    DECLARE @MonthNumber INT = @MonthsElapsed + 1;

    IF @MonthsElapsed BETWEEN 1 AND 12
    BEGIN
        SELECT @YearToDateRevenue = SUM(YearToDateRevenue)
        FROM dbo.Customer;

        SET @ProjectedRevenue = @YearToDateRevenue;

        WHILE @MonthNumber <= 12
        BEGIN
            SET @ProjectedRevenue =
                @ProjectedRevenue + (@YearToDateRevenue / @MonthsElapsed);
            SET @MonthNumber = @MonthNumber + 1;
        END;
    END;

    RETURN @ProjectedRevenue;
END;
GO

Utiliza una llamada por separado para calcular la métrica una sola vez para el panel:

DECLARE @ProjectedAnnualRevenue DECIMAL(18, 2);

SET @ProjectedAnnualRevenue =
    dbo.GetProjectedAnnualRevenue(MONTH(GETUTCDATE()));

SELECT @ProjectedAnnualRevenue AS ProjectedAnnualRevenue;
GO

La consulta devuelve los ingresos anuales proyectados en función de los ingresos mensuales promedio hasta el mes actual en UTC.

INLINE = AUTO permite crear la función, pero no hace que una función no inlineable sea elegible para su uso en una consulta distribuida. Por ejemplo, dbo.GetProjectedAnnualRevenue no puede usarse en una SELECT ... FROM dbo.Customer consulta.