Eu gostaria de saber qual é a substituição geral para um cursor. A implementação geral de um cursor que vejo por aí é
DECLARE @variable INT, @sqlstr NVARCHAR(MAX)
DECLARE cursor_name CURSOR
FOR select_statement --essentially to get an array for @variable
--usually it's a subset of unique ids for accounts, clients, parts, etc
OPEN cursor_name
FETCH NEXT FROM cursor_name INTO @variable
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sqlstr = N'
/* some query that uses '+ str(@variable) +' to do dirty work
such as: go through all our accounts, if it''s some subset (possible new cursor),
go through those accounts and connect this way,
map those fields and add it to our big uniform table */
'
EXEC sp_executesql @sqlstr
FETCH NEXT FROM cursor_name INTO @variable
END
CLOSE cursor_name
DEALLOCATE cursor_name
Como tantas pessoas são anti-cursor (com um aceno para SO: Por que as pessoas odeiam cursores ), qual é a substituição geral para a implementação geral (de preferência SQL Server)?
Depende™
A capacidade de contornar um ou vários cursores dependerá do que será executado dentro desse cursor. Sem saber o que está acontecendo nele, não há como dizer. Pode ser que não haja solução alternativa e você precise fazer o processamento de linha por linha.
Abaixo estão alguns exemplos.
Não trabalhando em conjuntos
Este exemplo é o mais básico e é simplesmente o fato de que você pode consultar todo o seu conjunto de dados ou partes de seu conjunto de dados de uma vez, mas o cursor foi criado e está consultando os dados linha por linha. Os mais comuns para substituir isso são
JOIN
's,CROSS APPLY
/OUTER APPLY
e outros.Considere o seguinte conjunto de dados:
Pode-se tentar encontrar cada registro e ele corresponde separadamente, fazendo um loop sobre a
Lotr
Tabela.Cursor:
Resultando em dois conjuntos de resultados
Quando isso
inner join
é usado, obtemos o mesmo resultado de um conjunto de resultados.Manipulação de String
Um comum é usar
FOR XML PATH('')
para substituir manipulações de string dentro de cursores.Conjunto de dados
Cursor duplo com manipulação de string
Resultado
Removendo os cursores com FOR XML PATH('')
*
A solução real aqui seria descobrir por que os dados são apresentados dessa maneira e alterar o application/... para não precisar dele nesse formato, armazenando-o em algum lugar, ....
Se suas mãos estão atadas, esta seria a próxima melhor coisa.
Insira os 10 principais valores em uma tabela temporária com base nos IDs em outra tabela
Dados
CREATE TABLE dbo.sometable(InsertTableId int, val varchar(255)); CREATE TABLE dbo.Top10Table(Top10TableId int, InsertTableId int, val varchar(255));
Cursor
Resultado
Substituindo o cursor por
CROSS APPLY
e umCTE
Outros exemplos
CROSS APPLY
aqui .Às vezes não há outra escolha
Se você não puder trabalhar em conjuntos e tiver que fazer o processamento linha por linha, ainda poderá otimizar o cursor.
Uma das maiores mudanças na aceleração do cursor é adicionar
LOCAL FAST_FORWARD
a ele.Dê uma olhada neste post de @AaronBertrand onde ele explica as possíveis diferenças de desempenho ao usar ou não usar configurações de cursor como
LOCAL
&FAST_FORWARD
.Não há "substituição geral" - você escondeu todo o "trabalho sujo" aqui, então é difícil dizer se há uma substituição específica neste caso. Certamente existem alguns casos específicos em que você está processando um conjunto de linhas uma linha de cada vez, seja usando um cursor, loop while ou qualquer outro processo iterativo, em que a conversão para um processo baseado em conjunto que processa todas as linhas em uma vez é muito melhor. Mas há outras coisas que só precisam ser feitas uma linha de cada vez, como executar um procedimento armazenado ou algum SQL dinâmico por linha, a mesma consulta em vários bancos de dados etc.
Cursor ou não, os problemas aos quais você está aludindo e vinculando são os mesmos se você usa declare cursor ou alguma outra estrutura de loop (veja este post ), e são irrelevantes quando o que você precisa fazer tem que ser feito uma linha por vez Tempo de qualquer maneira. Portanto, se você fornecer alguns detalhes específicos sobre o que esse cursor está fazendo, poderá obter alguns conselhos sobre como remover o cursor (ou que não pode), mas sua busca por uma abordagem mágica de eliminar todos os cursores que você pode aplicar para todos os cenários vai ser muito frustrante para você.
O conselho geral para novas pessoas que estão entrando no idioma, IMHO, deve ser sempre pensar sobre o que você precisa fazer em um conjunto de linhas , em oposição ao que você precisa fazer em cada linha em um conjunto . A diferença na linguagem é sutil, mas crucial. Se as pessoas pensarem no problema como um conjunto de dados em vez de um monte de linhas individuais, é menos provável que usem um cursor por padrão. Mas se eles vêm de diferentes tipos de programação - onde iterativo é a melhor / única maneira - além de simplesmente ensiná-los que o SQL Server não é otimizado para funcionar dessa maneira, não sei se há alguma maneira de tornar isso óbvio ou automático.
Sua pergunta ainda pede uma substituição geral, e ainda acredito que isso não existe.
Doug Lane fez uma série de vídeos chamados "T-SQL Level Up" que estão no YouTube. Parte da série explora uma abordagem geral para remover cursores mais ou menos assim:
SELECT
que são usadas posteriormente em umINSERT
podem ser substituídas por umaINSERT INTO...SELECT
instrução, por exemplo)IF...ELSE
) paraWHERE
cláusulas,CASE
instruções, subconsultas, etc.Como as outras ótimas respostas aqui apontaram, não há bala de prata para isso. Mas esses vídeos são, na minha opinião, uma abordagem realmente intuitiva para resolver o problema.
Doug passa por três substituições de cursor de complexidade crescente em cada parte, eu recomendo assistir (já que todo o negócio fica melhor no vídeo):