Was ist die effizienteste Methode, um unter SQL Server 2005 das Minimum an mehreren Spalten abzurufen?


29

Ich bin in einer Situation, in der ich den Mindestwert von 6 Spalten erhalten möchte.

Bisher habe ich drei Möglichkeiten gefunden, um dies zu erreichen, aber ich habe Bedenken hinsichtlich der Leistung dieser Methoden und möchte wissen, welche für die Leistung besser wären.

Die erste Methode ist die Verwendung einer großen case-Anweisung . Hier ist ein Beispiel mit 3 Spalten, basierend auf dem Beispiel im obigen Link. Meine Fallbeschreibung wäre viel länger, da ich 6 Spalten betrachten werde.

Select Id,
       Case When Col1 <= Col2 And Col1 <= Col3 Then Col1
            When Col2 <= Col3 Then Col2 
            Else Col3
            End As TheMin
From   MyTable

Die zweite Möglichkeit besteht darin, den UNIONOperator mit mehreren select-Anweisungen zu verwenden . Ich würde dies in eine UDF setzen, die einen Id-Parameter akzeptiert.

select Id, dbo.GetMinimumFromMyTable(Id)
from MyTable

und

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

Und die dritte Option, die ich gefunden habe, war die Verwendung des UNPIVOT-Operators , von dem ich bis jetzt noch gar nicht wusste, dass er existiert

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

Aufgrund der Tabellengröße und Häufigkeit, mit der diese Tabelle abgefragt und aktualisiert wird, bin ich besorgt über die Auswirkungen auf die Leistung, die diese Abfragen auf die Datenbank haben würden.

Diese Abfrage wird tatsächlich in einem Join zu einer Tabelle mit einigen Millionen Datensätzen verwendet. Die zurückgegebenen Datensätze werden jedoch auf jeweils rund hundert Datensätze reduziert. Es wird den ganzen Tag über mehrmals ausgeführt und die 6 von mir abgefragten Spalten werden regelmäßig aktualisiert (sie enthalten tägliche Statistiken). Ich denke nicht, dass es irgendwelche Indizes für die 6 Spalten gibt, die ich abfrage.

Welche dieser Methoden ist für die Leistung besser, wenn Sie versuchen, das Minimum mehrerer Spalten abzurufen? Oder gibt es eine andere bessere Methode, die ich nicht kenne?

Ich verwende SQL Server 2005

Probendaten & Ergebnisse

Wenn meine Daten Aufzeichnungen wie diese enthielten:

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

Das Endergebnis sollte sein

ID-Wert
1 0
2 2
3 1
4 4

Antworten:


22

Ich habe die Leistung aller drei Methoden getestet und Folgendes festgestellt:

  • 1 Datensatz: Kein merklicher Unterschied
  • 10 Datensätze: Kein merklicher Unterschied
  • 1.000 Datensätze: Kein merklicher Unterschied
  • 10.000 Datensätze: UNIONUnterabfrage war etwas langsamer. Die CASE WHENAbfrage ist etwas schneller als die UNPIVOTeine.
  • 100.000 Datensätze: Die UNIONUnterabfrage ist erheblich langsamer, die UNPIVOTAbfrage wird jedoch etwas schneller als die CASE WHENAbfrage
  • 500.000 Datensätze: UNIONUnterabfrage ist immer noch erheblich langsamer, UNPIVOTwird jedoch viel schneller als die CASE WHENAbfrage

Das Endergebnis scheint also zu sein

  • Bei kleineren Datensätzen scheint es keinen ausreichenden Unterschied zur Materie zu geben. Verwenden Sie das, was am einfachsten zu lesen und zu warten ist.

  • Sobald Sie größere Datensätze UNION ALLerstellen , ist die Leistung der Unterabfrage im Vergleich zu den beiden anderen Methoden schlecht.

  • Die CASEAnweisung liefert die beste Leistung bis zu einem bestimmten Punkt (in meinem Fall ungefähr 100.000 Zeilen), und an welchem ​​Punkt wird die UNPIVOTAbfrage zur Abfrage mit der besten Leistung

Die tatsächliche Anzahl, bei der eine Abfrage besser als eine andere ist, wird sich wahrscheinlich aufgrund Ihrer Hardware, Ihres Datenbankschemas, Ihrer Daten und der aktuellen Serverauslastung ändern. Testen Sie daher auf jeden Fall Ihr eigenes System, wenn Sie sich Sorgen um die Leistung machen.

Ich habe auch einige Tests mit Mikaels Antwort durchgeführt . Es war jedoch langsamer als alle drei anderen Methoden, die hier für die meisten Datensatzgrößen verwendet wurden. Die einzige Ausnahme war, dass es besser lief als die UNION ALLAbfrage nach sehr großen Recordset-Größen. Ich mag die Tatsache, dass der Spaltenname zusätzlich zum kleinsten Wert angezeigt wird.

Ich bin kein DBA, daher habe ich meine Tests möglicherweise nicht optimiert und etwas verpasst. Ich habe mit den tatsächlichen Live-Daten getestet, sodass die Ergebnisse möglicherweise beeinträchtigt wurden. Ich habe versucht, das zu erklären, indem ich jede Abfrage ein paar Mal anders ausgeführt habe, aber Sie wissen es nie. Es würde mich auf jeden Fall interessieren, wenn jemand einen sauberen Test dazu aufschreibt und seine Ergebnisse mitteilt.


6

Sie wissen nicht, was am schnellsten ist, aber Sie könnten so etwas versuchen.

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

Ergebnis:

ColName ColValue
------- -----------
Col1    1
Col3    1

Wenn Sie sich nicht dafür interessieren, welche Spalte den Mindestwert hat, können Sie dies stattdessen verwenden.

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

Eine vereinfachte Unpivot-Abfrage.

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

Fügen Sie eine dauerhaft berechnete Spalte hinzu, die eine CASEAnweisung verwendet, um die erforderliche Logik auszuführen.

Der Mindestwert ist dann immer effizient verfügbar, wenn Sie einen Join (oder was auch immer) basierend auf diesem Wert ausführen müssen.

Der Wert wird jedes Mal neu berechnet, wenn sich die Quellwerte ändern ( INSERT/ UPDATE/ MERGE). Ich sage nicht , dass dies unbedingt die beste Lösung für die Arbeitsbelastung, ich nur als biete eine Lösung, ebenso wie die anderen Antworten. Nur das OP kann bestimmen, welches für die Arbeitsbelastung am besten ist.


1

Fallbeschreibung für 6 Daten. Um weniger zu tun, kopieren Sie die echte Verzweigung aus der ersten case-Anweisung. Der schlimmste Fall ist, wenn Datum1 der niedrigste Wert ist. Der beste Fall ist, wenn Datum6 der niedrigste Wert ist. Fügen Sie daher das wahrscheinlichste Datum in Datum6 ein. Ich habe dies aufgrund der Einschränkungen von berechneten Spalten geschrieben.

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

Wenn Sie auf diese Seite gestoßen sind, um nur Daten zu vergleichen, und sich nicht um Leistung oder Kompatibilität kümmern möchten, können Sie einen Tabellenwertkonstruktor verwenden, der überall dort verwendet werden kann, wo Unterauswahlen zulässig sind (SQL Server 2008 und höher):

Lowest =    
(
    SELECT MIN(TVC.d) 
    FROM 
    (
        VALUES
            (Date1), 
            (Date2), 
            (Date3), 
            (Date4), 
            (Date5), 
            (Date6)
    ) 
    AS TVC(d)
)

1

Ihre caseAussage ist nicht effizient. Sie führen im schlimmsten Fall 5 Vergleiche durch und im besten Fall 2; Das Minimum von zu finden, nsollte höchstens n-1Vergleiche bringen.

Im Durchschnitt führen Sie für jede Zeile 3,5 statt 2 Vergleiche durch. Dies kostet mehr CPU-Zeit und ist langsam. Versuchen Sie Ihre Tests mit der folgenden caseAnweisung erneut. Es werden nur 2 Vergleiche pro Zeile verwendet und es sollte effizienter als unpivotund sein 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

Die union allMethode ist in Ihrem Fall falsch, da Sie den Mindestwert nicht pro Zeile, sondern für die gesamte Tabelle erhalten. Es ist auch nicht effizient, da Sie den gleichen Tisch dreimal scannen werden. Wenn die Tabelle klein ist, macht die E / A keinen großen Unterschied, bei großen Tabellen jedoch. Verwenden Sie diese Methode nicht.

Unpivotist gut und versuchen Sie es auch manuell, indem Sie cross join your table with verwenden (select 1 union all select 2 union all select 3). Es sollte so effizient sein wie das unpivot.

Die beste Lösung wäre eine berechnete persistierte Spalte, wenn Sie keine Speicherplatzprobleme haben. Es wird die Zeilengröße um 4 Bytes interhöhen (ich nehme an, Sie werden Typ haben), was wiederum die Größe der Tabelle erhöht.

Speicherplatz und Arbeitsspeicher sind jedoch ein Problem in Ihrem System, und die CPU wird dann nicht dauerhaft aktiviert, sondern es wird eine einfach berechnete Spalte mit der case-Anweisung verwendet. Dadurch wird der Code einfacher.


-1

Ich denke, dass die erste Option am schnellsten ist (obwohl sie aus Programmierperspektive nicht sehr geschickt aussieht!). Dies liegt daran, dass es sich um genau N Zeilen handelt (wobei N die Tabellengröße ist) und keine Suche oder Sortierung wie bei Methode 2 oder 3 durchführen muss.

Ein Test mit großer Stichprobe sollte dies belegen.

Eine weitere Option, die Sie in Betracht ziehen sollten (als ob Sie mehr benötigen!), Besteht darin, eine materialisierte Ansicht über Ihre Tabelle zu erstellen . wenn Ihre Tischgröße in Hunderttausenden oder mehr ist. Auf diese Weise wird der min-Wert berechnet, während die Zeile geändert wird, und die gesamte Tabelle müsste nicht bei jeder Abfrage verarbeitet werden. In SQL Server werden materialisierte Ansichten als indizierte Ansichten bezeichnet


-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

Sie berücksichtigen keine NULL-Werte - das macht Ihren CASE-Ausdruck relativ einfach. Wenn jedoch mindestens eine der Spalten tatsächlich NULL ist, wird Ihre Lösung Year1als Ergebnis zurückgegeben, was möglicherweise nicht korrekt ist.
Andriy M
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.