J'ai besoin de renvoyer le résultat partiel (comme simple sélection) d'une procédure stockée avant qu'elle ne soit terminée.
Est-il possible de faire ça?
Si oui, comment faire?
Sinon, une solution?
EDIT: J'ai plusieurs parties de la procédure. Dans la première partie, je calcule plusieurs chaînes. Je les utilise plus tard dans la procédure pour effectuer des opérations supplémentaires. Le problème est que la chaîne est requise par l'appelant dès que possible. J'ai donc besoin de calculer cette chaîne et de la transmettre (en quelque sorte, à partir d'une sélection par exemple), puis de continuer à travailler. L'appelant obtient sa chaîne de valeur beaucoup plus rapidement.
L'appelant est un service Web.
sql-server-2012
t-sql
stored-procedures
multi-thread
web-service
Bogdan Bogdanov
la source
la source
Réponses:
Vous recherchez probablement la
RAISERROR
commande avec l'NOWAIT
option.Par les remarques :
Cela ne renvoie pas les résultats d'une
SELECT
instruction, mais cela vous permettra de transmettre des messages / chaînes au client. Si vous souhaitez renvoyer un sous-ensemble rapide des données que vous sélectionnez, vous souhaiterez peut-être prendre en compte l'FAST
indice de requête.Ajouté par Shannon Severance dans un commentaire:
De la gestion des erreurs et des transactions dans SQL Server par Erland Sommarskog:
Voir l'article source pour le contexte complet.
la source
FAST
résolu le problème pour moi dans un problème où je devais synchroniser l'exécution d'une procédure stockée et du code C # pour exacerber et reproduire une condition de concurrence critique. Il est plus facile de consommer des jeux de résultats par programme que d'utiliser quelque chose commeRAISERROR()
. Quand j'ai commencé à lire votre réponse, il semblait que vous disiez que cela ne pouvait pas être faitSELECT
, alors peut-être que cela pourrait être clarifié?MISE À JOUR: Voir la réponse de strutzky ( ci-dessus ) et les commentaires pour au moins un exemple où cela ne se comporte pas comme je m'attends et le décris ici. Je devrai expérimenter / lire plus loin pour mettre à jour ma compréhension lorsque le temps le permettra ...
Si votre appelant interagit avec la base de données de manière asynchrone ou est fileté / multi-processus, vous pouvez donc ouvrir une deuxième session pendant que la première est toujours en cours d'exécution, vous pouvez créer une table pour contenir les données partielles et la mettre à jour au fur et à mesure de la progression de la procédure. Cela peut ensuite être lu par une deuxième session avec le niveau d'isolement de transaction 1 défini pour lui permettre de lire les modifications non validées:
1: selon les commentaires et la mise à jour subséquente dans la réponse de srutzky, la définition du niveau d'isolement n'est pas requise si le processus surveillé n'est pas encapsulé dans une transaction, bien que j'ai tendance à le mettre hors d'usage dans les circonstances car il ne provoque pas nuire lorsqu'il n'est pas nécessaire dans ces cas
Bien sûr, si plusieurs processus peuvent fonctionner de cette façon (ce qui est probable si votre serveur Web accepte des utilisateurs simultanés et il est très rare que ce ne soit pas le cas), vous devrez identifier les informations de progression de ce processus d'une manière ou d'une autre. . Peut-être passer la procédure un UUID fraîchement créé comme clé, l'ajouter au tableau de progression et lire avec:
J'ai utilisé cette méthode pour surveiller les processus manuels de longue durée dans SSMS. Je ne peux pas décider si ça "sent" trop pour moi d'envisager de l'utiliser en production ...
la source
L'OP a déjà essayé d'envoyer plusieurs jeux de résultats (pas MARS) et a vu qu'il attend en effet que la procédure stockée se termine avant de retourner des jeux de résultats. Avec cette situation à l'esprit, voici quelques options:
Si vos données sont suffisamment petites pour tenir dans les 128 octets, vous pourriez très probablement utiliser
SET CONTEXT_INFO
ce qui devrait rendre cette valeur visible viaSELECT [context_info] FROM [sys].[dm_exec_requests] WHERE [session_id] = @SessionID;
. Vous auriez juste besoin d'exécuter une requête rapide avant d'exécuter la procédure stockéeSELECT @@SPID;
et de la saisir viaSqlCommand.ExecuteScalar
.Je viens de tester cela et cela fonctionne.
Semblable à la suggestion de @ David de mettre les données dans un tableau de "progression", mais sans avoir à s'occuper des problèmes de nettoyage ou de concurrence / séparation des processus:
Guid
dans le code d'application et transmettez-le en tant que paramètre à la procédure stockée. Stockez ce Guid dans une variable car il sera utilisé plusieurs fois.CREATE TABLE ##MyProcess_{GuidFromApp};
. La table peut avoir toutes les colonnes de tous les types de données dont vous avez besoin.Chaque fois que vous avez les données, insérez-les dans cette table de températures globales.
Dans le code de l'application, commencez à essayer de lire les données, mais encapsulez-le
SELECT
dans unIF EXISTS
afin qu'il n'échoue pas si la table n'a pas encore été créée:Avec
String.Format()
, vous pouvez remplacer{0}
par la valeur de la variable Guid. Testez siReader.HasRows
et si vrai, lisez les résultats, sinon appelezThread.Sleep()
ou quoi que ce soit pour interroger à nouveau.Avantages:
EXEC
/sp_executesql
J'ai testé cela et cela fonctionne comme prévu. Vous pouvez l'essayer par vous-même avec l'exemple de code suivant.
Dans un onglet de requête, exécutez ce qui suit, puis mettez en surbrillance les 3 lignes dans le bloc-commentaire et exécutez cela:
Accédez à l'onglet "Messages" et copiez le GUID qui a été imprimé. Ensuite, ouvrez un autre onglet de requête et exécutez ce qui suit, en plaçant le GUID que vous avez copié à partir de l'onglet Messages de l'autre session dans l'initialisation variable sur la ligne 1:
Continuez à frapper F5. Vous devriez voir 1 entrée pour les 10 premières secondes, puis 2 entrées pour les 10 secondes suivantes.
Vous pouvez utiliser SQLCLR pour rappeler votre application via un service Web ou tout autre moyen.
Vous pouvez peut - être utiliser
PRINT
/RAISERROR(..., 1, 10) WITH NOWAIT
pour renvoyer des chaînes immédiatement, mais ce serait un peu délicat en raison des problèmes suivants:VARCHAR(8000)
ouNVARCHAR(4000)
Par défaut, les messages ne sont également pas envoyés avant la fin du processus. Ce comportement, cependant, peut être modifiée en réglant la SqlConnection.FireInfoMessageEventOnUserErrors propriété à
true
. La documentation indique:L'inconvénient ici est que la plupart des erreurs SQL ne soulèveront plus a
SqlException
. Dans ce cas, vous devez tester des propriétés d'événement supplémentaires qui sont transmises au gestionnaire d'événements de message. Cela vaut pour toute la connexion, ce qui rend les choses un peu plus délicates, mais pas ingérables.Tous les messages apparaissent au même niveau, sans champ ni propriété distincts pour les distinguer les uns des autres. L'ordre dans lequel ils sont reçus doit être le même que la façon dont ils sont envoyés, mais ne savez pas si cela est suffisamment fiable. Vous devrez peut-être inclure une balise ou quelque chose que vous pourrez ensuite analyser. De cette façon, vous pourriez au moins être certain lequel est lequel.
la source
RETURN
instruction). Donc ça ne marche pas.SqlConnection
? Combien de données souhaitez-vous transmettre? Quels types de données? Avez-vous essayé l'unPRINT
ou l' autreRAISERROR WITH NOWAIT
?Si votre procédure stockée doit s'exécuter en arrière-plan (c'est-à-dire de manière asynchrone), vous devez utiliser Service Broker. C'est un peu pénible à configurer, mais une fois terminé, vous pourrez lancer la procédure stockée (non bloquante) et écouter les messages de progression aussi longtemps (ou aussi peu) que vous le souhaitez.
la source