Pour être complet, une autre façon d'aborder ce problème consiste à utiliser OUTER APPLY . Nous pouvons ajouter un OUTER APPLY
opérateur pour chaque valeur distincte que nous devons trouver. Ceci est similaire dans son concept à l'approche récursive d'Ypercube, mais a effectivement la récursion écrite à la main. Un avantage est que nous pouvons utiliser TOP
dans les tables dérivées au lieu de la ROW_NUMBER()
solution de contournement. Un gros inconvénient est que le texte de la requête s'allonge à mesure qu'il N
augmente.
Voici une implémentation de la requête sur le tas:
SELECT VAL
FROM (
SELECT t1.VAL VAL1, t2.VAL VAL2, t3.VAL VAL3, t4.VAL VAL4, t5.VAL VAL5, t6.VAL VAL6, t7.VAL VAL7, t8.VAL VAL8, t9.VAL VAL9, t10.VAL VAL10
FROM
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP
) t1
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t2 WHERE t2.VAL NOT IN (t1.VAL)
) t2
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t3 WHERE t3.VAL NOT IN (t1.VAL, t2.VAL)
) t3
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t4 WHERE t4.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL)
) t4
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t5 WHERE t5.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL)
) t5
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t6 WHERE t6.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL)
) t6
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t7 WHERE t7.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL)
) t7
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t8 WHERE t8.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL)
) t8
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t9 WHERE t9.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL)
) t9
OUTER APPLY
(
SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t10 WHERE t10.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL, t9.VAL)
) t10
) t
UNPIVOT
(
VAL FOR VALS IN (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL8, VAL9, VAL10)
) AS upvt;
Voici le plan de requête réel pour la requête ci-dessus. Sur ma machine, cette requête se termine en 713 ms avec 625 ms de temps CPU et 12605 lectures logiques. Nous obtenons une nouvelle valeur distincte toutes les 100k lignes, je m'attends donc à ce que cette requête analyse environ 900000 * 10 * 0,5 = 4500000 lignes. En théorie, cette requête devrait faire cinq fois les lectures logiques de cette requête de l'autre réponse:
DECLARE @j INT = 10;
SELECT DISTINCT TOP (@j) VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, OPTIMIZE FOR (@j = 1));
Cette requête a effectué 2537 lectures logiques. 2537 * 5 = 12685 qui est assez proche de 12605.
Pour la table avec l'index clusterisé, nous pouvons faire mieux. Cela est dû au fait que nous pouvons passer la dernière valeur de clé en cluster dans la table dérivée pour éviter d'analyser deux fois les mêmes lignes. Une mise en œuvre:
SELECT VAL
FROM (
SELECT t1.VAL VAL1, t2.VAL VAL2, t3.VAL VAL3, t4.VAL VAL4, t5.VAL VAL5, t6.VAL VAL6, t7.VAL VAL7, t8.VAL VAL8, t9.VAL VAL9, t10.VAL VAL10
FROM
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI
) t1
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t2 WHERE PK > t1.PK AND t2.VAL NOT IN (t1.VAL)
) t2
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t3 WHERE PK > t2.PK AND t3.VAL NOT IN (t1.VAL, t2.VAL)
) t3
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t4 WHERE PK > t3.PK AND t4.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL)
) t4
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t5 WHERE PK > t4.PK AND t5.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL)
) t5
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t6 WHERE PK > t5.PK AND t6.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL)
) t6
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t7 WHERE PK > t6.PK AND t7.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL)
) t7
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t8 WHERE PK > t7.PK AND t8.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL)
) t8
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t9 WHERE PK > t8.PK AND t9.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL)
) t9
OUTER APPLY
(
SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t10 WHERE PK > t9.PK AND t10.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL, t9.VAL)
) t10
) t
UNPIVOT
(
VAL FOR VALS IN (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL8, VAL9, VAL10)
) AS upvt;
Voici le plan de requête réel pour la requête ci-dessus. Sur ma machine, cette requête se termine en 154 ms avec 140 ms de temps CPU et 3203 lectures logiques. Cela semblait fonctionner un peu plus vite que la OPTIMIZE FOR
requête sur la table d'index cluster. Je ne m'attendais pas à cela, alors j'ai essayé de mesurer les performances plus attentivement. Ma méthodologie consistait à exécuter chaque requête dix fois sans jeux de résultats et à examiner les nombres agrégés à partir de sys.dm_exec_sessions
et sys.dm_exec_session_wait_stats
. La session 56 était la APPLY
requête et la session 63 était la OPTIMIZE FOR
requête.
Sortie de sys.dm_exec_sessions
:
╔════════════╦══════════╦════════════════════╦═══════════════╗
║ session_id ║ cpu_time ║ total_elapsed_time ║ logical_reads ║
╠════════════╬══════════╬════════════════════╬═══════════════╣
║ 56 ║ 1360 ║ 1373 ║ 32030 ║
║ 63 ║ 2094 ║ 2091 ║ 30400 ║
╚════════════╩══════════╩════════════════════╩═══════════════╝
Il semble y avoir un net avantage dans cpu_time et elapsed_time pour la APPLY
requête.
Sortie de sys.dm_exec_session_wait_stats
:
╔════════════╦════════════════════════════════╦═════════════════════╦══════════════╦══════════════════╦═════════════════════╗
║ session_id ║ wait_type ║ waiting_tasks_count ║ wait_time_ms ║ max_wait_time_ms ║ signal_wait_time_ms ║
╠════════════╬════════════════════════════════╬═════════════════════╬══════════════╬══════════════════╬═════════════════════╣
║ 56 ║ SOS_SCHEDULER_YIELD ║ 340 ║ 0 ║ 0 ║ 0 ║
║ 56 ║ MEMORY_ALLOCATION_EXT ║ 38 ║ 0 ║ 0 ║ 0 ║
║ 63 ║ SOS_SCHEDULER_YIELD ║ 518 ║ 0 ║ 0 ║ 0 ║
║ 63 ║ MEMORY_ALLOCATION_EXT ║ 98 ║ 0 ║ 0 ║ 0 ║
║ 63 ║ RESERVED_MEMORY_ALLOCATION_EXT ║ 400 ║ 0 ║ 0 ║ 0 ║
╚════════════╩════════════════════════════════╩═════════════════════╩══════════════╩══════════════════╩═════════════════════╝
La OPTIMIZE FOR
requête a un type d'attente supplémentaire, RESERVED_MEMORY_ALLOCATION_EXT . Je ne sais pas exactement ce que cela signifie. Il peut simplement s'agir d'une mesure de la surcharge dans l'opérateur de correspondance de hachage (flux distinct). Dans tous les cas, cela ne vaut peut-être pas la peine de s'inquiéter d'une différence de 70 ms dans le temps CPU.