web-dev-qa-db-ja.com

SQL Server 2005で複数の列を最小限に抑える最も効率的な方法は何ですか?

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

私は3つの方法すべてのパフォーマンスをテストしましたが、次のことがわかりました。

  • 1件の記録:目立った違いはありません
  • 10件のレコード:目立った違いはありません
  • 1,000レコード:目立った違いはありません
  • 10,000レコード:UNIONサブクエリは少し遅くなりました。 CASE WHENクエリは、UNPIVOTクエリよりも少し高速です。
  • 100,000レコード:UNIONサブクエリは大幅に遅くなりますが、UNPIVOTクエリはCASE WHENクエリよりも少し速くなります
  • 500,000レコード:UNIONサブクエリは依然として大幅に遅くなりますが、UNPIVOTCASE WHENクエリよりもはるかに高速になります

したがって、最終結果は

  • レコードセットが小さいと、重要な違いが十分にないように見えます。読みやすく、保守しやすいものを使用してください。

  • より大きなレコードセットに入ると、UNION ALLサブクエリは、他の2つのメソッドと比較してパフォーマンスが低下し始めます。

  • CASEステートメントは、特定のポイント(私の場合、約10万行)まで最高のパフォーマンスを発揮し、そのポイントでUNPIVOTクエリが最もパフォーマンスの高いクエリになります

ハードウェア、データベーススキーマ、データ、および現在のサーバーの負荷の結果として、1つのクエリが別のクエリよりも良くなる実際の数はおそらく変化するので、パフォーマンスが心配な場合は、独自のシステムでテストしてください。

私は ミカエルの答え ;を使用していくつかのテストも実行しました。ただし、ほとんどのレコードセットサイズでここで試した他の3つの方法すべてよりも低速でした。唯一の例外は、非常に大きなレコードセットサイズのUNION ALLクエリよりも優れていたことです。私はそれが最小値に加えて列名を表示するという事実が好きです。

私はdbaではないので、テストを最適化しておらず、何かを見逃している可能性があります。実際のライブデータでテストしていたため、結果に影響が出た可能性があります。私は各クエリを数回実行することでそれを説明しようとしましたが、あなたは決して知りません。誰かがこれについてのクリーンなテストを書き、その結果を共有してくれたら、私は間違いなく興味があります。

22
Rachel

何が最速かわからないが、このようなものを試すことができます。

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
6
Mikael Eriksson

CASEステートメントを使用して必要なロジックを実行する永続的な計算列を追加します。

最小値は、その値に基づいて結合(またはその他)を実行する必要がある場合に常に効率的に使用できます。

値は、ソース値が変更されるたびに再計算されます(INSERT/UPDATE/MERGE)。これが必ずしもワークロードに最適なソリューションであると言っているわけではありません。他の回答と同様に、単にaソリューションとして提供しています。 OPのみが、ワークロードに最適なものを決定できます。

6
Jon Seigel

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ステートメントを使用して単純な計算列を使用します。それはコードをより簡単にします。

1
Gulli Meel

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)
)
1
Jesse Adam