基本的にキーまたはIDである列値に基づいて、マッピングIDテーブルから対応する値をフェッチする必要がある状況でレポートを実行します。以下のようなもの:
SELECT
(case when [column1='A'] then (select value from Table1)
when [column1='B'] then (select value from Table2)
when [column1='C'] then (select value from Table3)
and so on uptil 35 more 'when' conditions ...
ELSE column1 end) Value
from Table1
より正確に:
SELECT
(case when [A.column1='1']
then (select value from B where B.clientId=100 and A.column1=B.Id)
when [A.column1='2']
then (select value from C where C.clientId=100 and A.column1=C.Id)
when [A.column1='3']
then (select value from D where D.clientId=100 and A.column1=D.Id)
...
and so on uptil 30 more 'when' conditions
...
ELSE column1 end)
FROM A
テーブルB、C、D ..などでは、すべてのクライアントのデータを保持しています。各クライアントには特定のClientId
があり、これらのテーブルB、C、DなどにはId
列とClientId
列にインデックスが設定されています。
SQL Serverでこれを処理する効率的な方法はありますか?
私は、サブクエリのテーブルに適切なインデックスがあると想定しています。いくつかのクイックテストデータをモックアップして、テーブルA
に1000万行を入れました。私は30個のテーブルを作成するゲームではなかったので、CASE
式用に3個を作成しました。一般的な原則を示すには3で十分だと思います。
_DROP TABLE IF EXISTS dbo.B;
CREATE TABLE dbo.B (
ClientID INT NOT NULL,
Id VARCHAR(20) NOT NULL,
[Value] VARCHAR(100),
PRIMARY KEY (ClientID, Id)
);
INSERT INTO B VALUES (100, '1', 'TABLE B');
DROP TABLE IF EXISTS dbo.C;
CREATE TABLE dbo.C (
ClientID INT NOT NULL,
Id VARCHAR(20) NOT NULL,
[Value] VARCHAR(100),
PRIMARY KEY (ClientID, Id)
);
INSERT INTO C VALUES (100, '2', 'TABLE C');
DROP TABLE IF EXISTS dbo.D;
CREATE TABLE dbo.D (
ClientID INT NOT NULL,
Id VARCHAR(20) NOT NULL,
[Value] VARCHAR(100),
PRIMARY KEY (ClientID, Id)
);
INSERT INTO D VALUES (100, '3', 'TABLE D');
DROP TABLE IF EXISTS dbo.A;
CREATE TABLE dbo.A (
column1 VARCHAR(20) NOT NULL
);
INSERT INTO dbo.A WITH (TABLOCK)
SELECT CAST(1 + t.RN % 3 AS VARCHAR(20))
FROM
(
SELECT TOP (5000000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
) t;
_
結果セットを無効にして、SSMSで次のクエリを実行しました。
_SELECT A.column1
FROM A;
_
約0.723秒かかりました。私はあなたのデータを何も知らないので、かなり非科学的なテストをしています。いずれにせよ、シリアルクエリでは0.7秒以上の結果は期待できません。それが私たちのベースラインです。
このクエリを作成する最も効率的な方法は、結合をまったく使用しない方法です。重要なのは、CASE
式は、一致が見つかった場合にのみ3(または30)の一意の値を返すことです。結果をローカル変数に保存して、クエリで使用することができます。以下のクエリは約1.044秒で終了します。
_DECLARE @B_VALUE VARCHAR(100) = (select value from B where B.clientId=100 and B.Id = '1');
DECLARE @C_VALUE VARCHAR(100) = (select value from C where C.clientId=100 and C.Id = '2');
DECLARE @D_VALUE VARCHAR(100) = (select value from D where D.clientId=100 and D.Id = '3');
SELECT
(case when A.column1='1' then @B_VALUE
when A.column1='2' then @C_VALUE
when A.column1='3' then @D_VALUE
-- omitted other columns
else column1 end)
FROM A;
_
計画は非常に簡単です:
別のオプションとして、結合を使用してクエリを記述できます(CASE
式をCOALESCE()
を使用してよりコンパクトな形式に書き換えることができます。これは約2.314秒で終了しました:
_SELECT
COALESCE(B.column1, C.column1, D.column1, -- omitted other columns
A.column1)
-- (case A.column1
-- when '1' then B.value
-- when '2' then C.value
-- when '3' then D.value
-- -- omitted other columns
-- else A.column1 end)
FROM A
LEFT JOIN B ON B.clientId=100 and B.Id = '1'
LEFT JOIN C ON C.clientId=100 and C.Id = '2'
LEFT JOIN D ON D.clientId=100 and D.Id = '3';
_
ここに計画があります:
次のようなクエリを記述することで、ほぼ同じランタイムとクエリプランを取得できます。
_SELECT
(case A.column1
when '1' then (select value from B where B.clientId=100 and '1'=B.Id)
when '2' then (select value from C where C.clientId=100 and '2'=C.Id)
when '3' then (select value from D where D.clientId=100 and '3'=D.Id)
-- omitted other columns
else column1 end)
FROM A;
_
問題の元のクエリには問題があります。SQLServerはネストされたループ結合の前に無用なソートを行っています。このクエリは、私のマシンでは約5.838秒で終了します。
トレースフラグ8690は、並べ替えとテーブルスプールを削除します。クエリはトレースフラグ8690で約7.479秒で実行されるため、スプールはこのクエリに役立つと思います。
それらがすべて異なるテーブルである場合、これが最良のケースのシナリオである可能性があります。 UNLESS Table1.column1の値は繰り返すことができます(複数行= '1'など)。その場合、サブクエリから結合に移動することができます。
SELECT CASE
WHEN A.column1='1' THEN B.value
WHEN A.column1='2' THEN C.value
WHEN A.column1='3' THEN D.value
and so on uptil 30 more 'when' conditions
ELSE A.column1
END
FROM A
LEFT JOIN B ON A.column1='1' AND A.column1=B.Id AND B.clientId=100
LEFT JOIN C ON A.column1='2' AND A.column1=C.Id AND C.clientId=100
LEFT JOIN D ON A.column1='2' AND A.column1=D.Id AND D.clientId=100