it-swarm.com.de

Auswahl des letzten Datensatzes, der eine Bedingung erfüllt

Ich habe eine vereinfachte Tabelle wie diese:

id |val |
---|----|
1  |6   |
2  |5   |
3  |4   |
4  |4   |
5  |8   |
6  |9   |
7  |7   |

Ich möchte die Datensätze so auswählen, dass val < 5 der letzte Datensatz, in dem val >= 5 ist ausgewählt. Das Ergebnis sollte also so aussehen:

id |val |
---|----|
1  |6   |
2  |5   |
3  |5   |
4  |5   |
5  |8   |
6  |9   |
7  |7   |

Ich habe mich gefragt, ob dies möglich ist, ohne eine Funktion zu definieren, also habe ich versucht:

SELECT
CASE
    WHEN val < 5 THEN LAG(id) OVER (ORDER BY id)
    WHEN val >= 5 THEN id
END id,
CASE
    WHEN val < 5 THEN LAG(val) OVER (ORDER BY id)
    WHEN val >= 5 THEN val
END val
FROM test;

aber es gibt nur zurück:

id |val |
---|----|
1  |6   |
2  |5   |
3  |5   |
4  |4   |
5  |8   |
6  |9   |
7  |7   |

Ist es möglich, diese Abfrage zu schreiben, ohne eine Funktion zu definieren? Oder wäre es besser mit einer Funktion?

EDIT: Eine kombinierte Lösung

Der Vollständigkeit halber, obwohl es geringfügig von der ursprünglichen Problembeschreibung abweicht, füge ich hinzu, dass wenn die Antworten von @Andriy M und @Hogan kombiniert werden, NULL-Werte erhalten bleiben und nur Werte unterhalb des Schwellenwerts mit dem zuletzt gültigen Wert aktualisiert werden Wert. In einigen Fällen ist es wünschenswert, die NULL-Werte beizubehalten. Die Abfrage würde also so aussehen:

SELECT
    id,
    val,
CASE WHEN val < 5 THEN (SELECT sub.val
                        FROM test AS sub
                        WHERE sub.id <= main.id
                            AND sub.val >= 5
                        ORDER BY id DESC
                        LIMIT 1) 
ELSE val END AS newval
FROM test AS main;

Welches kehrt zurück:

id |val |newval |
---|----|-------|
1  |6   |6      |
2  |5   |5      |
3  |4   |5      |
4  |NULL|NULL   |
5  |8   |8      |
6  |9   |9      |
7  |7   |7      |
4
Balazs Dukai

Ich denke, LAG () kann hier nicht verwendet werden, da Sie bei LAG genau angeben müssen, wie viele Zeilen Sie zurücklegen möchten. (Standardmäßig ist es 1, aber Sie können 3, 10 oder eine andere Zahl angeben. Der Punkt ist jedoch, dass es sich um eine bestimmte Zahl handeln muss.) In Ihrer Situation wissen Sie nicht, ob der letzte übereinstimmende Wert auf dem war vorherige Zeile oder in der Zeile davor oder noch früher.

Ein anderer Ansatz wäre also, die ID der letzten Zeile mit dem übereinstimmenden Wert zu finden und diese ID dann nachzuschlagen, um den Wert für die endgültige Ausgabe zu erhalten - ungefähr so:

SELECT
  s.id,
  t.val
FROM
  (
    SELECT
      id,
      MAX(CASE WHEN val >=5 THEN id END) OVER (ORDER BY id ASC) AS last_id
    FROM
      test
  ) AS s
  INNER JOIN test AS t ON s.last_id = t.id
ORDER BY
  s.id ASC
;

Oder Sie können eine korrelierte Unterabfrage verwenden, um den letzten Wert, der mehr als 5 in der Teilmenge beträgt, von der niedrigsten ID zur aktuellen ID zu erhalten:

SELECT
  id,
  (
    SELECT
      sub.val
    FROM
      test AS sub
    WHERE
      sub.id <= main.id
      AND sub.val >= 5
    ORDER BY
      id DESC
    LIMIT
      1
  ) AS val
FROM
  test AS main
ORDER BY
  id ASC
;

Dies wäre ähnlich wie LAG (), aber flexibler (und wahrscheinlich weniger effizient) - eine LAG () mit einem Tweak, wenn Sie möchten.

2
Andriy M

Die vorherige Antwort war falsch, wie in den Kommentaren ausgeführt - dies funktioniert:

SELECT id, val, 
       CASE when val < 5 then (
            select max(val) from t as tsub where tsub.id < t.id) 
       else val end as newval
FROM T

müssen eine Unterabfrage verwenden, um die vorherigen max.

1
Hogan