Kann ich einen optionalen OUTPUT-Parameter in einer gespeicherten Prozedur haben?

76

Ich habe eine gespeicherte Prozedur, die eine Reihe von Eingabe- und Ausgabeparametern enthält, da Werte in mehrere Tabellen eingefügt werden. In einigen Fällen wird der gespeicherte Prozess nur in eine einzelne Tabelle eingefügt (abhängig von den Eingabeparametern). Hier ist ein nachgebildetes Szenario zur Veranschaulichung.

Tabellen / Datenobjekte:

Person

Id
Name
Address

Name

Id
FirstName
LastName

Adresse

Id
Country
City

Angenommen, ich habe eine gespeicherte Prozedur, die eine Person einfügt. Wenn die Adresse nicht vorhanden ist, werde ich sie nicht zur AddressTabelle in der Datenbank hinzufügen .

Wenn ich also den Code zum Aufrufen der gespeicherten Prozedur generiere, möchte ich den AddressParameter nicht hinzufügen . Für INPUTParameter ist dies in Ordnung, da ich mit SQL Server Standardwerte angeben kann. Aber für den OUTPUTParameter, was mache ich in der gespeicherten Prozedur, um ihn optional zu machen, damit ich keinen Fehler erhalte ...

Prozedur oder Funktion 'Person_InsertPerson' erwartet den Parameter '@AddressId', der nicht angegeben wurde.

Justin
quelle
Wie sieht Ihr Code aus? Das heißt, irgendwo verzweigen Sie, ob eine Adresse vorhanden ist. Ich bin misstrauisch, dass meine Frage, sobald ich diesen Zweig gesehen habe, lautet: "Warum rufen Sie den Sproc nicht mit dem an, wofür NULLSie sich entscheiden, @AddressIdwenn eine Adresse nicht existiert, ohne einen Zweig zu verwenden?"
Ruffin

Antworten:

119

Sowohl Eingabe- als auch Ausgabeparameter können Standardeinstellungen zugewiesen werden. In diesem Beispiel:

CREATE PROCEDURE MyTest
  @Data1 int
 ,@Data2 int = 0
 ,@Data3 int = null output

AS

PRINT @Data1
PRINT @Data2
PRINT isnull(@Data3, -1)

SET @Data3 = @Data3 + 1

RETURN 0

Der erste Parameter ist erforderlich, und der zweite und dritte sind optional. Wenn sie nicht von der aufrufenden Routine festgelegt werden, werden ihnen die Standardwerte zugewiesen. Probieren Sie es und die folgende Testaufrufroutine in SSMS mit verschiedenen Werten und Einstellungen aus, um zu sehen, wie alles zusammenarbeitet.

DECLARE @Output int

SET @Output = 3

EXECUTE MyTest
  @Data1 = 1
 ,@Data2 = 2
 ,@Data3 = @Output output

PRINT '---------'
PRINT @Output
Philip Kelley
quelle
2
Vielen Dank! Ich habe mich gefragt, warum ich nicht verwenden kann @var INT OUTPUT = NULL- die Syntax ist verwirrend.
Trojjer
2
Ruft den Standardwert nicht wirklich auf (Sie setzen @output). Die Standardeinstellungen werden nur aktiviert, wenn Sie etwas nicht übergeben. Eine vollständige Behandlung finden Sie in meiner Antwort.
Flucht-llc
13

Ausgabeparameter und Standardwerte funktionieren nicht gut zusammen! Dies ist aus SQL 10.50.1617 (2008 R2). Lassen Sie sich nicht täuschen, wenn Sie glauben, dass dieses Konstrukt SETin Ihrem Namen auf magische Weise einen Wert hat (wie es mein Kollege getan hat)!

Dieses "Spielzeug" SP fragt den OUTPUTParameterwert ab, ob es sich um den Standardwert handelt oder nicht NULL.

CREATE PROCEDURE [dbo].[omgwtf] (@Qty INT, @QtyRetrieved INT = 0 OUTPUT)
AS
IF @QtyRetrieved = 0
BEGIN
    print 'yay its zero'
END
IF @QtyRetrieved is null
BEGIN
    print 'wtf its NULL'
END
RETURN

Wenn Sie einen nicht initialisierten Wert (dh NULL) für das senden OUTPUT, sind Sie wirklich NULLin den SP gelangt und nicht 0. Sinnvoll, für diesen Parameter wurde etwas übergeben.

declare @QR int
exec [dbo].[omgwtf] 1, @QR output
print '@QR=' + coalesce(convert(varchar, @QR),'NULL')

Ausgabe ist:

wtf its NULL
@QR=NULL

Wenn wir eine explizite SETvom Anrufer hinzufügen, erhalten wir:

declare @QR int
set @QR = 999
exec [dbo].[omgwtf] 1, @QR output
print '@QR=' + coalesce(convert(varchar, @QR),'NULL')

und die (nicht überraschende) Ausgabe:

@QR=999

Auch hier ist es sinnvoll, einen Parameter zu übergeben und SP hat keine explizite Aktion für SETeinen Wert ausgeführt.

Fügen Sie einen SETder OUTPUTParameter im SP hinzu (wie Sie es tun sollen), aber legen Sie nichts vom Aufrufer fest:

ALTER PROCEDURE [dbo].[omgwtf] (@Qty INT, @QtyRetrieved INT = 0 OUTPUT)
AS
IF @QtyRetrieved = 0
BEGIN
    print 'yay its zero'
END
IF @QtyRetrieved is null
BEGIN
    print 'wtf its NULL'
END
SET @QtyRetrieved = @Qty
RETURN

Jetzt, wenn ausgeführt:

declare @QR int
exec [dbo].[omgwtf] 1234, @QR output
print '@QR=' + coalesce(convert(varchar, @QR),'NULL')

Die Ausgabe ist:

wtf its NULL
@QR=1234

Dies ist das "Standard" -Verhalten für die OUTPUTParameterbehandlung in SPs.

Nun zum Plot Twist : Die einzige Möglichkeit, den Standardwert zum "Aktivieren" zu erhalten, besteht darin , den OUTPUTParameter überhaupt nicht zu übergeben , was meiner Meinung nach wenig Sinn macht: Da er als OUTPUTParameter eingerichtet ist, würde dies bedeuten, etwas "Wichtiges" zurückzugeben. das sollte gesammelt werden.

declare @QR int
exec [dbo].[omgwtf] 1
print '@QR=' + coalesce(convert(varchar, @QR),'NULL')

gibt diese Ausgabe:

yay its zero
@QR=NULL

Dies erfasst jedoch nicht die SP-Ausgabe, vermutlich zunächst den Zweck dieses SP.

IMHO ist diese Feature-Kombination ein zweifelhaftes Konstrukt, das ich als Code-Geruch betrachten würde (Puh !!)

Flucht-llc
quelle
Was ist der Vorteil gegenüber der Knappheit (einschließlich der Wiederverwendung von Variablen im Code, die diesen und andere Sprocs aufrufen könnten) gegenüber der Verwendung von OUTPUTParametern für die Eingabe? Warum nicht diese Eingabewerte als "reguläre" Parameter übergeben?
Ruffin
2
@ Ruffin gibt es keinen Vorteil; Optionale OUTPUT-Parameter funktionieren nicht wie erwartet, weshalb ich diese Antwort erstellt habe.
Flucht-llc
+1. Ich denke, ich möchte nur hinzufügen, dass dies bedeutet, dass die Übergabe von Werten über die OUTPUTParameter c / sh / aus zwei Gründen als schlechte Idee angesehen werden kann: 1. Ideologischer / Code-Geruch und 2. Praktische Gründe aufgrund des hier gefundenen Verhaltens. ¯ \ _ (ツ) _ / ¯
Ruffin
Wenn Sie also optionale OUTPUT-Parameter verwenden möchten, setzen Sie den Standardwert immer auf NULL (und halten Sie Ihre Nase vom Geruch fern).
Baodad
Eigentlich ist es moralisch, diese beiden "Merkmale" NICHT zusammen zu verwenden ... das Halten der Nase ist optional ;-)
Escape-llc
4

Sieht so aus, als könnte ich dem OUTPUTParameter einfach einen Standardwert hinzufügen, z.

@AddressId int = -1 Output

Scheint in Bezug auf die Lesbarkeit schlecht zu sein, da dies AddressIdausschließlich als OUTPUTVariable gedacht ist. Aber es funktioniert. Bitte lassen Sie mich wissen, wenn Sie eine bessere Lösung haben.

Justin
quelle
Dies hat Fallstricke, siehe meine Antwort für die vollständige Behandlung.
Flucht-llc
1

Hinzufügen zu dem, was Philip gesagt hat:

Ich hatte eine gespeicherte Prozedur in meiner SQL Server-Datenbank, die wie folgt aussah:

dbo.<storedProcedure>
(@current_user char(8) = NULL,
@current_phase char(3) OUTPUT)

Und ich habe es von meinem .net-Code wie folgt aufgerufen:

 DataTable dt = SqlClient.ExecuteDataTable(<connectionString>, <storedProcedure>);

Ich habe eine System.Data.SqlClient.SqlException erhalten: Prozedur oder Funktion erwartet den Parameter '@current_phase', der nicht angegeben wurde.

Ich benutze diese Funktion auch irgendwo anders in meinem Programm und übergebe einen Parameter und behandle den ausgegebenen. Damit ich den aktuellen Aufruf nicht ändern musste, habe ich nur die gespeicherte Prozedur geändert, um den Ausgabeparameter ebenfalls optional zu machen.

So sieht es jetzt aus:

dbo.<storedProcedure>
(@current_user char(8) = NULL,
@current_phase char(3) = NULL OUTPUT)
Ein Kimmel
quelle
1

Da Sie eine gespeicherte Prozedur und keine SQL-Anweisung ausführen, müssen Sie den Befehlstyp Ihres SQL-Befehls auf Gespeicherte Prozedur setzen:

cmd.CommandType = CommandType.StoredProcedure;

Von hier genommen .

Sobald Sie diesen Fehler behoben haben, können Sie die SQL- nvl()Funktion in Ihrer Prozedur verwenden, um anzugeben, was angezeigt werden soll, wenn ein NULL-Wert auftritt.

Tut mir leid, dass ich die Frage nicht richtig beantwortet habe ... muss dich missverstanden haben. Hier ist ein Beispiel für nvl, das meiner Meinung nach etwas besser angesprochen werden könnte.

select NVL(supplier_city, 'n/a')
from suppliers;

Die obige SQL-Anweisung würde 'n / a' zurückgeben, wenn das supplier_cityFeld einen Nullwert enthält. Andernfalls würde der supplier_cityWert zurückgegeben.

Rownage
quelle
Das beantwortet die Frage nicht. Ich verwende den db.GetStoredProcCommand der Enterprise Library, der das für mich erledigt. Ich habe viele gespeicherte Prozesse auf diese Weise aufgerufen und sie funktionieren recht gut. z.B. Code zum Aufrufen des StoredProc mit (DbCommand insertPersonCommand = db.GetStoredProcCommand ("Person_InsertPerson")) {this.AppendInsertPersonParameters (db, insertPersonCommand); db.ExecuteNonQuery (insertPersonCommand, dbTransaction);
Justin