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


29

6列の最小値を取得したい状況です。

これを達成するためにこれまでに3つの方法を見つけましたが、これらの方法のパフォーマンスに懸念があり、どちらがパフォーマンスに優れているかを知りたいと思います。

最初の方法は、大きなcaseステートメントを使用することです。上記のリンクの例に基づいて、3列の例を次に示します。6つの列を見るので、私のcaseステートメントはもっと長くなります。

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

そして、私が見つけた第三の選択肢はしたUNPIVOT演算子使用し、私も今まで存在していた知りませんでした、

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

回答:


22

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

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

したがって、最終結果は

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

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

  • CASEステートメントを実行する最良の(私の場合は、周り100K行)特定の時点まで、ポイントUNPIVOTクエリは最高の実行クエリになり

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

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

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


6

何が最速かはわかりませんが、このようなことを試すことができます。

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

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

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

値は、ソース値のいずれかが変更されるたびに再計算されます(INSERT/ UPDATE/ MERGE)。私はこれが必ずしもワークロードにとって最良のソリューションであると言っているのではなく、他の答えと同じように、単にソリューションとして提供しているだけです。OPのみが、どちらがワークロードに最適であるかを判断できます。


1

6つの日付のケースステートメント。これを減らすには、最初のcaseステートメントから真のブランチをコピーします。最悪の場合は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

あなたのcase声明は効率的ではありません。最悪の場合は5回、最良の場合は2回比較しています。一方、最小値を見つけることはn多くてもn-1比較する必要があります。

各行について、平均で2回ではなく3.5回の比較を行っています。したがって、CPU時間をより多く消費し、速度が低下します。以下のcaseステートメントを使用してテストを再試行してください。行ごとに2つの比較を使用しているだけで、unpivotand よりも効率的です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

最初のオプションが最速だと思います(ただし、プログラミングの観点からはあまり見栄えが良くありません!)。これは、正確にN行(Nはテーブルサイズ)を処理し、方法2または3のような検索または並べ替えを行う必要がないためです。

大規模なサンプルでのテストがポイントを証明するはずです。

(さらに必要な場合と同様に)検討すべきもう1つのオプションとして、テーブル上にマテリアライズドビューを作成することがあります。テーブルサイズが数十万以上の場合。これにより、行が変更されている間に最小値が計算され、すべてのクエリでテーブル全体を処理する必要がなくなります。SQL Serverでは、マテリアライズドビューはインデックス付きビューと呼ばれます


-1
Create table #temp
   (
    id int identity(1,1),
    Name varchar(30),
    Year1 int,
    Year2 int,
    Year3 int,
    Year4 int
   )

   Insert into #temp values ('A' ,2015,2016,2014,2010)
   Insert into #temp values ('B' ,2016,2013,2017,2018)
   Insert into #temp values ('C' ,2010,2016,2014,2017)
   Insert into #temp values ('D' ,2017,2016,2014,2015)
   Insert into #temp values ('E' ,2016,2016,2016,2016)
   Insert into #temp values ('F' ,2016,2017,2018,2019)
   Insert into #temp values ('G' ,2016,2017,2020,2019)

   Select *, Case 
                 when Year1 >= Year2 and Year1 >= Year3 and Year1 >= Year4 then Year1
                 when Year2 >= Year3 and Year2 >= Year4 and Year2 >= Year1 then Year2
                 when Year3 >= Year4 and Year3 >= Year1 and Year3 >= Year2 then Year3
                 when Year4 >= Year1 and Year4 >= Year2 and Year4 >= Year3 then Year4  
                 else Year1 end as maxscore  
                 from #temp

NULLを考慮していないため、CASE式は比較的単純になります。ただし、少なくとも1つの列が実際にNULLである場合、ソリューションはYear1結果として返されますが、必ずしも正しいとは限りません。
アンドリーM
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.