2016-10-30 2 views
2

Ich stehe vor einem einfachen Problem mit einer SQL-Abfrage, die ich nicht angehen kann.SQL min/max mit allen Feldern

Ich habe eine Tabelle mit der folgenden Struktur

CITY COUNTRY DATES TEMPERATURE 

Beachten Sie, dass für ein bestimmtes Land, ich mehrere Städte haben kann. Und für eine bestimmte Stadt habe ich mehrere Reihen, die mir zu jedem verfügbaren DATUM die TEMPERATUR geben. Dies ist nur eine Zeitreihe.

Ich möchte eine Abfrage schreiben, die mir für jede Stadt das DATUM gibt, wo die TEMPERATUR die MIN ist und das DATUM wo die TEMPERATUR der MAX ist. Die Abfrage sollte so etwas zurückgeben:

CITY COUNTRY DATE_MIN_TEMPERATURE MIN_TEMPERATURE DATE_MAX_TEMPERATURE MAX_TEMPERATURE 

Eine Idee, wie dies zu erreichen?

Mit freundlichen Grüßen,

Deny

+0

, was Sie wollen für eine bestimmte Stadt angezeigt, wenn Sie es sind zwei verschiedene Zeiten, in denen die Mindesttemperatur erreicht wurde? – mathguy

+0

Guter Punkt. Ich kann mit jedem des Datums umgehen ... –

Antwort

1

Oracle keep/dense_rank first für diesen Zweck bestimmt:

select city, 
     min(temperature) as min_temperature, 
     max(date) keep (dense_rank first order by temperature asc) as min_temperature_date, 
     max(temperature) as max_temperature, 
     max(date) keep (dense_rank first order by temperature desc) as max_temperature_date 
from t 
group by city; 

Beachten Sie, dass dies gibt nur ein Datum, wenn es Bindungen sind. Wenn Sie damit umgehen möchten, wird mehr Logik benötigt:

select city, min(temperature) as min_temperature, 
     listagg(case when seqnum_min = 1 then date end, ',') within group (order by date) as mindates, 
     max(temperature) as max_temperature, 
     listagg(case when seqnum_max = 1 then date end, ',') within group (order by date) as maxdates, 
from (select t.*, 
      rank() over (partition by city order by temperature) as seqnum_min, 
      rank() over (partition by city order by temperature desc) as seqnum_max 
     from t 
    ) t 
where seqnum_min = 1 or seqnum_max = 1 
group by city; 
+0

Schade, die 'ersten' und' letzten' Funktionen funktionieren nicht mit 'listagg' als Aggregatfunktion! (Wirklich nicht klar, warum sie das nicht tun, aber sie tun es nicht.) Es wäre cool, beide Ideen in einem kombinieren zu können. – mathguy

+0

err ... die zweite Lösung hat mehrere fehlende Schlüsselwörter, Tippfehler, "Datum" funktioniert nicht als Spaltenname usw. Nachdem alle diese Lösungen korrigiert wurden, gibt die Lösung die falsche Antwort. (Wahrscheinlich, weil der zweite row_number() Aufruf nach der Temperatur sortieren sollte ** desc **?) Und es sollte nicht 'row_number()' sein, es sollte 'rank() 'auf jeden Fall sein. Viel zu viele Fehler. – mathguy

+0

@mathguy. . . "Datum" ist die vom OP bereitgestellte Spalte. Ich sehe keinen Grund, dem zu entgehen, denn das ist wahrscheinlich nicht der eigentliche Name der Spalte. Ich denke andere Tippfehler sind behoben. Der Kommentar zu 'rank()' ist sehr auf den Punkt, aber wirklich, es ist die erste Abfrage, die tatsächlich die Frage des OP beantwortet. –

0

In Oracle 11 und höher können Sie PIVOT verwenden. In der folgenden Lösung verwende ich LISTAGG, um alle Daten im Falle von Bindungen anzuzeigen. Eine andere Möglichkeit ist, bei Krawatten das jüngste Datum zu zeigen, an dem die extreme Temperatur erreicht wurde; Wenn dies bevorzugt ist, ersetzen Sie einfach LISTAGG(dt, ....) (einschließlich WITHIN GROUP Klausel) mit MAX(dt). In diesem Fall ist die erste von Gordon angebotene Lösung (mit der Funktion first) ohnehin effizienter - keine Notwendigkeit zum Pivotieren.

Beachten Sie, dass ich "Datum" in "dt" geändert habe - DATE ist ein reserviertes Wort in Oracle. Ich zeige auch die Reihen zuerst nach Land, dann nach Stadt (die logischere Reihenfolge). Ich habe Testdaten in einer WITH-Klausel erstellt, aber die Lösung ist alles unterhalb der Kommentarzeile.

with 
    inputs (city, country, dt, temperature) as (
     select 'Palermo', 'Italy' , date '2014-02-13', 3 from dual union all 
     select 'Palermo', 'Italy' , date '2002-01-23', 3 from dual union all 
     select 'Palermo', 'Italy' , date '1998-07-22', 42 from dual union all 
     select 'Palermo', 'Italy' , date '1993-08-24', 30 from dual union all 
     select 'Maseru' , 'Lesotho', date '1994-01-11', 34 from dual union all 
     select 'Maseru' , 'Lesotho', date '2004-08-13', 12 from dual 
    ) 
-- >> end test data; solution (SQL query) begins with the next line 
select country, city, 
     "'min'_DT" as date_min_temp, "'min'_TEMP" as min_temp, 
     "'max'_DT" as date_max_temp, "'max'_TEMP" as max_temp 
from ( 
     select city, country, dt, temperature, 
       case when temperature = min(temperature) 
         over (partition by city, country) then 'min' 
        when temperature = max(temperature) 
         over (partition by city, country) then 'max' 
        end as flag 
     from inputs 
    ) 
pivot (listagg(to_char(dt, 'dd-MON-yyyy'), ', ') 
     within group (order by dt) as dt, min(temperature) as temp 
     for flag in ('min', 'max')) 
order by country, city -- ORDER BY is optional 
; 

COUNTRY CITY DATE_MIN_TEMP    MIN_TEMP DATE_MAX_TEMP MAX_TEMP 
------- ------- ------------------------ ---------- -------------- ---------- 
Italy Palermo 23-JAN-2002, 13-FEB-2014   3 22-JUL-1998   42 
Lesotho Maseru 13-AUG-2004      12 11-JAN-1994   34 

2 rows selected. 
0

Statt keep/dense_rank first Funktion können Sie auch FIRST_VALUE und LAST_VALUE verwenden können:

select distinct city, 
    MIN(temperature) OVER (PARTITION BY city) as min_temperature, 
    FIRST_VALUE(date) OVER (PARTITION BY city ORDER BY temperature) AS min_temperature_date, 
    MAX(temperature) OVER (PARTITION BY city) as max_temperature, 
    LAST_VALUE(date) OVER (PARTITION BY city ORDER BY temperature) AS max_temperature_date 
FROM t; 
Verwandte Themen