it-swarm.com.de

Wie kann der Mindestwert aus mehreren Spalten am besten ausgewählt werden?

Ausgehend von der folgenden Tabelle in SQL Server 2005:

ID   Col1   Col2   Col3
--   ----   ----   ----
1       3     34     76  
2      32    976     24
3       7    235      3
4     245      1    792

Was ist der beste Weg, um die Abfrage zu schreiben, die das folgende Ergebnis liefert (d. H. Eine, die die letzte Spalte liefert - eine Spalte mit den Mindestwerten aus Col1, Col2 und Col 3 für jede Zeile)?

ID   Col1   Col2   Col3  TheMin
--   ----   ----   ----  ------
1       3     34     76       3
2      32    976     24      24
3       7    235      3       3
4     245      1    792       1

UPDATE:

Zur Verdeutlichung (wie ich in den Kommentaren gesagt habe) ist die Datenbank im realen Szenario richtig normalisiert. Diese "Array" -Spalten befinden sich nicht in einer tatsächlichen Tabelle, sondern in einer Ergebnismenge, die in einem Bericht erforderlich ist. Neu ist, dass der Bericht auch diese MinValue-Spalte benötigt. Ich kann die zugrunde liegende Ergebnismenge nicht ändern und habe daher nach einem praktischen "Get out of Jail Card" für T-SQL gesucht.

Ich habe den unten erwähnten CASE-Ansatz ausprobiert und er funktioniert, obwohl er etwas umständlich ist. Es ist auch komplizierter als in den Antworten angegeben, da Sie die Tatsache berücksichtigen müssen, dass sich zwei min-Werte in derselben Zeile befinden.

Wie auch immer, ich dachte, ich würde meine aktuelle Lösung posten, die unter Berücksichtigung meiner Einschränkungen ziemlich gut funktioniert. Es verwendet den UNPIVOT-Operator:

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

Ich sage vorweg, dass ich nicht erwarte, dass dies die beste Leistung bietet, aber unter den gegebenen Umständen (ich kann nicht alle Abfragen nur für die neue MinValue-Spaltenanforderung umgestalten) ist es ein ziemlich elegantes "Get out of Jail" Karte".

64
stucampbell

Es gibt wahrscheinlich viele Möglichkeiten, dies zu erreichen. Mein Vorschlag ist, Case/When zu verwenden, um es zu tun. Mit 3 Spalten ist es nicht so schlimm.

Select Id,
       Case When Col1 < Col2 And Col1 < Col3 Then Col1
            When Col2 < Col1 And Col2 < Col3 Then Col2 
            Else Col3
            End As TheMin
From   YourTableNameHere
48
G Mastros

Mit CROSS APPLY:

SELECT ID, Col1, Col2, Col3, MinValue
FROM YourTable
CROSS APPLY (SELECT MIN(d) MinValue FROM (VALUES (Col1), (Col2), (Col3)) AS a(d)) A

SQL Fiddle

43
Nizam
SELECT ID, Col1, Col2, Col3, 
    (SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin
FROM Table
11
dsz

Der beste Weg dies zu tun ist wahrscheinlich nicht es zu tun - es ist seltsam, dass die Leute darauf bestehen, ihre Daten auf eine Weise zu speichern, die SQL "Gymnastik" erfordert, um sinnvolle Informationen zu extrahieren, wenn es weitaus einfacher ist Möglichkeiten, um das gewünschte Ergebnis zu erzielen, wenn Sie Ihr Schema nur ein wenig besser strukturieren :-)

Die richtig Möglichkeit, dies zu tun, besteht meiner Meinung nach darin, die folgende Tabelle zu haben:

ID    Col    Val
--    ---    ---
 1      1      3
 1      2     34
 1      3     76

 2      1     32
 2      2    976
 2      3     24

 3      1      7
 3      2    235
 3      3      3

 4      1    245
 4      2      1
 4      3    792

mit ID/Col als Primärschlüssel (und möglicherweise Col als zusätzlichen Schlüssel, abhängig von Ihren Anforderungen). Dann wird Ihre Abfrage zu einer einfachen select min(val) from tbl und Sie können die einzelnen 'alten Spalten' weiterhin separat behandeln, indem Sie in Ihren anderen Abfragen where col = 2 Verwenden. Dies ermöglicht auch eine einfache Erweiterung, falls die Anzahl der "alten Spalten" zunimmt.

Dies macht Ihre Abfragen so viel einfacher. Die allgemeine Richtlinie, die ich normalerweise verwende, lautet: Wenn Sie ever etwas haben, das wie ein Array in einer Datenbankzeile aussieht, machen Sie wahrscheinlich etwas falsch und sollten über eine Umstrukturierung der Daten nachdenken.


Wenn Sie jedoch aus irgendeinem Grund kann nicht diese Spalten ändern, würde ich vorschlagen, Einfüge- und Aktualisierungs-Trigger zu verwenden und eine andere Spalte hinzuzufügen, auf die diese Trigger eingestellt sind das Minimum auf Col1/2/3. Dadurch werden die 'Kosten' des Vorgangs von der Auswahl zu dem Update/Insert verschoben, zu dem er gehört - die meisten Datenbanktabellen werden nach meiner Erfahrung weitaus häufiger gelesen als geschrieben, sodass die Kosten für das Schreiben mit der Zeit tendenziell effizienter sind.

Mit anderen Worten, das Minimum für eine Zeile ändert sich nur, wenn sich eine der anderen Spalten ändert, also das ist wenn Sie es berechnen sollten, nicht jedes Mal, wenn Sie auswählen (was verschwendet wird, wenn die Daten nicht vorhanden sind nicht ändern). Sie würden dann mit einem Tisch enden wie:

ID   Col1   Col2   Col3   MinVal
--   ----   ----   ----   ------
 1      3     34     76        3
 2     32    976     24       24
 3      7    235      3        3
 4    245      1    792        1

Jede andere Option, die zum Zeitpunkt select Entscheidungen treffen muss, ist in der Regel eine schlechte Idee, da sich die Daten nur beim Einfügen/Aktualisieren ändern - das Hinzufügen einer weiteren Spalte nimmt mehr Platz in der Datenbank in Anspruch und wird dies auch sein etwas langsamer für die Einfügungen und Aktualisierungen, kann aber für Auswahlen viel schneller sein - der bevorzugte Ansatz sollte von Ihren Prioritäten dort abhängen, aber, wie angegeben, werden die meisten Tabellen gelesen weit öfter als geschrieben.

7
paxdiablo

Sie können den "Brute Force" -Ansatz mit einer Wendung verwenden:

SELECT CASE
    WHEN Col1 <= Col2 AND Col1 <= Col3 THEN Col1
    WHEN                  Col2 <= Col3 THEN Col2
    ELSE                                    Col3
END AS [Min Value] FROM [Your Table]

Wenn die erste when-Bedingung fehlschlägt, wird garantiert, dass Col1 nicht der kleinste Wert ist. Sie können ihn daher aus den übrigen Bedingungen entfernen. Ebenso für nachfolgende Bedingungen. Für fünf Spalten wird Ihre Abfrage zu:

SELECT CASE
    WHEN Col1 <= Col2 AND Col1 <= Col3 AND Col1 <= Col4 AND Col1 <= Col5 THEN Col1
    WHEN                  Col2 <= Col3 AND Col2 <= Col4 AND Col2 <= Col5 THEN Col2
    WHEN                                   Col3 <= Col4 AND Col3 <= Col5 THEN Col3
    WHEN                                                    Col4 <= Col5 THEN Col4
    ELSE                                                                      Col5
END AS [Min Value] FROM [Your Table]

Beachten Sie, dass bei einem Gleichstand zwischen zwei oder mehr Spalten <= stellt sicher, dass wir die Anweisung CASE so früh wie möglich beenden.

7
Salman A

Benutze das:

select least(col1, col2, col3) FROM yourtable
6
user3493139

Wenn die Spalten Ganzzahlen wären, wie in Ihrem Beispiel, würde ich eine Funktion erstellen:

create function f_min_int(@a as int, @b as int) 
returns int
as
begin
    return case when @a < @b then @a else coalesce(@b,@a) end
end

dann, wenn ich es benutzen muss, würde ich tun:

select col1, col2, col3, dbo.f_min_int(dbo.f_min_int(col1,col2),col3)

wenn Sie 5 Spalten haben, wird das oben genannte

select col1, col2, col3, col4, col5,
dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(col1,col2),col3),col4),col5)
6
Georgios

Sie können dies auch mit einer Unionsabfrage tun. Wenn die Anzahl der Spalten zunimmt, müsste die Abfrage geändert werden, zumindest ist dies eine einfache Änderung.

Select T.Id, T.Col1, T.Col2, T.Col3, A.TheMin
From   YourTable T
       Inner Join (
         Select A.Id, Min(A.Col1) As TheMin
         From   (
                Select Id, Col1
                From   YourTable

                Union All

                Select Id, Col2
                From   YourTable

                Union All

                Select Id, Col3
                From   YourTable
                ) As A
         Group By A.Id
       ) As A
       On T.Id = A.Id
5
G Mastros

Das ist rohe Gewalt, funktioniert aber

 select case when col1 <= col2 and col1 <= col3 then col1
           case when col2 <= col1 and col2 <= col3 then col2
           case when col3 <= col1 and col3 <= col2 then col3
    as 'TheMin'
           end

from Table T

... weil min () nur für eine Spalte und nicht spaltenübergreifend funktioniert.

3
Learning

Beide diese Frage Und diese Frage versuchen dies zu beantworten.

Die Zusammenfassung ist, dass Oracle eine eingebaute Funktion dafür hat. Mit SQL Server stecken Sie fest, entweder eine benutzerdefinierte Funktion zu definieren oder case-Anweisungen zu verwenden.

2
Sam Saffron

Wenn Sie in der Lage sind, eine gespeicherte Prozedur zu erstellen, kann dies ein Array von Werten annehmen, und Sie können dies einfach aufrufen.

1
Kev
select *,
case when column1 < columnl2 And column1 < column3 then column1
when columnl2 < column1 And columnl2 < column3 then columnl2
else column3
end As minValue
from   tbl_example
1
Phil Corcoran
SELECT [ID],
            (
                SELECT MIN([value].[MinValue])
                FROM
                (
                    VALUES
                        ([Col1]),
                        ([Col1]),
                        ([Col2]),
                        ([Col3])
                ) AS [value] ([MinValue])
           ) AS [MinValue]
FROM Table;

Wenn Sie SQL 2005 verwenden, können Sie etwas Ordentliches tun:

;WITH    res
          AS ( SELECT   t.YourID ,
                        CAST(( SELECT   Col1 AS c01 ,
                                        Col2 AS c02 ,
                                        Col3 AS c03 ,
                                        Col4 AS c04 ,
                                        Col5 AS c05
                               FROM     YourTable AS cols
                               WHERE    YourID = t.YourID
                             FOR
                               XML AUTO ,
                                   ELEMENTS
                             ) AS XML) AS colslist
               FROM     YourTable AS t
             )
    SELECT  YourID ,
            colslist.query('for $c in //cols return min(data($c/*))').value('.',
                                            'real') AS YourMin ,
            colslist.query('for $c in //cols return avg(data($c/*))').value('.',
                                            'real') AS YourAvg ,
            colslist.query('for $c in //cols return max(data($c/*))').value('.',
                                            'real') AS YourMax
    FROM    res

Auf diese Weise verlieren Sie sich nicht in so vielen Operatoren :)

Dies könnte jedoch langsamer sein als die andere Wahl.

Es ist deine Wahl...

1
leoinfo

Für mehrere Spalten empfiehlt es sich, eine CASE-Anweisung zu verwenden. Für zwei numerische Spalten i und j können Sie jedoch einfache Mathematik verwenden:

min (i, j) = (i + j)/2 - abs (i-j)/2

Diese Formel kann verwendet werden, um den Mindestwert für mehrere Spalten zu erhalten, aber es ist wirklich chaotisch, dass nach 2 min (i, j, k) min (i, min (j, k)) ist.

1
user3658750

Unten benutze ich eine temporäre Tabelle, um das Minimum von mehreren Daten zu erhalten. Die erste temporäre Tabelle fragt mehrere verknüpfte Tabellen ab, um verschiedene Daten (sowie andere Werte für die Abfrage) zu erhalten. Die zweite temporäre Tabelle ruft dann die verschiedenen Spalten und das Mindestdatum mit so vielen Durchläufen ab, wie es Datumsspalten gibt.

Dies ist im Wesentlichen wie bei der Unionsabfrage, die gleiche Anzahl von Durchläufen ist erforderlich, kann jedoch effizienter sein (basierend auf der Erfahrung, müsste jedoch getestet werden). Effizienz war in diesem Fall kein Problem (8.000 Datensätze). Man könnte etc. indizieren.

--==================== this gets minimums and global min
if object_id('tempdb..#temp1') is not null
    drop table #temp1
if object_id('tempdb..#temp2') is not null
    drop table #temp2

select r.recordid ,  r.ReferenceNumber, i.InventionTitle, RecordDate, i.ReceivedDate
, min(fi.uploaddate) [Min File Upload], min(fi.CorrespondenceDate) [Min File Correspondence]
into #temp1
from record r 
join Invention i on i.inventionid = r.recordid
left join LnkRecordFile lrf on lrf.recordid = r.recordid
left join fileinformation fi on fi.fileid = lrf.fileid
where r.recorddate > '2015-05-26'
 group by  r.recordid, recorddate, i.ReceivedDate,
 r.ReferenceNumber, i.InventionTitle



select recordid, recorddate [min date]
into #temp2
from #temp1

update #temp2
set [min date] = ReceivedDate 
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.ReceivedDate < [min date] and  t1.ReceivedDate > '2001-01-01'

update #temp2 
set [min date] = t1.[Min File Upload]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Upload] < [min date] and  t1.[Min File Upload] > '2001-01-01'

update #temp2
set [min date] = t1.[Min File Correspondence]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Correspondence] < [min date] and t1.[Min File Correspondence] > '2001-01-01'


select t1.*, t2.[min date] [LOWEST DATE]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
order by t1.recordid
1
user3438020

Ein kleiner Dreh in der Gewerkschaftsabfrage:

DECLARE @Foo TABLE (ID INT, Col1 INT, Col2 INT, Col3 INT)

INSERT @Foo (ID, Col1, Col2, Col3)
VALUES
(1, 3, 34, 76),
(2, 32, 976, 24),
(3, 7, 235, 3),
(4, 245, 1, 792)

SELECT
    ID,
    Col1,
    Col2,
    Col3,
    (
        SELECT MIN(T.Col)
        FROM
        (
            SELECT Foo.Col1 AS Col UNION ALL
            SELECT Foo.Col2 AS Col UNION ALL
            SELECT Foo.Col3 AS Col 
        ) AS T
    ) AS TheMin
FROM
    @Foo AS Foo
1
Lamprey

Wenn Sie wissen, nach welchen Werten Sie suchen, normalerweise nach einem Statuscode, kann Folgendes hilfreich sein:

select case when 0 in (PAGE1STATUS ,PAGE2STATUS ,PAGE3STATUS,
PAGE4STATUS,PAGE5STATUS ,PAGE6STATUS) then 0 else 1 end
FROM CUSTOMERS_FORMS
0

Ich weiß, dass diese Frage alt ist, aber ich war immer noch auf der Suche nach einer Antwort und war mit anderen Antworten nicht zufrieden. Deshalb musste ich mir eine eigene Frage ausdenken, die eine Wendung von @ paxdiablo´s answer darstellt.


Ich stamme aus dem Land von SAP ASE 16.0 und benötigte nur einen Blick auf die Statistik bestimmter Daten, die IMHO in verschiedenen Spalten einer einzelnen Zeile gültig gespeichert sind (sie stellen unterschiedliche Zeiten dar - als die Ankunft von etwas geplant war, was wann erwartet wurde) die aktion hat begonnen und endlich was war die aktuelle zeit). So hatte ich Spalten in die Zeilen der temporären Tabelle transponiert und meine Abfrage darüber wie gewohnt durchgeführt.

N.B. Nicht die Einheitslösung voraus!

CREATE TABLE #tempTable (ID int, columnName varchar(20), dataValue int)

INSERT INTO #tempTable 
  SELECT ID, 'Col1', Col1
    FROM sourceTable
   WHERE Col1 IS NOT NULL
INSERT INTO #tempTable 
  SELECT ID, 'Col2', Col2
    FROM sourceTable
   WHERE Col2 IS NOT NULL
INSERT INTO #tempTable 
  SELECT ID, 'Col3', Col3
    FROM sourceTable
   WHERE Col3 IS NOT NULL

SELECT ID
     , min(dataValue) AS 'Min'
     , max(dataValue) AS 'Max'
     , max(dataValue) - min(dataValue) AS 'Diff' 
  FROM #tempTable 
  GROUP BY ID

Dies dauerte etwa 30 Sekunden bei einer Quellmenge von 630000 Zeilen und verwendete nur Indexdaten. Dies ist also nicht die Voraussetzung für die Ausführung in einem zeitkritischen Prozess, sondern für Dinge wie die einmalige Datenüberprüfung oder den Tagesabschlussbericht in Ordnung (aber überprüfen Sie dies bitte mit Ihren Kollegen oder Vorgesetzten!). Der Hauptbonus dieses Stils für mich war, dass ich leicht mehr/weniger Spalten verwenden und die Gruppierung, Filterung usw. ändern konnte, insbesondere wenn die Daten kopiert wurden.

Die zusätzlichen Daten (columnName, maxes, ...) sollten mir bei meiner Suche helfen, sodass Sie sie möglicherweise nicht benötigen. Ich habe sie hier gelassen, um vielleicht spark einige Ideen :-).

0
Rao