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é!
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);
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
etOUTER 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 Id
Numé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
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
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.)