Preciso retornar um resultado parcial (como uma seleção simples) de um procedimento armazenado antes de terminar.
É possível fazer isso?
Se sim, como fazer isso?
Se não, alguma solução alternativa?
EDIT: Eu tenho várias partes do procedimento. Na primeira parte eu calculo várias strings. 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. Portanto, preciso calcular essa string e passá-la de volta (de alguma forma, de um select, por exemplo) e continuar trabalhando. O chamador obtém sua string valiosa muito mais rapidamente.
O chamador é um serviço da Web.
Você provavelmente está procurando o
RAISERROR
comando com aNOWAIT
opção.Pelas observações :
Isso não retorna os resultados de uma
SELECT
instrução, mas permite que você passe mensagens/strings de volta para o cliente. Se você deseja retornar um subconjunto rápido dos dados que está selecionando, considere aFAST
dica de consulta.Adicionado por Shannon Severance em um comentário:
De Error and Transaction Handling in SQL Server por Erland Sommarskog:
Veja o artigo de origem para o contexto completo.
O OP já tentou enviar vários conjuntos de resultados (não MARS) e viu que realmente espera a conclusão do procedimento armazenado 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
SET CONTEXT_INFO
o que deve tornar esse valor visível por meio deSELECT [context_info] FROM [sys].[dm_exec_requests] WHERE [session_id] = @SessionID;
. Você só precisaria executar uma consulta rápida antes de executar o Stored Procedure paraSELECT @@SPID;
e pegá-lo 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 com 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 procedimento armazenado. Armazene este Guid em uma variável, pois ele será usado várias vezes.CREATE TABLE ##MyProcess_{GuidFromApp};
. A tabela pode ter quaisquer colunas de quaisquer tipos de dados que você precisar.Sempre que você tiver os dados, insira-os nessa Tabela Temp Global.
No código do aplicativo, comece a tentar ler os dados, mas envolva-os
SELECT
em umIF EXISTS
para que não haja falha se a tabela ainda não tiver sido criada:Com
String.Format()
, você pode substituir{0}
pelo valor na variável Guid. Teste para ifReader.HasRows
, e se for true, 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 código de exemplo a seguir.
Em uma guia de consulta, execute o seguinte e, em seguida, destaque as 3 linhas no comentário de bloco e execute-o:
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ê deve ver 1 entrada nos primeiros 10 segundos e, em seguida, 2 entradas nos próximos 10 segundos.
Você pode usar o SQLCLR para fazer uma chamada de volta para 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 passar strings de volta imediatamente, mas isso seria um pouco complicado devido aos seguintes problemas:VARCHAR(8000)
ouNVARCHAR(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 afirma:A desvantagem aqui é que a maioria dos erros de SQL não gerará mais um arquivo
SqlException
. Nesse caso, você precisa testar propriedades de eventos adicionais que são passadas para o Manipulador de Eventos de Mensagem. 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 nenhum campo ou propriedade separada para distinguir uma da outra. A ordem em que são recebidos deve ser a mesma de como são enviados, mas não tenho certeza se isso é confiável o suficiente. Pode ser necessário incluir uma tag ou algo que possa ser analisado. Dessa forma, você poderia pelo menos ter certeza de qual é qual.
ATUALIZAÇÃO: Veja a resposta de strutzky ( acima ) e os comentários para pelo menos um exemplo em que isso não se comporta como eu espero e descrevo aqui. Terei 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/multiprocesso, para que você possa abrir uma segunda sessão enquanto a primeira ainda está em execução, você pode criar uma tabela para manter 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 a tendência de defini-lo como hábito em tais circunstâncias, pois não causa prejudicar quando não necessário nesses casos
Obviamente, se você puder ter vários processos operando dessa maneira (o que é provável se o seu servidor da Web aceitar usuários simultâneos e é muito raro que não seja o caso), você precisará identificar as informações de progresso desse processo de alguma forma . Talvez passe para o procedimento um UUID recém-criado como uma chave, adicione-o à tabela de progresso e leia com:
Eu usei esse método para monitorar processos manuais de longa duração no SSMS. Não consigo decidir se "cheira" demais para eu considerar usá-lo na produção...
Se o procedimento armazenado precisar ser executado em segundo plano (ou seja, de forma assíncrona), você deverá usar o Service Broker. É um pouco trabalhoso de configurar, mas uma vez feito, você poderá iniciar o procedimento armazenado (sem bloqueio) e ouvir as mensagens de progresso pelo tempo (ou pouco) que desejar.