web-dev-qa-db-fra.com

Exemple concret, quand utiliser OUTER / CROSS APPLY dans SQL

J'ai consulté CROSS / OUTER APPLY avec un collègue et nous avons du mal à trouver des exemples concrets d'utilisation.

J'ai passé pas mal de temps à regarder Quand devrais-je utiliser l'application croisée sur Inner Join? et googler mais l'exemple principal (seul) semble assez bizarre (utiliser le nombre de lignes d'un tableau pour déterminer comment nombre de lignes à sélectionner dans une autre table).

Je pensais que ce scénario pourrait bénéficier de OUTER APPLY:

Table des contacts (contient 1 enregistrement pour chaque contact) Table des entrées de communication (peut contenir n téléphone, fax, email de chaque contact)

Mais en utilisant des sous-requêtes, des expressions de table communes, OUTER JOIN avec RANK() et OUTER APPLY semblent tous fonctionner de manière égale. Je suppose que cela signifie que le scénario ne s'applique pas à APPLY.

S'il vous plaît partager des exemples de la vie réelle et aider à expliquer la fonctionnalité!

119
Lee Tickett

Certaines utilisations de APPLY sont ...

1) Top N requêtes par groupe (peut être plus efficace pour certaines cardinalités)

SELECT pr.name,
       pa.name
FROM   sys.procedures pr
       OUTER APPLY (SELECT TOP 2 *
                    FROM   sys.parameters pa
                    WHERE  pa.object_id = pr.object_id
                    ORDER  BY pr.name) pa
ORDER  BY pr.name,
          pa.name 

2) Appel d'une fonction de valeur de table pour chaque ligne de la requête externe

SELECT *
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle)

3) Réutilisation d'un alias de colonne

SELECT number,
       doubled_number,
       doubled_number_plus_one
FROM master..spt_values
CROSS APPLY (SELECT 2 * CAST(number AS BIGINT)) CA1(doubled_number)  
CROSS APPLY (SELECT doubled_number + 1) CA2(doubled_number_plus_one)  

4) Déverrouiller plus d'un groupe de colonnes

Suppose que 1NF viole la structure de la table ....

CREATE TABLE T
  (
     Id   INT PRIMARY KEY,

     Foo1 INT, Foo2 INT, Foo3 INT,
     Bar1 INT, Bar2 INT, Bar3 INT
  ); 

Exemple utilisant 2008+ VALUES syntaxe.

SELECT Id,
       Foo,
       Bar
FROM   T
       CROSS APPLY (VALUES(Foo1, Bar1),
                          (Foo2, Bar2),
                          (Foo3, Bar3)) V(Foo, Bar); 

En 2005, UNION ALL peut être utilisé à la place.

SELECT Id,
       Foo,
       Bar
FROM   T
       CROSS APPLY (SELECT Foo1, Bar1 
                    UNION ALL
                    SELECT Foo2, Bar2 
                    UNION ALL
                    SELECT Foo3, Bar3) V(Foo, Bar);
165
Martin Smith

Il existe différentes situations dans lesquelles vous ne pouvez pas éviter CROSS APPLY ou OUTER APPLY.

Considérez que vous avez deux tables.

TABLEAU PRINCIPAL

x------x--------------------x
| Id   |        Name        |
x------x--------------------x
|  1   |          A         |
|  2   |          B         |
|  3   |          C         |
x------x--------------------x

TABLEAU DÉTAILS

x------x--------------------x-------x
| Id   |      PERIOD        |   QTY |
x------x--------------------x-------x
|  1   |   2014-01-13       |   10  |
|  1   |   2014-01-11       |   15  |
|  1   |   2014-01-12       |   20  |
|  2   |   2014-01-06       |   30  |
|  2   |   2014-01-08       |   40  |
x------x--------------------x-------x                                       



CROSS APPLY

Il existe de nombreuses situations dans lesquelles nous devons remplacer INNER JOIN par CROSS APPLY.

1. Si nous voulons joindre 2 tables sur TOP n résultats avec INNER JOIN fonctionnalité

Considérez si nous devons sélectionner Id et Name parmi Master et les deux dernières dates pour chaque Id parmi Details table.

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
INNER JOIN
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D      
    ORDER BY CAST(PERIOD AS DATE)DESC
)D
ON M.ID=D.ID

La requête ci-dessus génère le résultat suivant.

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
x------x---------x--------------x-------x

Voir, il a généré des résultats pour les deux dernières dates avec Id, puis n'a joint ces enregistrements que dans la requête externe sur Id, ce qui est faux. Pour ce faire, nous devons utiliser CROSS APPLY.

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
CROSS APPLY
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D  
    WHERE M.ID=D.ID
    ORDER BY CAST(PERIOD AS DATE)DESC
)D

et forme le résultat suivant.

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-08   |  40   |
|   2  |   B     | 2014-01-06   |  30   |
x------x---------x--------------x-------x

Voici le travail. La requête à l'intérieur de CROSS APPLY peut faire référence à la table externe, alors que INNER JOIN ne peut pas le faire (une erreur de compilation est générée). Lors de la recherche des deux dernières dates, la jonction est effectuée à l'intérieur de CROSS APPLY c'est-à-dire WHERE M.ID=D.ID.

2. Lorsque nous avons besoin de la fonctionnalité INNER JOIN à l'aide de fonctions.

CROSS APPLY peut être utilisé en remplacement de INNER JOIN lorsque nous devons obtenir le résultat de Master table et d'un function.

SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
CROSS APPLY dbo.FnGetQty(M.ID) C

Et voici la fonction

CREATE FUNCTION FnGetQty 
(   
    @Id INT 
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT ID,PERIOD,QTY 
    FROM DETAILS
    WHERE ID=@Id
)

qui a généré le résultat suivant

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-11   |  15   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-06   |  30   |
|   2  |   B     | 2014-01-08   |  40   |
x------x---------x--------------x-------x



EXTERNE APPLIQUÉ

1. Si nous voulons joindre 2 tables sur TOP n résultats avec LEFT JOIN fonctionnalité

Considérez si nous devons sélectionner Id et Nom dans Master et les deux dernières dates pour chaque ID dans la table Details.

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
LEFT JOIN
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D  
    ORDER BY CAST(PERIOD AS DATE)DESC
)D
ON M.ID=D.ID

qui forme le résultat suivant

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     |   NULL       |  NULL |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x

Cela produira des résultats erronés, c’est-à-dire qu’il n’apportera que les deux dernières données de date de la table Details, indépendamment de Id, bien que nous joignions avec Id. La solution appropriée consiste donc à utiliser OUTER APPLY.

SELECT M.ID,M.NAME,D.PERIOD,D.QTY
FROM MASTER M
OUTER APPLY
(
    SELECT TOP 2 ID, PERIOD,QTY 
    FROM DETAILS D  
    WHERE M.ID=D.ID
    ORDER BY CAST(PERIOD AS DATE)DESC
)D

qui forme le résultat souhaité suivant

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-08   |  40   |
|   2  |   B     | 2014-01-06   |  30   |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x

2. Lorsque nous avons besoin de la fonctionnalité LEFT JOIN avec functions.

OUTER APPLY peut être utilisé en remplacement de LEFT JOIN lorsque nous devons obtenir le résultat de Master table et d'un function.

SELECT M.ID,M.NAME,C.PERIOD,C.QTY
FROM MASTER M
OUTER APPLY dbo.FnGetQty(M.ID) C

Et la fonction va ici.

CREATE FUNCTION FnGetQty 
(   
    @Id INT 
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT ID,PERIOD,QTY 
    FROM DETAILS
    WHERE ID=@Id
)

qui a généré le résultat suivant

x------x---------x--------------x-------x
|  Id  |   Name  |   PERIOD     |  QTY  |
x------x---------x--------------x-------x
|   1  |   A     | 2014-01-13   |  10   |
|   1  |   A     | 2014-01-11   |  15   |
|   1  |   A     | 2014-01-12   |  20   |
|   2  |   B     | 2014-01-06   |  30   |
|   2  |   B     | 2014-01-08   |  40   |
|   3  |   C     |   NULL       |  NULL |
x------x---------x--------------x-------x



Caractéristique commune de CROSS APPLY et OUTER APPLY)

CROSS APPLY ou OUTER APPLY peut être utilisé pour conserver les valeurs NULL non pivotantes, qui sont interchangeables.

Considérez que vous avez le tableau ci-dessous

x------x-------------x--------------x
|  Id  |   FROMDATE  |   TODATE     |
x------x-------------x--------------x
|   1  |  2014-01-11 | 2014-01-13   | 
|   1  |  2014-02-23 | 2014-02-27   | 
|   2  |  2014-05-06 | 2014-05-30   |    
|   3  |   NULL      |   NULL       | 
x------x-------------x--------------x

Lorsque vous utilisez UNPIVOT pour amener FROMDATE ET TODATE dans une colonne, les valeurs de NULL sont éliminées par défaut.

SELECT ID,DATES
FROM MYTABLE
UNPIVOT (DATES FOR COLS IN (FROMDATE,TODATE)) P

qui génère le résultat ci-dessous. Notez que nous avons manqué l'enregistrement de IdNuméro 3

  x------x-------------x
  | Id   |    DATES    |
  x------x-------------x
  |  1   |  2014-01-11 |
  |  1   |  2014-01-13 |
  |  1   |  2014-02-23 |
  |  1   |  2014-02-27 |
  |  2   |  2014-05-06 |
  |  2   |  2014-05-30 |
  x------x-------------x

Dans ce cas, un CROSS APPLY ou OUTER APPLY sera utile

SELECT DISTINCT ID,DATES
FROM MYTABLE 
OUTER APPLY(VALUES (FROMDATE),(TODATE))
COLUMNNAMES(DATES)

qui forme le résultat suivant et conserve Id où sa valeur est 3

  x------x-------------x
  | Id   |    DATES    |
  x------x-------------x
  |  1   |  2014-01-11 |
  |  1   |  2014-01-13 |
  |  1   |  2014-02-23 |
  |  1   |  2014-02-27 |
  |  2   |  2014-05-06 |
  |  2   |  2014-05-30 |
  |  3   |     NULL    |
  x------x-------------x
81
Sarath Avanavu

Un exemple concret serait si vous aviez un planificateur et vouliez voir quelle était l'entrée la plus récente du journal pour chaque tâche planifiée.

select t.taskName, lg.logResult, lg.lastUpdateDate
from task t
cross apply (select top 1 taskID, logResult, lastUpdateDate
             from taskLog l
             where l.taskID = t.taskID
             order by lastUpdateDate desc) lg
8
BJury

Pour répondre au point ci-dessus, créez un exemple:

create table #task (taskID int identity primary key not null, taskName varchar(50) not null)
create table #log (taskID int not null, reportDate datetime not null, result varchar(50) not null, primary key(reportDate, taskId))

insert #task select 'Task 1'
insert #task select 'Task 2'
insert #task select 'Task 3'
insert #task select 'Task 4'
insert #task select 'Task 5'
insert #task select 'Task 6'

insert  #log
select  taskID, 39951 + number, 'Result text...'
from    #task
        cross join (
            select top 1000 row_number() over (order by a.id) as number from syscolumns a cross join syscolumns b cross join syscolumns c) n

Et exécutez maintenant les deux requêtes avec un plan d'exécution.

select  t.taskID, t.taskName, lg.reportDate, lg.result
from    #task t
        left join (select taskID, reportDate, result, rank() over (partition by taskID order by reportDate desc) rnk from #log) lg
            on lg.taskID = t.taskID and lg.rnk = 1

select  t.taskID, t.taskName, lg.reportDate, lg.result
from    #task t
        outer apply (   select  top 1 l.*
                        from    #log l
                        where   l.taskID = t.taskID
                        order   by reportDate desc) lg

Vous pouvez voir que la requête externe est plus efficace. (Impossible de joindre le plan car je suis un nouvel utilisateur ... Doh.)

5
BJury