web-dev-qa-db-fra.com

Comment vérifier si une combinaison de colonnes existe dans une autre table dans SQL Server?

Voici comment j'exprimerais ma requête dans MySQL ou Oracle:

SELECT t1.c1, t2.c1, t1.c2, t2.c2, ...
FROM t1, t2
WHERE (t1.c1, t2.c1) NOT IN (SELECT c1, c2 FROM t3)

Comment puis-je exprimer la même requête dans SQL Server?

J'ai pensé que je pouvais les enchaîner sous forme de cordes mais ce ne pouvait pas être la meilleure façon de le faire.

Je n'ai pas pu trouver un moyen de le faire correctement dans SQL Server.

Je ne peux plus les rejoindre car chaque table contient des millions d'enregistrements.

Exemples de tableaux pour qu'il soit plus facile d'imaginer le problème:

t1:

c1 c2 c3
--------
1  x  A
2  y  B
3  z  C

t2:

c1 c2 c3
--------
1  m  D
2  n  E
3  t  F
4  v  G

t3:

c1 c2 c4
--------
1  2  aa
1  4  bb
2  3  cc
3
Haggra

Une jointure dans SQL Server n'est pas implémentée automatiquement en tant que boucle imbriquée. Par exemple, une jointure de hachage peut être utilisée pour implémenter le NOT IN. Cela signifie que la requête ne se terminera pas nécessairement par un nombre de tuples supérieur à 10 ^ 18, même si chacune des trois tables jointes contient plus de 10 ^ 6 lignes.

Par exemple, voici un plan de requête sur un volume de données réduit qui montre que le plus grand nombre de lignes à n'importe quel point du plan de requête est proportionnel à la taille de t1 fois la taille de t2, mais est indépendant de la taille de t3.

enter image description here

Même ainsi, il est impossible de contourner le fait qu'une requête qui sélectionne 10 billions de lignes (probablement pour les agréger ou les insérer dans une table columnstore?) Va prendre un certain temps à s'exécuter. Voici le script complet, ainsi qu'une analyse plus approfondie:

Créer des exemples de données

DROP TABLE IF EXISTS #t1, #t2, #t3, #batchMode, #results
GO

CREATE TABLE #t1 (c1 INT NOT NULL, c2 INT NOT NULL, INDEX CI CLUSTERED (c1, c2))
CREATE TABLE #t2 (c1 INT NOT NULL, c2 INT NOT NULL, INDEX CI CLUSTERED (c1, c2))
CREATE TABLE #t3 (c1 INT NOT NULL, c2 INT NOT NULL, INDEX CI CLUSTERED (c1, c2))
CREATE TABLE #batchMode (dummy INT NOT NULL)
CREATE CLUSTERED COLUMNSTORE INDEX CCI ON #batchMode
GO

DECLARE @numRows INT = 32000
INSERT INTO #t1 (c1, c2)
SELECT TOP(@numRows)
    ABS(CRYPT_GEN_RANDOM(8) % 33) AS c1,
    ABS(CRYPT_GEN_RANDOM(8) % 57) AS c2
FROM master..spt_values v1
CROSS JOIN master..spt_values v2

INSERT INTO #t2 (c1, c2)
SELECT TOP(@numRows)
    ABS(CRYPT_GEN_RANDOM(8) % 17) AS c1,
    ABS(CRYPT_GEN_RANDOM(8) % 74) AS c2
FROM master..spt_values v1
CROSS JOIN master..spt_values v2

INSERT INTO #t3 (c1, c2)
SELECT TOP(@numRows)
    ABS(CRYPT_GEN_RANDOM(8) % 33) AS c1,
    ABS(CRYPT_GEN_RANDOM(8) % 17) AS c2
FROM master..spt_values v1
CROSS JOIN master..spt_values v2
GO

Exécutez la requête

En exécutant la requête avec différents nombres de lignes, nous pouvons voir que les performances des requêtes augmentent de façon quadratique (N ^ 2) avec le nombre de lignes de t1 et t2, mais n'est pas significativement impacté par le nombre de lignes de t3. Nous pouvons donc extrapoler le temps d'exécution avec le produit croisé complet de 16 billions de lignes à environ 217 heures.

-- Perform your query, using aggregates to avoid writing billions of rows to console
-- 4K rows / table      CPU time = 13906 ms,  elapsed time = 1510 ms.
-- 8K rows / table      CPU time = 54563 ms,  elapsed time = 4548 ms.
-- 16K rows / table     CPU time = 192032 ms,  elapsed time = 13683 ms.
-- 32K rows / table     CPU time = 757688 ms,  elapsed time = 50754 ms.
-- Since the time scales quadratically once we have enough rows to efficiently utilize
-- all threads, we can predict 4MM rows / table with the following query:
--      SELECT POWER(4000000 / 32000, 2) * (50. / 3600)
-- 4MM rows / table     Estimated elapsed time = 217 hours
SELECT COUNT_BIG(*), SUM(1.0 * t1.c1 * t2.c1 * t1.c2 * t2.c2)
FROM #t1 t1
CROSS JOIN #t2 t2
WHERE NOT EXISTS (
    SELECT *
    FROM #t3 t3
    WHERE t3.c1 = t1.c1
        AND t3.c2 = t2.c2
)
    /* Enable batch mode hash join to avoid repartition streams on billions of rows
        This reduces elapsed time from 77 seconds to 50 seconds at 32K rows / table */
    AND NOT EXISTS (SELECT * FROM #batchMode WHERE 0=1)
OPTION (MAXDOP 16)
GO

Remplacer la jointure de boucle par la jointure de hachage en mode batch

Étant donné que cette requête est très gourmande en ressources processeur, il est naturel de se demander si la majorité du travail - la boucle se joint pour créer le produit croisé des lignes entre t1 et t2 - peut être remplacé par un opérateur qui prend en charge l'exécution en mode batch. Une façon de procéder consiste à réimplémenter la jointure croisée en tant qu'équijoin sur une seule colonne où toutes les lignes de chaque table ont la même valeur.

Cela réduit le temps prévu d'environ un facteur 10, à environ 20 heures pour filtrer et agréger les 16 billions de rangées intermédiaires générées par le produit croisé.

-- Add a column that will always have the value 1 to each table, enabling an
-- as hash match implementation of a cross join by matching on this column
ALTER TABLE #t1 ADD one TINYINT NOT NULL DEFAULT (1)
ALTER TABLE #t2 ADD one TINYINT NOT NULL DEFAULT (1)
GO

-- 32K rows / table     CPU time = 61112 ms,    elapsed time = 5507 ms.
-- 64K rows / table     CPU time = 274955 ms,   elapsed time = 19695 ms.
-- Since the time scales quadratically once we have enough rows to efficiently utilize
-- all threads, we can predict 4MM rows / table with the following query:
--      SELECT POWER(4000000 / 64000, 2) * (19. / 3600)
-- 4MM rows / table     Estimated elapsed time = 20 hours
SELECT COUNT_BIG(*), SUM(1.0 * t1.c1 * t2.c1 * t1.c2 * t2.c2)
FROM #t1 t1
JOIN #t2 t2
    /* Add an equijoin logically equivalent to 1=1 in order to enable batch mode
        hash join to replace a loop join for this "cross join" */
    ON t2.one = t1.one
WHERE NOT EXISTS (
    SELECT *
    FROM #t3 t3
    WHERE t3.c1 = t1.c1
        AND t3.c2 = t2.c2
)
    /* Enable batch mode hash join  */
    AND NOT EXISTS (SELECT * FROM #batchMode WHERE 0=1)
OPTION (MAXDOP 16)
GO

Voici le plan de requête correspondant avec 32K lignes/table:

enter image description here

3
Geoff Patterson

Cela devrait fonctionner pour vous. Vous devez également définir explicitement votre syntaxe de jointure. Je l'ai fait ici, mais cela peut être incorrect ou incomplet, mais votre échantillon aussi.

J'ai également inclus un exemple utilisant une table dérivée comme table T3. Votre T3 pourrait être une requête plus complexe avec ses jointures si vous le souhaitez.

SELECT T1.C1, T2.C1, T1.C2, T2.C2
FROM T1
    INNER JOIN T2 ON T2.C1 = T1.C1 AND T2.C2 = T1.C2
    LEFT OUTER JOIN (SELECT C1, C2 FROM T3) AS T3 ON T3.C1 = T1.C1 AND T3.C2 = T2.C1
WHERE T3.C1 IS NULL
0
Jonathan Fite

Je ne sais pas exactement ce que vous cherchez. Mais sans voir des exemples d'entrée et de sortie, cela fonctionnerait-il?

Select t1.c1, t1.c2
from t1
where t1.c1 not in (select c1 from t3)


union

Select t2.c1, t2.c2
from t2
where t2.c1 not in (select c2 from t3)
0
Anthony Genovese