Wie wähle ich in PL / SQL eine Variable aus, wenn das Ergebnis null sein könnte?

74

Gibt es eine Möglichkeit, eine Abfrage nur einmal auszuführen, um sie in eine Variable auszuwählen, da die Abfrage möglicherweise nichts zurückgibt? In diesem Fall sollte die Variable null sein.

Derzeit kann ich eine select intoVariable nicht direkt ausführen, da sich die Variable PL / SQL beschweren würde, wenn die Abfrage nichts zurückgibt. Ich kann die Abfrage nur zweimal ausführen, wobei die erste die Zählung durchführt. Wenn die Zählung Null ist, setzen Sie die Variable auf Null. Wenn die Zählung 1 ist, wählen Sie sie in die Variable aus.

Der Code wäre also wie folgt:

v_column my_table.column%TYPE;
v_counter number;
select count(column) into v_counter from my_table where ...;
if (v_counter = 0) then
    v_column := null;
elsif (v_counter = 1) then
    select column into v_column from my_table where ...;
end if;

Vielen Dank.

Update: Der Grund, warum ich keine Ausnahme verwendet habe, ist, dass ich nach dem Zuweisen der noch eine folgende Logik v_columnhabe und diese gotoim Ausnahmeabschnitt verwenden muss, um zum folgenden Code zurückzukehren. Ich zögere ein bisschen mit gotoZeilen.

Weisheit
quelle

Antworten:

127

Sie können die NO_DATA_FOUNDAusnahme einfach behandeln, indem Sie Ihre Variable auf setzen NULL. Auf diese Weise ist nur eine Abfrage erforderlich.

    v_column my_table.column%TYPE;

BEGIN

    BEGIN
      select column into v_column from my_table where ...;
    EXCEPTION
      WHEN NO_DATA_FOUND THEN
        v_column := NULL;
    END;

    ... use v_column here
END;
Adam Paynter
quelle
@Adam , der Grund, warum ich keine Ausnahme verwendet habe, ist, dass ich nach dem Zuweisen der noch eine folgende Logik habe v_columnund im Ausnahmeabschnitt "goto" verwenden muss, um zum folgenden Code zurückzukehren. Ich zögere ein bisschen mit gotoZeilen.
Sapience
6
@Sapience, das ist kein Problem, setzen Sie einfach die Logik nach dem ENDE dieses verschachtelten Blocks.
Tony Andrews
1
@ Shannon: Danke für die Bearbeitung. Ich verwende natürlich anonyme PL / SQL-Blöcke, daher habe ich nicht bemerkt, dass dies nicht eindeutig ist.
Adam Paynter
16

Ich weiß, dass es ein alter Thread ist, aber ich denke immer noch, dass es sich lohnt, ihn zu beantworten.

select (
        SELECT COLUMN FROM MY_TABLE WHERE ....
        ) into v_column
from dual;

Anwendungsbeispiel:

declare v_column VARCHAR2(100);
begin
  select (SELECT TABLE_NAME FROM ALL_TABLES WHERE TABLE_NAME = 'DOES NOT EXIST')
  into v_column 
  from dual;
  DBMS_OUTPUT.PUT_LINE('v_column=' || v_column);
end;
jpe
quelle
1
Ja, es ist PLSQL. Es funktioniert gut. Der Einfachheit halber habe ich nur das Anfangs- und Endmaterial übersprungen.
Jpe
@Miki: Ich habe ein Beispiel hinzugefügt, um zu zeigen, dass es PLSQL ist und es funktioniert.
Jpe
1
Ich habe diese Technik hier gesehen: stackoverflow.com/a/42415454/577052 Es scheint nicht zu funktionieren, wenn ich mehrere Spalten in Variablen schreibe. Ich bekomme ORA-00913zu viele Werte.
Bernhard Döbler
Danke für den Kommentar! Es ist richtig, es funktioniert nur für 1 Spalte, da skalare Unterabfragen nur 1 Wert zurückgeben können. Ich hoffe, jemand könnte das jemals ändern ...
jpe
Ich habe diesen Tipp abgespalten und eine schöne Auswahlabfrage erstellt. Danke: (wähle Runde (Runde (Monate zwischen ('30 -mai-2018 ', c.column_date), 0) / 12,1) von dual) als josh
JoshYates1980
8

Was ist mit MAX? Auf diese Weise wird die Variable auf NULL gesetzt, andernfalls auf den Maximalwert, wenn keine Daten gefunden werden.
Da Sie entweder einen Wert von 0 oder 1 erwarten, sollte MAX in Ordnung sein.

v_column my_table.column%TYPE;
select MAX(column) into v_column from my_table where ...;
Björn
quelle
Schön, es funktioniert auch, wenn die Abfrage mehrere Zeilen
zurückgibt
7

Die Verwendung einer Cursor FOR LOOP-Anweisung ist meine bevorzugte Methode, um dies zu tun.

Dies ist sicherer als die Verwendung eines expliziten Cursors, da Sie nicht daran denken müssen, ihn zu schließen, damit Sie keine Cursor "auslaufen" können.

Sie brauchen keine "in" -Variablen, Sie müssen nicht "FETCH", Sie müssen keine "NO DATA FOUND" -Ausnahmen abfangen und behandeln.

Probieren Sie es aus, Sie werden nie zurückkehren.

v_column my_table.column%TYPE;

v_column := null;

FOR rMyTable IN (SELECT COLUMN FROM MY_TABLE WHERE ....) LOOP
  v_column := rMyTable.COLUMN;
  EXIT;  -- Exit the loop if you only want the first result.
END LOOP;
jmc
quelle
5

Von allen obigen Antworten scheint Björns Antwort die eleganteste und kurzeste zu sein. Ich persönlich habe diesen Ansatz oft verwendet. Die MAX- oder MIN-Funktion erledigt den Job gleich gut. Es folgt vollständiges PL / SQL, nur die where-Klausel sollte angegeben werden.

declare v_column my_table.column%TYPE;
begin
    select MIN(column) into v_column from my_table where ...;
    DBMS_OUTPUT.PUT_LINE('v_column=' || v_column);
end;
schemerys
quelle
4

Ich würde empfehlen, einen Cursor zu verwenden. Ein Cursorabruf besteht immer aus einer einzelnen Zeile (es sei denn, Sie verwenden eine Massensammlung), und Cursor lösen nicht automatisch no_data_found- oder too_many_rows-Ausnahmen aus. Sie können jedoch das einmal geöffnete Cursorattribut überprüfen, um festzustellen, ob und wie viele Zeilen vorhanden sind.

declare
v_column my_table.column%type;
l_count pls_integer;
cursor my_cursor is
  select count(*) from my_table where ...;

begin
  open my_cursor;
    fetch my_cursor into l_count;
  close my_cursor;

  if l_count = 1 then
    select whse_code into v_column from my_table where ...;
  else
    v_column := null;
  end if;
end;

Oder noch einfacher:

    declare
    v_column my_table.column%type;
    cursor my_cursor is
      select column from my_table where ...;

    begin
      open my_cursor;
        fetch my_cursor into v_column;
        -- Optional IF .. THEN based on FOUND or NOTFOUND
        -- Not really needed if v_column is not set
        if my_cursor%notfound then
          v_column := null;
        end if;
      close my_cursor;
    end;
Wolf
quelle
Danke für deine Antwort. Aber was ich versuchte zu vermeiden , läuft die Abfrage der gleichen where - Klausel zweimal , dass der erste, der Richter ist , wenn es einen gibt es mit der Bedingung ist, und das zweite ist den vorhandenen Datensatz auf eine Variable zu holen.
Sapience
2
@Sapience: Gotcha, ich habe deinen ersten Satz beschönigt. In diesem Fall macht die Verwendung eines Cursorattributs dies sehr einfach (zweite Option, die ich vorgeschlagen habe). Schreiben Sie einfach Ihre eigentliche Abfrage in den Cursor (nein COUNT(*)), öffnen Sie den Cursor und rufen Sie das Ergebnis in Ihre lokale Variable ab. Verwenden Sie dann das %FOUNDAttribut, um den gewünschten Status zu bestimmen. Auf diese Weise führen Sie die Abfrage nur einmal aus, benötigen keinen Ausnahmeblock und vermeiden SELECT .. INTOmögliche Ausnahmen.
Wolf
2

Ich benutze diese Syntax für Flexibilität und Geschwindigkeit -

    begin
    --
    with KLUJ as
    ( select 0 ROES from dual
       union 
      select count(*) from MY_TABLE where rownum = 1
    ) select max(ROES) into has_rows from KLUJ;
    --
    end;

Dual gibt 1 Zeile zurück, Rownum fügt 0 oder 1 Zeilen und max () Gruppen zu genau 1 hinzu. Dies ergibt 0 für keine Zeilen in einer Tabelle und 1 für eine andere Anzahl von Zeilen.

Ich erweitere die where-Klausel, um Zeilen nach Bedingung zu zählen, entferne Rownum, um Zeilen zu zählen, die eine Bedingung erfüllen, und erhöhe Rownum, um Zeilen zu zählen, die die Bedingung bis zu einem Grenzwert erfüllen.

user2168235
quelle
-3

COALESCEgibt immer das erste Nicht-Null-Ergebnis zurück. Auf diese Weise erhalten Sie die gewünschte Anzahl oder 0:

select coalesce(count(column) ,0) into v_counter from my_table where ...;
Chuy
quelle
Count gibt immer eine Zahl zurück, es besteht keine Notwendigkeit, sie zusammenzuführen. Bei dieser Frage geht es aber nicht wirklich darum, die Zählung zu erhalten. Der Autor erhielt nur die Zählung, um Ausnahmen zu vermeiden. Wenn Sie die Aggregatfunktion entfernen, hilft die Koaleszenz nicht, NO_DATA_FOUND zu vermeiden, da möglicherweise keine Zeilen vorhanden sind.
Jon Heller
Wenn Sie eine Funktion um eine solche Spalte wickeln, wird normalerweise jeder Index übersprungen, der möglicherweise verwendet wurde, da der Compiler nicht herausfinden kann, ob die Funktion den Spaltenwert ändern kann, sodass er im Index nicht gefunden werden kann, sodass er einfach nicht nach einem sucht Indizes. (Sie können einen funktionsbasierten Index erstellen, um zu helfen, aber wer weiß das schon?)
Grokster