Gegeben die folgende 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 (dh eine, die die letzte Spalte ergibt - eine Spalte, die die Miniumwerte von Col1, Col2 und Col3 für jede Zeile enthält )?
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
AKTUALISIEREN:
Zur Verdeutlichung (wie ich in den Kommentaren gesagt habe) wird die Datenbank im realen Szenario ordnungsgemäß normalisiert . Diese "Array" -Spalten befinden sich nicht in einer tatsächlichen Tabelle, sondern in einer Ergebnismenge, die in einem Bericht erforderlich ist. Die neue Anforderung besteht darin, dass der Bericht auch diese MinValue-Spalte benötigt. Ich kann die zugrunde liegende Ergebnismenge nicht ändern und habe daher bei T-SQL nach einer praktischen "Ausstiegskarte" 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 veröffentlichen, die angesichts 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 werde im Voraus sagen, dass ich nicht erwarte, dass dies die beste Leistung bietet, aber angesichts der Umstände (ich kann nicht alle Abfragen nur für die neue MinValue-Spaltenanforderung neu gestalten) ist es ein ziemlich eleganter Ausstieg aus dem Gefängnis Karte".
quelle
Antworten:
Es gibt wahrscheinlich viele Möglichkeiten, dies zu erreichen. Mein Vorschlag ist, Case / When zu verwenden. Mit 3 Spalten ist es nicht schlecht.
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
quelle
Verwenden von
CROSS APPLY
:SELECT ID, Col1, Col2, Col3, MinValue FROM YourTable CROSS APPLY (SELECT MIN(d) AS MinValue FROM (VALUES (Col1), (Col2), (Col3)) AS a(d)) A
SQL Fiddle
quelle
cross apply
. Fügt es Mehrwert / Leistung hinzu? stackoverflow.com/a/14712024/78522where MinValue > 10
, auf das ich nicht verzichten könnteCROSS APPLY
SELECT ID, Col1, Col2, Col3, (SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin FROM Table
quelle
Verwenden Sie unter MySQL Folgendes:
select least(col1, col2, col3) FROM yourtable
quelle
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 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 lautet Ihre Abfrage:
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
<=
sichergestellt wird, dass dieCASE
Anweisung so früh wie möglich beendet wird.quelle
<=
stattdessen, andernfalls wird der letzte übereinstimmende Mindestwert anstelle des ersten verwendet.Wenn die Spalten Ganzzahlen wie in Ihrem Beispiel wären, 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)
quelle
Der beste Weg, dies zu tun, ist wahrscheinlich, es nicht zu tun - es ist seltsam, dass die Leute darauf bestehen, ihre Daten so zu speichern, dass SQL "Gymnastik" erforderlich ist, um aussagekräftige Informationen zu extrahieren, wenn es weitaus einfachere Möglichkeiten gibt, das gewünschte Ergebnis zu erzielen, wenn Sie dies nur tun strukturiere dein Schema etwas besser :-)
Der richtige Weg, dies zu tun, ist meiner Meinung nach die folgende Tabelle:
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öglicherweiseCol
als zusätzlicher Schlüssel, abhängig von Ihren Anforderungen). Dann wird Ihre Abfrage einfachselect min(val) from tbl
und Sie können die einzelnen "alten Spalten" weiterhin separat behandeln, indem Sie siewhere col = 2
in Ihren anderen Abfragen verwenden. Dies ermöglicht auch eine einfache Erweiterung, falls die Anzahl der "alten Spalten" zunimmt.Dies macht Ihre Anfragen so viel einfacher. Die allgemeine Richtlinie, die ich normalerweise verwende, lautet: Wenn Sie jemals 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 diese Spalten jedoch aus irgendeinem Grund nicht ändern können, würde ich empfehlen, Trigger zum Einfügen und Aktualisieren zu verwenden und eine weitere Spalte hinzuzufügen , für die diese Trigger das Minimum festgelegt haben
Col1/2/3
. Dadurch werden die "Kosten" des Vorgangs von der Auswahl zu dem Update / Insert verschoben, zu dem sie gehören. Die meisten Datenbanktabellen werden meiner Erfahrung nach weitaus häufiger gelesen als geschrieben, sodass die Kosten für das Schreiben im Laufe der Zeit tendenziell effizienter sind.Mit anderen Worten ändert sich das Minimum für eine Zeile nur dann , wenn eine der anderen Spalten zu ändern, so dass die , wenn Sie es werden die Berechnung sollte, nicht jedes Mal , wenn Sie wählen (was , wenn die Daten verschwendet wird , ist nicht zu ändern). Sie würden dann mit einem Tisch wie folgt enden:
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 zu einem bestimmten
select
Zeitpunkt Entscheidungen treffen muss, ist in Bezug auf die Leistung normalerweise 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 ein und ist für die Einfügungen und etwas langsamer Aktualisierungen können jedoch für die Auswahl viel schneller sein - der bevorzugte Ansatz sollte von Ihren Prioritäten dort abhängen, aber wie bereits erwähnt, werden die meisten Tabellen weitaus häufiger gelesen als geschrieben.quelle
Sie können dies auch mit einer Gewerkschaftsabfrage tun. Wenn die Anzahl der Spalten zunimmt, müssten Sie die Abfrage ändern, aber zumindest wäre 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
quelle
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 für mehrere Spalten funktioniert.
quelle
Sowohl diese Frage als auch diese Frage versuchen dies zu beantworten.
Die Zusammenfassung ist, dass Oracle eine eingebaute Funktion dafür hat. Mit SQL Server können Sie entweder eine benutzerdefinierte Funktion definieren oder case-Anweisungen verwenden.
quelle
Für mehrere Spalten ist es am besten, eine CASE-Anweisung zu verwenden. Für zwei numerische Spalten i und j können Sie jedoch einfache Berechnungen verwenden:
min (i, j) = (i + j) / 2 - abs (ij) / 2
Diese Formel kann verwendet werden, um den Mindestwert mehrerer Spalten zu erhalten, aber die wirklich unordentliche Zeit nach 2, min (i, j, k) wäre min (i, min (j, k)).
quelle
Wenn Sie in der Lage sind, eine gespeicherte Prozedur zu erstellen, kann dies eine Reihe von Werten erfordern, und Sie können dies einfach aufrufen.
quelle
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
quelle
Eine kleine Wendung bei 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
quelle
Wenn Sie SQL 2005 verwenden, können Sie Folgendes 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 Ihre Wahl...
quelle
Unten verwende 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) abzurufen. Die zweite temporäre Tabelle ruft dann die verschiedenen Spalten und das Mindestdatum mit so vielen Durchläufen ab, wie es Datumsspalten gibt.
Dies ähnelt im Wesentlichen der Gewerkschaftsabfrage. Es ist dieselbe Anzahl von Durchläufen erforderlich, kann jedoch effizienter sein (basierend auf Erfahrung, müsste jedoch getestet werden). Effizienz war in diesem Fall kein Problem (8.000 Datensätze). Man könnte indexieren 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
quelle
SELECT [ID], ( SELECT MIN([value].[MinValue]) FROM ( VALUES ([Col1]), ([Col1]), ([Col2]), ([Col3]) ) AS [value] ([MinValue]) ) AS [MinValue] FROM Table;
quelle
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
quelle
Ich weiß, dass diese Frage alt ist, aber ich brauchte die Antwort immer noch und war mit anderen Antworten nicht zufrieden, also musste ich meine eigene entwickeln, was eine Wendung in der Antwort von @ paxdiablo darstellt .
Ich kam aus dem Land von SAP ASE 16.0 und brauchte nur einen Blick auf Statistiken bestimmter Daten, die meiner Meinung nach in verschiedenen Spalten einer einzelnen Zeile gültig gespeichert sind (sie repräsentieren unterschiedliche Zeiten - als die Ankunft von etwas geplant war, was erwartet wurde, wann Die Aktion begann und schließlich, was war die tatsächliche Zeit). Daher hatte ich Spalten in die Zeilen der temporären Tabelle transponiert und meine Abfrage darüber wie gewohnt durchgeführt.
NB Nicht die Einheitslösung!
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 bei einem Quellensatz von 630000 Zeilen etwa 30 Sekunden und verwendete nur Indexdaten. Dies ist also nicht die Sache, die in einem zeitkritischen Prozess ausgeführt werden muss, sondern beispielsweise für eine einmalige Dateninspektion oder einen Tagesabschlussbericht gut (aber überprüfen Sie dies bitte mit Ihren Kollegen oder Vorgesetzten!). Hauptbonus dieses Stils für mich dass ich problemlos mehr / weniger Spalten verwenden und die Gruppierung, Filterung usw. ändern konnte, insbesondere nachdem die Daten kopiert wurden.
Die zusätzlichen Daten (
columnName
,max
es, ...) sollten mir bei meiner Suche helfen, sodass Sie sie möglicherweise nicht benötigen. Ich habe sie hier gelassen, um vielleicht ein paar Ideen zu wecken :-).quelle