Was ist der beste Weg, um den Minimalwert von mehreren Spalten wählen?
-
21-08-2019 - |
Frage
In der folgenden Tabelle in SQL Server 2005 Gegeben:
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 (dh ein ergibt sich, dass die letzte Spalte ergibt - eine Spalte mit den Minium Werte 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 Klarstellung (wie ich in dem coments gesagt habe) in dem realen Szenario ist die Datenbank richtig normalisiert . Diese „Array“ Spalten sind nicht in einer tatsächlichen Tabelle, sondern sind in der Ergebnismenge, die in einem Bericht erforderlich ist. Und die neue Anforderung ist, dass der Bericht auch diese MinValue Spalte benötigt. Ich kann nicht die zugrunde liegende Ergebnismenge ändern und deshalb ich war auf der Suche nach T-SQL für ein handliche „erhält aus dem Gefängnis Karte“.
Ich habe versucht, der CASE-Ansatz unten erwähnt und es funktioniert, obwohl es ein wenig umständlich ist. Es ist auch komplizierter als in den Antworten angegeben, weil Sie für die Tatsache gerecht zu werden müssen, dass es in der gleichen Zeile zwei min Werte sind.
Wie auch immer, dachte ich, dass ich meine aktuelle Lösung veröffentlichen würde meine Zwänge, die, gegeben, recht gut funktioniert. Es nutzt 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 werde im Voraus sagen, dass ich nicht erwarten, dass dies die beste Leistung zu bieten, aber unter den gegebenen Umständen (ich nicht all Abfragen Redesign kann nur für die neue MinValue Spalte Anforderung), es ist ein ziemlich elegant „erhalten aus dem Gefängnis Karte“.
Lösung
Es gibt wahrscheinlich viele Möglichkeiten, um dies zu erreichen. Mein Vorschlag ist, Fall zu verwenden, / Wenn es zu tun. Mit 3 Spalten, dann 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
Andere Tipps
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
SELECT ID, Col1, Col2, Col3,
(SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin
FROM Table
Sie können die "Brute-Force" -Ansatz mit einem Twist 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 Bedingung, wenn es nicht garantiert, dass Sp1 nicht der kleinste Wert ist daher können Sie es von restlichen Bedingungen beseitigen. Ebenso für die nachfolgenden Bedingungen. Für fünf Spalten Ihre Abfrage wird:
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, wenn es eine Verbindung zwischen zwei oder mehr Spalten ist dann <=
stellt sicher, dass wir die CASE
Aussage so früh wie möglich beenden.
Der beste Weg, das zu tun, ist wahrscheinlich nicht , es zu tun - es ist seltsam, dass die Menschen darauf bestehen, ihre Daten in eine Art und Weise zu speichern, die SQL „Gymnastik“ erfordert aussagekräftige Informationen zu extrahieren, wenn es weit einfachere Wege, um das gewünschte Ergebnis zu erzielen, wenn Sie nur Ihr Schema ein wenig besser strukturieren: -)
Die rechts Art und Weise, dies zu tun, meiner Meinung nach, ist die folgende Tabelle 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ätzliche Schlüssel, je nach Bedarf). Dann Ihre Anfrage wird zu einem einfachen select min(val) from tbl
und Sie können immer noch die einzelnen ‚alten Säulen‘ separat behandeln, indem where col = 2
in Ihren anderen Abfragen. Dies ermöglicht auch eine einfache Erweiterung sollte die Zahl der ‚alte Spalten‘ wachsen.
Das macht Ihre Anfragen so viel einfacher. Die allgemeine Richtlinie I zu verwenden, sind in der Regel ist, wenn Sie immer etwas, das in einer Datenbank Zeile wie ein Array aussieht, sind Sie wahrscheinlich etwas falsch und sollte tun, denken die Daten über die Umstrukturierung.
Wenn jedoch aus irgendeinem Grund Sie kann nicht die Spalten ändern, würde ich vorschlagen, Einfügen und Aktualisieren Trigger verwenden und fügen Sie andere Spalte, die diese Trigger auf die eingestellte Minimum auf Col1/2/3
. Dies wird die ‚Kosten‘ der Operation bewegt sich von der Auswahl weg zum Update / Insert, wo es hingehört - die meisten Datenbanktabellen in meiner Erfahrung sind weit häufiger gelesen als so geschrieben tendieren dazu, die Kosten für Schreib entstehen effizienter im Laufe der Zeit zu sein.
Mit anderen Worten ändert sich das Minimum für eine Zeile nur dann, wenn eine der anderen Spalten zu ändern, so das ist , wenn Sie sollten es werden, zu berechnen, nicht jedes Mal, wenn Sie wählen (die, wenn die Daten verschwendet ändert sich nicht). Sie würden dann mit einer Tabelle am Ende 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
Eine andere Option, die Entscheidungen auf select
Zeit zu machen hat, ist in der Regel eine schlechte Idee, Performance-weise, da nur die Daten ändern es auf insert / update - die Zugabe einer anderen Säule nimmt mehr Platz in der DB und werden etwas langsamer für die Einsätze und Updates kann aber viel schneller für wählt - der bevorzugte Ansatz sollte es auf Ihre Prioritäten ab, sondern, wie gesagt, die meisten Tabellen gelesen werden far häufiger als sie ‚re geschrieben.
Wenn die Spalten waren ganze Zahlen 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 verwenden muss ich tun würde:
select col1, col2, col3, dbo.f_min_int(dbo.f_min_int(col1,col2),col3)
Wenn Sie 5 colums haben dann die oben wird
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)
verwenden:
select least(col1, col2, col3) FROM yourtable
Sie können auch mit einer Union-Abfrage tun. Da die Anzahl der Spalten zu erhöhen, müssten Sie die Abfrage verändern, aber zumindest wäre es eine gerade nach vorne Modifikation sein.
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
Dies ist rohe Gewalt, sondern arbeitet
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 () funktioniert nur auf einer Spalte und nicht über mehrere Spalten.
diese Frage Und diese Frage versuchen, diese zu beantworten.
Die Rekapitulation ist, dass Oracle eine eingebaute Funktion für diese, mit SQL Server sind Sie stecken entweder die Definition einer benutzerdefinierten-Funktion oder Fall-Anweisungen.
Wenn Sie in der Lage sind, eine gespeicherte Prozedur zu machen, es könnte eine Reihe von Werten annehmen, und man kann nur so nennen.
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
Eine kleine Drehung auf der Union-Abfrage:
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
Wenn Sie SQL 2005 verwenden Sie etwas ordentlich wie dies 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 kann in so viele Betreiber verloren geht nicht:)
Dies könnte jedoch langsamer als die andere Wahl.
Sie haben die Wahl ...
Im Folgenden werde ich eine temporäre Tabelle verwenden, um die mindestens mehrere Termine zu bekommen. Die ersten temporären Tabellenabfragen Tabellen mehr beigetreten verschiedene Termine zu bekommen (wie auch andere Werte für die Abfrage), die zweite temporäre Tabelle wird dann die verschiedenen Spalten und das minimale Datum wie viele Pässe verwenden, da es Datumsspalte sind.
Dies ist im Wesentlichen wie die Union-Abfrage, die gleiche Anzahl von Durchgängen erforderlich, kann aber effizienter sein (basierend auf Erfahrung, sondern müßte Tests). Effizienz war kein Problem in diesem Fall (8.000 Datensätze). Man könnte Index etc.
--==================== 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
Für mehrere Spalten seine besten eine CASE-Anweisung zu verwenden, jedoch für zwei numerische Spalten i und j können Sie einfache mathematische verwenden:
min (i, j) = (i + j) / 2 - abs (i-j) / 2
Diese Formel verwendet werden, um den Minimalwert von mehreren Spalten zu erhalten, aber es ist wirklich chaotisch letzten 2 min (i, j, k) wäre min (i, min (j, k))
SELECT [ID],
(
SELECT MIN([value].[MinValue])
FROM
(
VALUES
([Col1]),
([Col1]),
([Col2]),
([Col3])
) AS [value] ([MinValue])
) AS [MinValue]
FROM Table;
Wenn Sie wissen, welche Werte Sie suchen, in der Regel einem Statuscode kann die folgenden hilfreich sein:
select case when 0 in (PAGE1STATUS ,PAGE2STATUS ,PAGE3STATUS,
PAGE4STATUS,PAGE5STATUS ,PAGE6STATUS) then 0 else 1 end
FROM CUSTOMERS_FORMS
Ich weiß, diese Frage ist alt, aber ich war immer noch in der Notwendigkeit der Antwort und war mit anderen Antworten nicht zufrieden, damit ich meine eigenen hatte zu entwickeln, das eine Drehung auf @ paxdiablo's ist answer .
Ich kam vom Land von SAP ASE 16.0, und ich brauchte nur einen Blick auf Statistiken von bestimmten Daten, die in verschiedenen Spalten einer einzigen Zeile gültig gespeichert sind IMHO (sie repräsentieren unterschiedliche Zeiten - wenn die Ankunft von etwas geplant, was es erwartet wurde, wenn die Aktion gestartet und schließlich, was die tatsächliche Zeit war). So hatte ich Spalten in die Reihen der temporären Tabelle umgesetzt und vorgeformten meine Abfrage über diese wie gewöhnlich.
N. B. Nicht die one-size-fits-all-Lö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
Das dauerte etwa 30 Sekunden Quellensatz von 630.000 Zeilen und nur Index-Daten, so dass das Ding nicht läuft in zeitkritischen Prozess, aber für Dinge wie eine Zeitdatum Inspektion oder End-of-the-day-Bericht verwendet Sie könnte in Ordnung sein (aber überprüfen, diese mit Ihren Kollegen oder Vorgesetzten, bitte!). Haupt Bonus dieses Stils für mich war, dass ich leicht mehr / weniger Spalten und ändern Gruppierung, Filterung, usw. verwenden können, vor allem, wenn Daten wurden copyied über.
Die zusätzlichen Daten (columnName
, max
es, ...) waren mir bei meiner Suche zu unterstützen, so dass Sie sie nicht benötigen; Ich habe sie hier vielleicht ein paar Ideen zu entfachen: -).