Eu preciso retornar o resultado parcial (como seleção simples) de um procedimento armazenado antes de ser concluído.
É possível fazer isso?
Se sim, como fazer isso?
Caso contrário, alguma solução alternativa?
EDIT: Eu tenho várias partes do procedimento. Na primeira parte, calculo várias seqüências. Eu os uso posteriormente no procedimento para fazer operações adicionais. O problema é que a string é necessária pelo chamador o mais rápido possível. Então, eu preciso calcular essa sequência e passá-la de volta (de alguma forma, de uma seleção, por exemplo) e continuar trabalhando. O chamador obtém sua valiosa sequência muito mais rapidamente.
O chamador é um serviço da Web.
sql-server-2012
t-sql
stored-procedures
multi-thread
web-service
Bogdan Bogdanov
fonte
fonte
Respostas:
Você provavelmente está procurando o
RAISERROR
comando com aNOWAIT
opçãoPelas observações :
Isso não retorna os resultados de uma
SELECT
instrução, mas permite que você repasse mensagens / strings de volta ao cliente. Se você deseja retornar um subconjunto rápido dos dados selecionados, considere aFAST
dica de consulta.Adicionado por Shannon Severance em um comentário:
Do tratamento de erros e transações no SQL Server por Erland Sommarskog:
Veja o artigo de origem para o contexto completo.
fonte
FAST
resolveu o problema para mim em um problema em que eu precisava sincronizar a execução de um procedimento armazenado e código C # para exacerbar e reproduzir uma condição de corrida. É mais fácil consumir conjuntos de resultados programaticamente do que usar algo parecidoRAISERROR()
. Quando comecei a ler sua resposta, parecia que você estava dizendo que isso não pode ser resolvidoSELECT
, então talvez isso possa ser esclarecido?ATUALIZAÇÃO: Veja a resposta de strutzky ( acima ) e os comentários de pelo menos um exemplo em que isso não se comporta como eu espero e descrevo aqui. Vou ter que experimentar / ler mais para atualizar meu entendimento quando o tempo permitir ...
Se o chamador interage com o banco de dados de forma assíncrona ou é encadeado / com vários processos, para que você possa abrir uma segunda sessão enquanto a primeira ainda está em execução, você pode criar uma tabela para armazenar os dados parciais e atualizá-los à medida que o procedimento avança. Isso pode ser lido por uma segunda sessão com o nível de isolamento de transação 1 definido para permitir a leitura de alterações não confirmadas:
1: de acordo com os comentários e a atualização subsequente na resposta de srutzky, não é necessário definir o nível de isolamento se o processo que está sendo monitorado não estiver envolvido em uma transação, embora eu tenha tendência a deixar o hábito em circunstâncias que não causem prejudicar quando não é necessário nesses casos
Obviamente, se você puder ter vários processos operando dessa maneira (o que provavelmente se o seu servidor da Web aceitar usuários simultâneos e for muito raro que não seja esse o caso), será necessário identificar as informações de progresso desse processo de alguma maneira . Talvez passe ao procedimento um UUID recém-cunhado como uma chave, adicione-o à tabela de progresso e leia com:
Eu usei esse método para monitorar processos manuais de longa execução no SSMS. Não posso decidir se "cheira" demais para eu considerar usá-lo na produção ...
fonte
O OP já tentou enviar vários conjuntos de resultados (não o MARS) e viu que realmente espera que o Procedimento Armazenado seja concluído antes de retornar qualquer conjunto de resultados. Com essa situação em mente, aqui estão algumas opções:
Se seus dados forem pequenos o suficiente para caber em 128 bytes, você provavelmente poderá usar o
SET CONTEXT_INFO
que deve tornar esse valor visível viaSELECT [context_info] FROM [sys].[dm_exec_requests] WHERE [session_id] = @SessionID;
. Você só precisaria executar uma consulta rápida antes de executar o Procedimento ArmazenadoSELECT @@SPID;
e obter essa viaSqlCommand.ExecuteScalar
.Acabei de testar isso e funciona.
Semelhante à sugestão de @ David de colocar os dados em uma tabela de "progresso", mas sem precisar mexer nos problemas de limpeza ou simultaneidade / separação de processos:
Guid
dentro do código do aplicativo e passe-o como um parâmetro para o Stored Procedure. Armazene este Guid em uma variável, pois ele será usado várias vezes.CREATE TABLE ##MyProcess_{GuidFromApp};
. A tabela pode ter quaisquer colunas dos tipos de dados que você precisa.Sempre que você tiver os dados, insira-os na tabela Temp Global.
No código do aplicativo, comece a tentar ler os dados, mas envolva o
SELECT
em umIF EXISTS
modo que não irá falhar se a tabela não foi criado ainda:Com
String.Format()
, você pode substituir{0}
pelo valor na variável Guid. Teste seReader.HasRows
, e se for verdadeiro, leia os resultados; caso contrário, ligueThread.Sleep()
ou o que quer que seja para pesquisar novamente.Benefícios:
EXEC
/sp_executesql
)Eu testei isso e funciona como esperado. Você pode tentar por si mesmo com o seguinte código de exemplo.
Em uma guia de consulta, execute o seguinte e destaque as 3 linhas no comentário do bloco e execute:
Vá para a guia "Mensagens" e copie o GUID que foi impresso. Em seguida, abra outra guia de consulta e execute o seguinte, colocando o GUID que você copiou da guia Mensagens da outra sessão na inicialização da variável na linha 1:
Continue batendo F5. Você deverá ver 1 entrada pelos primeiros 10 segundos e depois 2 entradas pelos próximos 10 segundos.
Você pode usar o SQLCLR para fazer uma chamada de volta ao seu aplicativo por meio de um serviço da Web ou de algum outro meio.
Talvez você possa usar
PRINT
/RAISERROR(..., 1, 10) WITH NOWAIT
para transmitir seqüências de caracteres imediatamente, mas isso seria um pouco complicado devido aos seguintes problemas:VARCHAR(8000)
ou aNVARCHAR(4000)
As mensagens, por padrão, também não são enviadas até que o processo seja concluído. Esse comportamento, no entanto, pode ser alterado definindo a propriedade SqlConnection.FireInfoMessageEventOnUserErrors como
true
. A documentação declara:A desvantagem aqui é que a maioria dos erros de SQL não aumentará mais a
SqlException
. Nesse caso, você precisa testar propriedades adicionais do evento que são passadas para o Message Event Handler. Isso vale para toda a conexão, o que torna as coisas um pouco mais complicadas, mas não incontroláveis.Todas as mensagens aparecem no mesmo nível sem campo ou propriedade separados para distinguir uma da outra. A ordem em que eles são recebidos deve ser a mesma de como são enviados, mas não tem certeza se isso é confiável o suficiente. Pode ser necessário incluir uma tag ou algo que você possa analisar. Dessa forma, você pode pelo menos ter certeza de qual é qual.
fonte
RETURN
instrução). Portanto, não está funcionando.SqlConnection
? Quantos dados você deseja transmitir? Quais tipos de dados? Você já tentouPRINT
ouRAISERROR WITH NOWAIT
?Se o seu procedimento armazenado precisar ser executado em segundo plano (ou seja, de forma assíncrona), você deverá usar o Service Broker. É um pouco complicado de configurar, mas, uma vez feito, você poderá iniciar o procedimento armazenado (sem bloqueio) e ouvir as mensagens de progresso pelo tempo (ou tão pouco) quanto desejar.
fonte