web-dev-qa-db-ja.com

SQL Serverの別のテーブルに列の組み合わせが存在するかどうかを確認するにはどうすればよいですか?

これは、MySQLまたは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)

SQL Serverで同じクエリをどのように表現できますか?

私はそれらを文字列として連結できると考えましたが、それを行う最良の方法ではありません。

SQL Serverでうまく実行する方法が見つかりませんでした。

各テーブルには数百万のレコードが含まれているため、もう一度結合することはできません。

問題を簡単に想像できるようにテーブルの例を示します。

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

SQL Serverの結合は、ネストされたループとして自動的に実装されません。たとえば、ハッシュ結合を使用してNOT INを実装できます。これは、結合される3つのテーブルのそれぞれに10 ^ 6行を超える行がある場合でも、クエリが必ずしも「10 ^ 18を超える数のタプルになる」とは限らないことを意味します。

たとえば、以下は、データ量を削減したクエリプランです。クエリプランの任意の時点での行の最大数は、t1のサイズにt2のサイズを掛けたものに比例しますが、t3のサイズとは無関係です。 。

enter image description here

それでも、10兆行を選択するクエリ(おそらくそれらを集約するため、または列ストアテーブルに挿入するため)の実行にかなりの時間がかかるという事実は回避できません。以下は、完全なスクリプトと詳細な分析です。

サンプルデータを作成

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

クエリを実行します

異なる行数でクエリを実行すると、t1t2の行数でクエリのパフォーマンスが2次的に(N ^ 2)向上しますが、t3の行数による大きな影響はありません。したがって、16兆行のクロス乗積全体を使用して実行時間を推定すると、約217時間になります。

-- 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

ループ結合をバッチモードハッシュ結合に置き換えます

このクエリは非常にCPUを集中的に使用するため、作業の大部分(t1t2の間の行のクロス積を構築するためのループ結合)を、バッチモードの実行をサポートする演算子で置き換えることができるかどうかを検討するのは当然です。これを行う1つの方法は、クロス結合を、各テーブルのすべての行が同じ値を持つ単一の列の等価結合として再実装することです。

これにより、予想される時間を約10分の1に短縮し、約20時間で、クロス積によって生成された16兆の中間行をフィルタリングして集計します。

-- 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

32K行/テーブルの対応するクエリプランは次のとおりです。

enter image description here

3
Geoff Patterson

これでうまくいくはずです。また、結合構文も明示的に定義する必要があります。私はここでそれを行いましたが、それは不正確または不完全かもしれませんが、それであなたのサンプルもそうです。

T3テーブルとして派生テーブルを使用する例も含めました。 T3は、必要に応じて、独自の結合を持つより複雑なクエリにすることができます。

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

あなたが何を探しているのか正確にはわかりません。しかし、入力と出力の例を見なければ、これはうまくいくでしょうか?

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