Usando APPLY

O operador APPLY permite que você invoque uma função com valor de tabela para cada linha retornada por uma expressão de tabela externa de uma consulta. A função com valor de tabela age como a entrada à direita e a expressão de tabela exterior age como a entrada à esquerda. A entrada à direita é avaliada para cada linha da entrada à esquerda e as linhas produzidas são combinadas na saída final. A lista de colunas produzida pelo operador APPLY é o conjunto de colunas na entrada à esquerda, seguido pela lista de colunas retornadas pela entrada à direita.

ObservaçãoObservação

Para usar APPLY, o nível de compatibilidade do banco de dados deve ser no mínimo 90.

Há duas formas de APPLY: CROSS APPLY e OUTER APPLY. CROSS APPLY só retorna linhas da tabela exterior que produzem um conjunto de resultados da função com valor de tabela. OUTER APPLY retorna linhas que produzem um conjunto de resultados e linhas que não o fazem, com valores NULL nas colunas produzidas pela função com valor de tabela.

Como exemplo, considere as tabelas seguintes, Employees e Departments:

--Create Employees table and insert values.
CREATE TABLE Employees
(
    empid   int         NOT NULL
    ,mgrid   int         NULL
    ,empname varchar(25) NOT NULL
    ,salary  money       NOT NULL
    CONSTRAINT PK_Employees PRIMARY KEY(empid)
);
GO
INSERT INTO Employees VALUES(1 , NULL, 'Nancy'   , $10000.00);
INSERT INTO Employees VALUES(2 , 1   , 'Andrew'  , $5000.00);
INSERT INTO Employees VALUES(3 , 1   , 'Janet'   , $5000.00);
INSERT INTO Employees VALUES(4 , 1   , 'Margaret', $5000.00);
INSERT INTO Employees VALUES(5 , 2   , 'Steven'  , $2500.00);
INSERT INTO Employees VALUES(6 , 2   , 'Michael' , $2500.00);
INSERT INTO Employees VALUES(7 , 3   , 'Robert'  , $2500.00);
INSERT INTO Employees VALUES(8 , 3   , 'Laura'   , $2500.00);
INSERT INTO Employees VALUES(9 , 3   , 'Ann'     , $2500.00);
INSERT INTO Employees VALUES(10, 4   , 'Ina'     , $2500.00);
INSERT INTO Employees VALUES(11, 7   , 'David'   , $2000.00);
INSERT INTO Employees VALUES(12, 7   , 'Ron'     , $2000.00);
INSERT INTO Employees VALUES(13, 7   , 'Dan'     , $2000.00);
INSERT INTO Employees VALUES(14, 11  , 'James'   , $1500.00);
GO
--Create Departments table and insert values.
CREATE TABLE Departments
(
    deptid    INT NOT NULL PRIMARY KEY
    ,deptname  VARCHAR(25) NOT NULL
    ,deptmgrid INT NULL REFERENCES Employees
);
GO
INSERT INTO Departments VALUES(1, 'HR',           2);
INSERT INTO Departments VALUES(2, 'Marketing',    7);
INSERT INTO Departments VALUES(3, 'Finance',      8);
INSERT INTO Departments VALUES(4, 'R&D',          9);
INSERT INTO Departments VALUES(5, 'Training',     4);
INSERT INTO Departments VALUES(6, 'Gardening', NULL);

A maioria dos departamentos na tabela Departments tem uma ID de gerente que corresponde a um funcionário na tabela Employees. A seguinte função com valor de tabela aceita uma ID de funcionário como um argumento e retorna esse funcionário e todos os seus subordinados.

CREATE FUNCTION dbo.fn_getsubtree(@empid AS INT) 
    RETURNS @TREE TABLE
(
    empid   INT NOT NULL
    ,empname VARCHAR(25) NOT NULL
    ,mgrid   INT NULL
    ,lvl     INT NOT NULL
)
AS
BEGIN
  WITH Employees_Subtree(empid, empname, mgrid, lvl)
  AS
  ( 
    -- Anchor Member (AM)
    SELECT empid, empname, mgrid, 0
    FROM Employees
    WHERE empid = @empid

    UNION all
    
    -- Recursive Member (RM)
    SELECT e.empid, e.empname, e.mgrid, es.lvl+1
    FROM Employees AS e
      JOIN Employees_Subtree AS es
        ON e.mgrid = es.empid
  )
  INSERT INTO @TREE
    SELECT * FROM Employees_Subtree;

  RETURN
END
GO

Para retornar todos os subordinados em todos os níveis para o gerente de cada departamento, use a consulta a seguir.

SELECT D.deptid, D.deptname, D.deptmgrid
    ,ST.empid, ST.empname, ST.mgrid
FROM Departments AS D
    CROSS APPLY fn_getsubtree(D.deptmgrid) AS ST;

Aqui está o conjunto de resultados.

deptid      deptname   deptmgrid   empid       empname    mgrid       lvl
----------- ---------- ----------- ----------- ---------- ----------- ---
1           HR         2           2           Andrew     1           0
1           HR         2           5           Steven     2           1
1           HR         2           6           Michael    2           1
2           Marketing  7           7           Robert     3           0
2           Marketing  7           11          David      7           1
2           Marketing  7           12          Ron        7           1
2           Marketing  7           13          Dan        7           1
2           Marketing  7           14          James      11          2
3           Finance    8           8           Laura      3           0
4           R&D        9           9           Ann        3           0
5           Training   4           4           Margaret   1           0
5           Training   4           10          Ina        4           1

Observe que cada linha da tabela Departments é duplicada tantas vezes quantas forem as linhas retornadas de fn_getsubtree para o gerente do departamento.

Além disso, o departamento Gardening não aparece nos resultados. Como esse departamento não tem gerente, fn_getsubtree retornou um conjunto vazio. Ao usar OUTER APPLY, o departamento Gardening também aparecerá no conjunto de resultados, com valores nulos no campo deptmgrid, bem como nos campos retornados por fn_getsubtree.

Consulte também

Referência