Ich muss ein Teilergebnis (als einfache Auswahl) von einer gespeicherten Prozedur zurückgeben, bevor es abgeschlossen ist.
Ist das möglich?
Wenn ja, wie geht das?
Wenn nicht, eine Problemumgehung?
EDIT: Ich habe mehrere Teile des Verfahrens. Im ersten Teil berechne ich mehrere Strings. Ich benutze sie später in der Prozedur, um zusätzliche Operationen durchzuführen. Das Problem ist, dass der Anrufer so schnell wie möglich eine Zeichenfolge benötigt. Also muss ich diesen String berechnen und zurückgeben (irgendwie aus einer Auswahl zum Beispiel) und dann weiterarbeiten. Der Anrufer erhält seine wertvolle Zeichenfolge viel schneller.
Anrufer ist ein Webdienst.
sql-server-2012
t-sql
stored-procedures
multi-thread
web-service
Bogdan Bogdanov
quelle
quelle
Antworten:
Sie suchen wahrscheinlich nach dem
RAISERROR
Befehl mit derNOWAIT
Option.Gemäß den Bemerkungen :
Dadurch werden die Ergebnisse einer
SELECT
Anweisung nicht zurückgegeben, aber Sie können Nachrichten / Zeichenfolgen an den Client zurückgeben. Wenn Sie eine kurze Teilmenge der von Ihnen ausgewählten Daten zurückgeben möchten, sollten Sie denFAST
Abfragehinweis berücksichtigen .Hinzugefügt von Shannon Severance in einem Kommentar:
Aus der Fehler- und Transaktionsbehandlung in SQL Server von Erland Sommarskog:
Den vollständigen Kontext finden Sie im Quellartikel.
quelle
FAST
Das Problem wurde für mich in einem Problem behoben, bei dem ich die Ausführung einer gespeicherten Prozedur und des C # -Codes synchronisieren musste, um eine Racebedingung zu verschärfen und zu reproduzieren. Es ist einfacher, Ergebnismengen programmgesteuert zu verwenden, als so etwas zu verwendenRAISERROR()
. Als ich anfing, Ihre Antwort zu lesen, schien es, als würden Sie sagen, dass dies nicht möglichSELECT
ist. Vielleicht könnte das geklärt werden?UPDATE: Siehe Strutzkys Antwort ( oben ) und die Kommentare für mindestens ein Beispiel, bei dem sich dies nicht so verhält, wie ich es hier erwarte und beschreibe. Ich muss weiter experimentieren / lesen, um mein Verständnis zu aktualisieren, wenn es die Zeit erlaubt ...
Wenn Ihr Anrufer asynchron mit der Datenbank interagiert oder über einen Thread / Multiprozess verfügt, sodass Sie eine zweite Sitzung öffnen können, während die erste noch ausgeführt wird, können Sie eine Tabelle erstellen, in der die Teildaten gespeichert sind, und diese im Verlauf der Prozedur aktualisieren. Dies kann dann von einer zweiten Sitzung mit der Transaktionsisolationsstufe 1 gelesen werden, damit nicht festgeschriebene Änderungen gelesen werden können:
1: Gemäß den Kommentaren und der anschließenden Aktualisierung in der Antwort von srutzky ist das Festlegen der Isolationsstufe nicht erforderlich, wenn der überwachte Prozess nicht in eine Transaktion eingebunden ist, obwohl ich ihn unter Umständen, die er nicht verursacht, aus Gewohnheit festlege Schaden, wenn in diesen Fällen nicht benötigt
Wenn auf diese Weise mehrere Prozesse ausgeführt werden könnten (was wahrscheinlich ist, wenn Ihr Webserver gleichzeitig Benutzer akzeptiert und dies sehr selten nicht der Fall ist), müssen Sie die Fortschrittsinformationen für diesen Prozess auf irgendeine Weise identifizieren . Übergeben Sie der Prozedur möglicherweise eine frisch geprägte UUID als Schlüssel, fügen Sie diese der Fortschrittstabelle hinzu und lesen Sie mit:
Ich habe diese Methode verwendet, um lang laufende manuelle Prozesse in SSMS zu überwachen. Ich kann mich nicht entscheiden, ob es zu viel "riecht", als dass ich es in der Produktion verwenden könnte ...
quelle
Das OP hat bereits versucht, mehrere Ergebnismengen (nicht MARS) zu senden, und hat festgestellt, dass es tatsächlich auf den Abschluss der gespeicherten Prozedur wartet, bevor es Ergebnismengen zurückgibt. In Anbetracht dieser Situation gibt es hier einige Optionen:
Wenn Ihre Daten klein genug sind, um in 128 Bytes zu passen, können Sie höchstwahrscheinlich verwenden
SET CONTEXT_INFO
, um diesen Wert über sichtbar zu machenSELECT [context_info] FROM [sys].[dm_exec_requests] WHERE [session_id] = @SessionID;
. Sie müssten nur eine kurze Abfrage ausführen, bevor Sie die gespeicherte Prozedur ausführenSELECT @@SPID;
und diese über abrufenSqlCommand.ExecuteScalar
.Ich habe das gerade getestet und es funktioniert.
Ähnlich wie bei @ Davids Vorschlag, die Daten in eine "Fortschritts" -Tabelle zu stellen, ohne sich jedoch mit Aufräum- oder Parallelitäts- / Prozesstrennungsproblemen herumschlagen zu müssen:
Guid
Code im App-Code und übergeben Sie ihn als Parameter an die gespeicherte Prozedur. Speichern Sie diese Guid in einer Variablen, da sie mehrmals verwendet wird.CREATE TABLE ##MyProcess_{GuidFromApp};
. Die Tabelle kann beliebige Spalten mit beliebigen Datentypen enthalten.Wenn Sie die Daten haben, fügen Sie sie in diese globale Temp-Tabelle ein.
Versuchen Sie im App-Code, die Daten zu lesen, schließen Sie sie jedoch
SELECT
ein,IF EXISTS
damit sie nicht fehlschlagen, wenn die Tabelle noch nicht erstellt wurde:Mit
String.Format()
können Sie durch{0}
den Wert in der Guid-Variablen ersetzen . Testen Sie, obReader.HasRows
und ob true, lesen Sie die Ergebnisse, rufen Sie anThread.Sleep()
oder was auch immer, um dann erneut abzufragen.Leistungen:
EXEC
/sp_executesql
-Anruf).Ich habe dies getestet und es funktioniert wie erwartet. Sie können es mit dem folgenden Beispielcode selbst ausprobieren.
Führen Sie auf einer Abfrage-Registerkarte Folgendes aus, markieren Sie dann die 3 Zeilen im Blockkommentar und führen Sie Folgendes aus:
Gehen Sie zur Registerkarte "Nachrichten" und kopieren Sie die gedruckte GUID. Öffnen Sie dann eine andere Abfrage-Registerkarte und führen Sie die folgenden Schritte aus. Platzieren Sie die GUID, die Sie von der Registerkarte Nachrichten der anderen Sitzung kopiert haben, in die Variableninitialisierung in Zeile 1:
Schlagen Sie weiter F5. Sie sollten 1 Eintrag für die ersten 10 Sekunden und dann 2 Einträge für die nächsten 10 Sekunden sehen.
Sie können SQLCLR verwenden, um über einen Webdienst oder auf andere Weise einen Rückruf zu Ihrer App zu tätigen.
Sie könnten / möglicherweise verwenden , um Zeichenfolgen sofort zurückzugeben, dies wäre jedoch aufgrund der folgenden Probleme etwas schwierig:
PRINT
RAISERROR(..., 1, 10) WITH NOWAIT
VARCHAR(8000)
oder beschränktNVARCHAR(4000)
Standardmäßig werden auch keine Nachrichten gesendet, bis der Vorgang abgeschlossen ist. Dieses Verhalten kann jedoch geändert werden, indem die Eigenschaft SqlConnection.FireInfoMessageEventOnUserErrors auf gesetzt wird
true
. In der Dokumentation heißt es:Der Nachteil hierbei ist, dass die meisten SQL-Fehler a nicht mehr auslösen
SqlException
. In diesem Fall müssen Sie zusätzliche Ereigniseigenschaften testen, die an den Nachrichtenereignishandler übergeben werden. Dies gilt für die gesamte Verbindung, was die Dinge etwas schwieriger, aber nicht unüberschaubar macht.Alle Nachrichten werden auf derselben Ebene ohne separates Feld oder Eigenschaft angezeigt, um sie voneinander zu unterscheiden. Die Reihenfolge, in der sie empfangen werden, sollte der Reihenfolge entsprechen, in der sie gesendet werden, aber nicht sicher, ob dies zuverlässig genug ist. Möglicherweise müssen Sie ein Tag oder etwas hinzufügen, das Sie dann analysieren können. Auf diese Weise könnten Sie zumindest sicher sein, welches welches ist.
quelle
RETURN
Anweisung). Es funktioniert also nicht.SqlConnection
? Wie viele Daten möchten Sie zurückgeben? Welche Datentypen? Hast du es entweder versuchtPRINT
oderRAISERROR WITH NOWAIT
?Wenn Ihre gespeicherte Prozedur im Hintergrund ausgeführt werden muss (dh asynchron), sollten Sie Service Broker verwenden. Das Einrichten ist etwas mühsam, aber wenn Sie fertig sind, können Sie die gespeicherte Prozedur (nicht blockierend) starten und Fortschrittsmeldungen so lange (oder so wenig) abhören, wie Sie möchten.
quelle