Crie uma função escalar definida pelo usuário (prévia)

Aplica-se a:✅ Endpoint de análise de SQL e Armazém de Dados no Microsoft Fabric

Microsoft Fabric Data Warehouse utiliza duas técnicas de inlining para executar funções escalares definidas pelo usuário (UDFs) como parte de consultas distribuídas:

  • A inserção inline de blocos de expressão suporta UDFs escalares baseadas em computação que não referenciam dados em seus corpos de função e suporta uma grande variedade de formas de consulta de chamada.
  • A expansão embutida de UDF escalar no Fabric Data Warehouse (prévia) tem requisitos rigorosos tanto para a definição da função quanto para a consulta que a chama. Para mais informações sobre as duas técnicas e seus requisitos, consulte CREATE FUNCTION.

Observação

Os UDFs escalares são atualmente um recurso de pré-visualização em Fabric Data Warehouse.

Neste artigo, aprenda como converter uma UDF não inlineável em uma função inlineável ou criar uma UDF não inlineável.

Torne uma FUD escalar não inlineável

As alterações no código T-SQL necessárias para tornar um UDF escalar inlineável dependem de qual construto torna sua definição não inlineável.

Os exemplos a seguir mostram como reescrever várias instruções RETURN, @@ROWCOUNT e uma função dependente de tempo em um UDF de acesso a dados.

Substituir vários comandos RETURN

A função a seguir lê o tipo de cliente de uma tabela e contém várias instruções RETURN. Como a função acessa dados de tabelas, exigências de inlining UDF escalar se aplicam. Múltiplas RETURN afirmações tornam essa definição não inlineável.

Especifique WITH INLINE = AUTO para criar a função enquanto você se prepara para reescrevê-la:

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

Verifique se a função é inlineável

A sys.sql_modules visualização de catálogo oferece duas propriedades que descrevem a inlineabilidade escalar da UDF:

  • is_inlineable indica se a definição da função é inlineável. Um valor de 1 significa inlineável, e 0 significa não inlineável.
  • inline_eligibility_mask identifica qual técnica de inlining pode processar a definição da função. Para mais informações sobre os valores, consulte Verifique se uma UDF escalar pode ser embutida.

Use a seguinte consulta para inspecionar 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

Os metadados descrevem a definição da função. Para a definição inicial de dbo.GetDiscount, is_inlineable é 0.

Reescreva a função para atribuir o resultado a uma variável local e use uma última RETURN afirmação:

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

A função revisada possui uma única instrução RETURN e atende aos requisitos de inline de UDF escalar. Execute novamente a consulta de metadados para verificar que is_inlineable é 1.

Observação

O inlining por bloco de expressão suporta múltiplas RETURN instruções. Este exemplo lê dados da tabela e, portanto, utiliza inlining escalar de UDF, que requer uma única instrução RETURN.

Substitua @@ROWCOUNT por um valor inicial

O UDF de acesso a dados a seguir usa @@ROWCOUNT para determinar se a consulta encontrou um cliente. @@ROWCOUNT não é suportado por inlining escalar UDF, então a definição não é inlineável:

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

Inicialize a variável com o resultado para o caso de não haver linha correspondente. A SELECT instrução só altera o valor quando uma linha é encontrada, o que elimina a necessidade de inspecionar @@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

Execute a consulta de metadados do exemplo anterior com CustomerExists como nome da função. A definição inicial tem is_inlineable = 0, e a definição revisada tem is_inlineable = 1.

Passe um valor dependente do tempo para um UDF de acesso a dados

A função a seguir combina acesso à tabela com GETUTCDATE(). O acesso à tabela requer inlineamento escalar UDF, onde uma função dependente do tempo no corpo UDF torna a definição não inlineável:

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

Passe a data e hora atuais para a função como um 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

Avalie GETUTCDATE() na consulta de chamada e passe seu valor para a UDF:

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

Torne uma consulta inlineável

Os requisitos de formato da consulta nesta seção se aplicam quando pelo menos uma UDF em uma consulta depende da inserção embutida de UDF escalar. Quando cada UDF em uma consulta é tratado por inlining de Expression Block, essas formas de consulta funcionam sem as reescritas mostradas aqui.

Os exemplos a seguir usam a versão dbo.GetDiscount inlineável e de acesso a dados da seção anterior.

Substituir uma expressão comum de tabela

Uma consulta que usa uma expressão de tabela comum (CTE) não pode embutir uma função que depende do embutimento de UDF escalar. A consulta a seguir não é suportada:

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

Materialize o resultado em uma tabela temporária e então consulte a tabela temporária:

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

Retire uma chamada UDF do GROUP BY

A consulta a seguir invoca a UDF diretamente na cláusula GROUP BY e não tem suporte para uma função que depende do inlining escalar de UDF:

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

Calcule o resultado do UDF em uma tabela derivada e então agrupe o resultado na consulta externa:

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

Use um alias de coluna em ORDER BY

Evite chamar um UDF que dependa diretamente do inlining escalar do UDF na ORDER BY cláusula. Projete o resultado uma única vez e ordene pelo alias da coluna ou pelo número ordinal da coluna:

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

Crie um UDF escalar não inlineável

Você pode precisar de uma função para um cálculo independente mesmo quando sua definição não pode ser inlineada. Por exemplo, uma aplicação ou relatório pode chamar um UDF uma vez para calcular uma métrica de negócio.

A função a seguir lê a receita acumulada do ano da tabela de clientes e depois usa um WHILE loop para somar a receita mensal média de cada mês restante. Como a função acessa dados de tabelas, ela depende do inlining escalar UDF, que não suporta WHILE. A definição da função, portanto, não é inlineável.

Use WITH INLINE = AUTO para permitir que a função seja criada mesmo que sua definição não seja inlineável:

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

Use uma chamada independente para calcular a métrica uma vez para o dashboard:

DECLARE @ProjectedAnnualRevenue DECIMAL(18, 2);

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

SELECT @ProjectedAnnualRevenue AS ProjectedAnnualRevenue;
GO

A consulta retorna a receita anual projetada com base na receita média mensal até o mês atual UTC.

INLINE = AUTO permite que a função seja criada, mas não torna uma função não inlineável elegível para uso em uma consulta distribuída. Por exemplo, dbo.GetProjectedAnnualRevenue não pode ser usado em uma SELECT ... FROM dbo.Customer consulta.