Criar uma função escalar definida pelo utilizador (pré-visualização)

Aplica-se a:✅ Ponto Final de Análise SQL e Armazém no Microsoft Fabric

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

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

Note

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

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

Torne uma UDF escalar não inlineável

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

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

Substituir múltiplas instruções RETURN

A função seguinte lê o tipo de cliente de uma tabela e contém várias RETURN instruções. Como a função acede aos dados da tabela, aplicam-se requisitos escalares de inlining UDF. Múltiplas RETURN afirmações tornam esta definição não inlineável.

Especifique WITH INLINE = AUTO para criar a função enquanto 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 vista sys.sql_modules de catálogo fornece 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 a técnica de inlining que pode processar a definição da função. Para mais informações sobre os valores, veja Verificar se um UDF escalar pode ser inlineado.

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 revista tem uma única instrução RETURN e cumpre os requisitos de inline de UDF escalar. Execute novamente a consulta de metadados para verificar que is_inlineable é 1.

Note

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

Substitua @@ROWCOUNT por um valor inicial

A UDF de acesso a dados seguinte utiliza @@ROWCOUNT para determinar se a sua consulta encontrou um cliente. @@ROWCOUNT não é suportado por inlining escalar UDF, por isso 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 existir nenhuma 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 revista tem is_inlineable = 1.

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

A função seguinte combina o acesso à tabela com GETUTCDATE(). O acesso à tabela requer inlining 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 parâmetro em vez disso:

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 o 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 relativos à estrutura da consulta nesta secção aplicam-se quando pelo menos uma UDF numa consulta depende da inlineização de UDFs escalares. Quando todas as UDF numa consulta são tratadas por Expression Block inlining, estas formas de consulta funcionam sem as reescritas aqui mostradas.

Os exemplos seguintes utilizam a versão dbo.GetDiscount inlineável e de acesso a dados da secção anterior.

Substituir uma expressão comum de tabela

Uma consulta que utiliza uma expressão de tabela comum (CTE) não pode expandir em linha uma função que depende da expansão em linha de UDFs escalares. A seguinte consulta 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 numa tabela temporária e depois 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

Retirar uma chamada a uma UDF de GROUP BY

A consulta seguinte chama a UDF diretamente na cláusula GROUP BY e não é suportada para uma função que depende da inlineização de UDF escalar:

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

Calcule o resultado do UDF numa tabela derivada e depois 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 do inlining escalar do UDF diretamente na ORDER BY cláusula. Projete o resultado uma 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

Criar um UDF escalar não inlineável

Poderá ser necessária uma função para um cálculo independente, mesmo quando a sua definição não pode ser incorporada diretamente. Por exemplo, uma aplicação ou relatório pode chamar uma UDF uma vez para calcular uma métrica de negócio.

A função seguinte lê a receita acumulada do ano a partir da tabela de clientes e depois utiliza um WHILE ciclo para somar a receita mensal média de cada mês restante. Como a função acede aos dados da tabela, depende do inlining scalar UDF, que não suporta WHILE. A definição da função é, portanto, não inlineável.

WITH INLINE = AUTO Use para permitir a criação da função mesmo que a 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 autónoma 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 apresenta receitas anuais projetadas com base na receita média mensal até ao mês UTC atual.

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