6列の最小値を取得したい状況です。
これを達成するためにこれまでに3つの方法を見つけましたが、これらのメソッドのパフォーマンスに懸念があり、どちらがパフォーマンスに優れているか知りたいのです。
最初の方法は 大きなケースステートメント を使用することです。上記のリンクの例に基づいた、3列の例を次に示します。 6つの列を見るので、私のケースステートメントははるかに長くなります。
Select Id,
Case When Col1 <= Col2 And Col1 <= Col3 Then Col1
When Col2 <= Col3 Then Col2
Else Col3
End As TheMin
From MyTable
2番目のオプションは、 UNION
演算子と複数の選択ステートメント を使用することです。これを、Idパラメーターを受け入れるUDFに入れます。
select Id, dbo.GetMinimumFromMyTable(Id)
from MyTable
そして
select min(col)
from
(
select col1 [col] from MyTable where Id = @id
union all
select col2 from MyTable where Id = @id
union all
select col3 from MyTable where Id = @id
) as t
そして、私が見つけた3番目のオプションは NPIVOT演算子を使用 でした。これは、今まで存在することさえ知りませんでした。
with cte (ID, Col1, Col2, Col3)
as
(
select ID, Col1, Col2, Col3
from TestTable
)
select cte.ID, Col1, Col2, Col3, TheMin from cte
join
(
select
ID, min(Amount) as TheMin
from
cte
UNPIVOT (Amount for AmountCol in (Col1, Col2, Col3)) as unpvt
group by ID
) as minValues
on cte.ID = minValues.ID
テーブルのサイズと、このテーブルがクエリおよび更新される頻度のため、これらのクエリがデータベースに与えるパフォーマンスの影響について心配しています。
このクエリは、実際には数百万レコードのテーブルへの結合で使用されますが、返されるレコードは一度に約100レコードに削減されます。それは1日を通して何度も実行され、私が照会している6つの列は頻繁に更新されます(毎日の統計が含まれています)。クエリしている6つの列にインデックスがないと思います。
複数の列を最小限にしようとする場合、これらの方法のどれがパフォーマンスに優れていますか?または、私が知らない別のより良い方法はありますか?
SQL Server 2005を使用しています
サンプルデータと結果
データに次のようなレコードが含まれている場合:
Id Col1 Col2 Col3 Col4 Col5 Col6 1 3 4 0 2 1 5 2 2 6 10 5 7 9 3 1 1 2 3 4 5 4 9 5 4 6 8 9
最終結果は
Id値 1 0 2 2 3 1 4 4
私は3つの方法すべてのパフォーマンスをテストしましたが、次のことがわかりました。
UNION
サブクエリは少し遅くなりました。 CASE WHEN
クエリは、UNPIVOT
クエリよりも少し高速です。UNION
サブクエリは大幅に遅くなりますが、UNPIVOT
クエリはCASE WHEN
クエリよりも少し速くなりますUNION
サブクエリは依然として大幅に遅くなりますが、UNPIVOT
はCASE WHEN
クエリよりもはるかに高速になりますしたがって、最終結果は
レコードセットが小さいと、重要な違いが十分にないように見えます。読みやすく、保守しやすいものを使用してください。
より大きなレコードセットに入ると、UNION ALL
サブクエリは、他の2つのメソッドと比較してパフォーマンスが低下し始めます。
CASE
ステートメントは、特定のポイント(私の場合、約10万行)まで最高のパフォーマンスを発揮し、そのポイントでUNPIVOT
クエリが最もパフォーマンスの高いクエリになります
ハードウェア、データベーススキーマ、データ、および現在のサーバーの負荷の結果として、1つのクエリが別のクエリよりも良くなる実際の数はおそらく変化するので、パフォーマンスが心配な場合は、独自のシステムでテストしてください。
私は ミカエルの答え ;を使用していくつかのテストも実行しました。ただし、ほとんどのレコードセットサイズでここで試した他の3つの方法すべてよりも低速でした。唯一の例外は、非常に大きなレコードセットサイズのUNION ALL
クエリよりも優れていたことです。私はそれが最小値に加えて列名を表示するという事実が好きです。
私はdbaではないので、テストを最適化しておらず、何かを見逃している可能性があります。実際のライブデータでテストしていたため、結果に影響が出た可能性があります。私は各クエリを数回実行することでそれを説明しようとしましたが、あなたは決して知りません。誰かがこれについてのクリーンなテストを書き、その結果を共有してくれたら、私は間違いなく興味があります。
何が最速かわからないが、このようなものを試すことができます。
declare @T table
(
Col1 int,
Col2 int,
Col3 int,
Col4 int,
Col5 int,
Col6 int
)
insert into @T values(1, 2, 3, 4, 5, 6)
insert into @T values(2, 3, 1, 4, 5, 6)
select T4.ColName, T4.ColValue
from @T as T1
cross apply (
select T3.ColValue, T3.ColName
from (
select row_number() over(order by T2.ColValue) as rn,
T2.ColValue,
T2.ColName
from (
select T1.Col1, 'Col1' union all
select T1.Col2, 'Col2' union all
select T1.Col3, 'Col3' union all
select T1.Col4, 'Col4' union all
select T1.Col5, 'Col5' union all
select T1.Col6, 'Col6'
) as T2(ColValue, ColName)
) as T3
where T3.rn = 1
) as T4
結果:
ColName ColValue
------- -----------
Col1 1
Col3 1
どの列が最小値を持っているか興味がない場合は、代わりにこれを使用できます。
declare @T table
(
Id int,
Col1 int,
Col2 int,
Col3 int,
Col4 int,
Col5 int,
Col6 int
)
insert into @T
select 1, 3, 4, 0, 2, 1, 5 union all
select 2, 2, 6, 10, 5, 7, 9 union all
select 3, 1, 1, 2, 3, 4, 5 union all
select 4, 9, 5, 4, 6, 8, 9
select T.Id, (select min(T1.ColValue)
from (
select T.Col1 union all
select T.Col2 union all
select T.Col3 union all
select T.Col4 union all
select T.Col5 union all
select T.Col6
) as T1(ColValue)
) as ColValue
from @T as T
単純化されたアンピボットクエリ。
select Id, min(ColValue) as ColValue
from @T
unpivot (ColValue for Col in (Col1, Col2, Col3, Col4, Col5, Col6)) as U
group by Id
CASE
ステートメントを使用して必要なロジックを実行する永続的な計算列を追加します。
最小値は、その値に基づいて結合(またはその他)を実行する必要がある場合に常に効率的に使用できます。
値は、ソース値が変更されるたびに再計算されます(INSERT
/UPDATE
/MERGE
)。これが必ずしもワークロードに最適なソリューションであると言っているわけではありません。他の回答と同様に、単にaソリューションとして提供しています。 OPのみが、ワークロードに最適なものを決定できます。
case
ステートメントは効率的ではありません。最悪の場合は5回、最良の場合は2回の比較を行っています。一方、最小のn
を検索すると、最大でn-1
の比較が行われます。
各行について、平均で2ではなく3.5の比較を行っています。したがって、CPU時間をより多く要し、速度が遅くなります。以下のcase
ステートメントを使用して、テストを再試行してください。行ごとに2つの比較を使用しているだけで、unpivot
およびunion all
よりも効率的です。
Select Id,
Case
When Col1 <= Col2 then case when Col1 <= Col3 Then Col1 else col3 end
When Col2 <= Col3 Then Col2
Else Col3
End As TheMin
From YourTableNameHere
union all
メソッドは、行ごとではなくテーブル全体の最小値を取得しているため、ケースでは間違っています。また、同じテーブルを3回スキャンするため、効率的ではありません。テーブルが小さい場合、I/Oは大きな違いはありませんが、大きなテーブルの場合は違いがあります。その方法は使用しないでください。
Unpivot
は適切であり、(select 1 union all select 2 union all select 3)
を使用したテーブルのクロス結合を使用して、手動でアンピボットを試みます。 unpivot
と同じくらい効率的でなければなりません。
スペースの問題がない場合、最適な解決策は、計算された永続化列を使用することです。行のサイズに4バイト追加されます(int
タイプになると思います)。これにより、テーブルのサイズが増加します。
ただし、システムのスペースとメモリの問題があり、CPUはそれを永続化せず、caseステートメントを使用して単純な計算列を使用します。それはコードをより簡単にします。
6つの日付のケースステートメント。作業を減らすには、最初のcaseステートメントからtrueブランチをコピーします。最悪のケースは、Date1が最も低い値である場合で、最良のケースは、Date6が最も低い値である場合です。したがって、最も可能性の高い日付をDate6に入れます。計算列の制限のため、これを書いた。
CASE WHEN Date1 IS NULL OR Date1 > Date2 THEN
CASE WHEN Date2 IS NULL OR Date2 > Date3 THEN
CASE WHEN Date3 IS NULL OR Date3 > Date4 THEN
CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
Date6
ELSE
Date4
END
END
ELSE
CASE WHEN Date3 IS NULL OR Date3 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date3 IS NULL OR Date3 > Date6 THEN
Date6
ELSE
Date3
END
END
END
ELSE
CASE WHEN Date2 IS NULL OR Date2 > Date4 THEN
CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
Date6
ELSE
Date4
END
END
END
ELSE
CASE WHEN Date2 IS NULL OR Date2 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date2 IS NULL OR Date2 > Date6 THEN
Date6
ELSE
Date2
END
END
END
END
ELSE
CASE WHEN Date1 IS NULL OR Date1 > Date3 THEN
CASE WHEN Date3 IS NULL OR Date3 > Date4 THEN
CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
Date6
ELSE
Date4
END
END
ELSE
CASE WHEN Date3 IS NULL OR Date3 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date3 IS NULL OR Date3 > Date6 THEN
Date6
ELSE
Date3
END
END
END
ELSE
CASE WHEN Date1 IS NULL OR Date1 > Date4 THEN
CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
Date6
ELSE
Date4
END
END
ELSE
CASE WHEN Date1 IS NULL OR Date1 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date1 IS NULL OR Date1 > Date6 THEN
Date6
ELSE
Date1
END
END
END
END
END
日付の比較だけを目的としていて、パフォーマンスや互換性についてそれほど気にしていないこのページに出くわした場合は、副選択が許可されている場合はいつでも使用できるテーブル値コンストラクターを使用できます(SQL Server 2008以降)。
Lowest =
(
SELECT MIN(TVC.d)
FROM
(
VALUES
(Date1),
(Date2),
(Date3),
(Date4),
(Date5),
(Date6)
)
AS TVC(d)
)