Olá Mundo,
Nesse post vou falar de mais um Caso do Dia a Dia sobre como o comando SET ROWCOUNT pode gerar alguns problemas e retornar um resultado inconsistente.
Mas antes, deixo aqui um pequeno desafio:
“Quantas linhas serão retornadas no script abaixo?”
OBS: Pense na resposta antes de rodar o script, não vale roubar hein.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
USE Traces CREATE TABLE TESTE_ROWCOUNT ( ID INT ) INSERT INTO TESTE_ROWCOUNT VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10) GO CREATE PROCEDURE stpTesteRowCount AS BEGIN SELECT * FROM TESTE_ROWCOUNT END GO SET ROWCOUNT 5 -- RESULTADO 1 SELECT TOP 10 * FROM TESTE_ROWCOUNT -- RESULTADO 2 EXEC stpTesteRowCount |
A) 10, 10
B) 5, 10
C) 10, 5
D) 5, 5
E) Não sei nem chutar, vou ler o post primeiro e depois respondo
No final do post eu darei a resposta, mas antes vamos ver alguns conceitos e exemplos. #gogogo
SET ROWCOUNT – Conceitos:
O comando SET ROWCOUNT é utilizado para fazer com que o SQL Server pare o processamento da query quando um número específico de registros for retornado.
Referência: https://docs.microsoft.com/pt-br/sql/t-sql/statements/set-rowcount-transact-sql?view=sql-server-ver15
O valor default é 0, mas na verdade isso significa que o SQL Server irá retornar todos os registros.
Vamos ver um exemplo bem simples para entender esse conceito. Vou criar uma tabela e inserir 10 registros. Depois vou utilizar o SET ROWCOUNT 5 e vamos ver o resultado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
USE Traces CREATE TABLE TESTE_ROWCOUNT ( ID INT ) INSERT INTO TESTE_ROWCOUNT VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10) SET ROWCOUNT 5 SELECT * FROM TESTE_ROWCOUNT -- (5 rows affected) |
O SQL Server retornou apenas 5 registros, sendo que a tabela possui 10 no total. Repare que não utilizamos nenhum filtro no SELECT.
Agora vamos voltar o ROWCOUNT para o valor default e executar o SELECT novamente.
1 2 3 4 5 |
SET ROWCOUNT 0 SELECT * FROM TESTE_ROWCOUNT -- (10 rows affected) |
Agora sim o SQL Server retornou todos os registros da tabela. Com isso, acredito que você tenha entendido o funcionamento do ROWCOUNT.
Observação Importante:
A própria Microsoft recomenda que você evite utilizar o SET ROWCOUNT, pois em alguma versão futura do SQL Server ele pode ter o comportamento alterado e recomenda que você modifique as aplicações que utilizam isso. Nesse caso, ela também indica a utilização do comando TOP que tem um comportamento similar.
Ou seja, se você utiliza o comando SET ROWCOUNT nas suas aplicações, você pode ter algum problema de inconsistência no resultado no futuro caso esse comportamento seja alterado. Se eu fosse você, planejaria uma alteração nisso o quanto antes para não ter dor de cabeça depois. #ficaadica
Deprecated Database Engine Features in SQL Server 2016:
Apenas reforçando que o SET ROWCOUNT está marcado como Deprecated, ou seja, ele pode ter o comportamento alterado em alguma versão futura do SQL Server.
“Entendi os conceitos Luiz, mas onde isso pode dar algum problema??? Parece ser algo tão simples e útil!”
Pois é, mas algumas coisas simples às vezes podem fazer um grande estrago. Vamos ver algumas situações.
SET ROWCOUNT – Exemplos:
Exemplo 1 – ROWCOUNT x UPDATE:
Nesse primeiro exemplo quero mostrar que você pode utilizar o ROWCOUNT para atualizar alguns registros. Aqui vou atualizar o ID de apenas 5 registros da tabela.
1 2 3 4 5 6 7 8 9 10 11 12 |
-- ROWCOUNT - UPDATE BEGIN TRAN SET ROWCOUNT 5 UPDATE TESTE_ROWCOUNT SET ID = 100 SET ROWCOUNT 0 SELECT * FROM TESTE_ROWCOUNT ROLLBACK |
Exemplo 2 – ROWCOUNT x DELETE:
Agora vou mostrar que você também pode utilizar o ROWCOUNT para excluir alguns registros. Aqui vou excluir 5 registros da tabela.
1 2 3 4 5 6 7 8 9 10 11 |
-- ROWCOUNT - DELETE BEGIN TRAN SET ROWCOUNT 5 DELETE TESTE_ROWCOUNT SET ROWCOUNT 0 SELECT * FROM TESTE_ROWCOUNT ROLLBACK |
Segue dois pontos que quero ressaltar aqui:
- Ao alterar o valor do ROWCOUNT, todos os comandos da sessão atual serão afetados. Por exemplo, se você alterar o valor do ROWCOUNT para 100, todos os SELECTs da sessão atual irão retornar no máximo 100 registros. Portanto, tome cuidado e fique ciente dos impactos que isso pode causar OK.
- O ROWCOUNT pode ter o comportamento alterado em alguma versão futura (está marcado como Deprecated). Já pensou se ao invés de atualizar ou excluir apenas 5 registros ele altere a tabela inteira ou não altere nada? Qual impacto isso pode causar na sua aplicação? Portanto, pense nisso se for optar por utilizar o ROWCOUNT.
Exemplo 3 – ROWCOUNT x PROCEDURES:
Vamos aumentar um pouco a dificuldade. Vou criar uma procedure bem simples que retorna todos os registros da tabela.
1 2 3 4 5 6 7 8 9 |
USE Traces GO CREATE PROCEDURE stpTesteRowCount AS BEGIN SELECT * FROM TESTE_ROWCOUNT END GO |
Será que o SELECT dentro dessa procedure também será afetado pelo SET ROWCOUNT? Bora testar!
1 2 3 |
SET ROWCOUNT 5 EXEC stpTesteRowCount |
Repare que o ROWCOUNT também afeta as procedures que você executa dentro da sessão que foi alterada. Isso pode ter um impacto muito grave, pois o resultado da procedure pode ser comprometido também!
Vamos ver mais um exemplo, dessa vez utilizando uma procedure que lista todos os registros da tabela, guarda em uma tabela temporária e por fim faz uma soma utilizando essa tabela temporária.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
GO CREATE PROCEDURE stpTesteSomaRowCount AS BEGIN SELECT ID INTO #TESTE_ID FROM TESTE_ROWCOUNT SELECT SUM(ID) AS SOMA FROM #TESTE_ID END GO SET ROWCOUNT 5 EXEC stpTesteSomaRowCount SET ROWCOUNT 0 EXEC stpTesteSomaRowCount |
“Caramba Luiz, o resultado fica diferente mesmo que doidera!!!”
Pois é, quando utilizamos o ROWCOUNT 5, o SQL Server vai guardar apenas os 5 primeiros registros na tabela temporária e por fim vai fazer a soma (1 + 2 + 3 + 4 + 5 = 15). Viu como ele pode ser perigoso!!!
TOP – Conceitos e Exemplos:
“Entendi Luiz, mas como eu posso evitar esses problemas do ROWCOUNT?”
Eu particularmente sempre utilizo o TOP ao invés do ROWCOUNT, pois o TOP afeta apenas a operação que ele é utilizado e não gera um impacto ou efeito colateral nos demais trechos do código (inclusive nas procedures).
Referência: https://docs.microsoft.com/pt-br/sql/t-sql/queries/top-transact-sql?view=sql-server-ver15
Aqui vou atualizar o valor do ID de apenas 5 registros aleatórios da tabela (cuidado que nem sempre vai ser os 5 primeiros, a ordenação só é garantida quando você utilizar o ORDER BY #ficaadica).
Repare que o SELECT após o UPDATE não será afetado e irá retornar todos os registros da tabela, diferentemente do ROWCOUNT que teria limitado a quantidade de registros dele também.
1 2 3 4 5 6 7 8 9 10 |
-- TOP - UPDATE BEGIN TRAN SET ROWCOUNT 0 UPDATE TOP (5) TESTE_ROWCOUNT SET ID = 100 SELECT * FROM TESTE_ROWCOUNT ROLLBACK |
Podemos também utilizar o TOP com o PERCENT para fazer algo com apenas um determinado percentual dos registros de uma tabela. No próximo exemplo, vou atualizar o valor do ID de apenas 20% dos registros da tabela (20% de 10 registros = 2 registros).
1 2 3 4 5 6 7 8 9 10 |
-- TOP PERCENT - UPDATE BEGIN TRAN SET ROWCOUNT 0 UPDATE TOP (20) PERCENT TESTE_ROWCOUNT SET ID = 100 SELECT * FROM TESTE_ROWCOUNT ROLLBACK |
Novamente o SELECT após o UPDATE não foi afetado e retornou todos os registros da tabela. Ou seja, realmente o TOP só afeta a operação que ele é utilizado e não afeta as operações posteriores.
Resposta:
Agora espero que você tenha aprendido um pouco mais sobre o SET ROWCOUNT e o TOP e seja capaz de dar a resposta correta.
“Quantas linhas serão retornadas no script abaixo?”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
USE Traces CREATE TABLE TESTE_ROWCOUNT ( ID INT ) INSERT INTO TESTE_ROWCOUNT VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10) GO CREATE PROCEDURE stpTesteRowCount AS BEGIN SELECT * FROM TESTE_ROWCOUNT END GO SET ROWCOUNT 5 -- RESULTADO 1 SELECT TOP 10 * FROM TESTE_ROWCOUNT -- RESULTADO 2 EXEC stpTesteRowCount |
Resposta Correta:
D) 5, 5
Espero que tenha gostado e que isso também possa ser útil no seu dia a dia. Até o próximo post!
Me siga no LinkedIn e YouTube para ficar por dentro das novidades.
Abraço,
Luiz Vitor França Lima
Consultor SQL Server
Ótimo alerta, Luiz. Principalmente quando estamos analisando o funcionamento de trecho de código SQL e não compreendemos porque os resultados retornados são inesperados.
Esse recurso provavelmente está presente em códigos SQL antigos. Mais um item a observar na pós-migração de bancos de dados antigos para versões atuais do SQL Server.
Obrigado José! Recentemente analisei uma procedure em um cliente que utiliza esse comando e ao testar uma alteração o resultado estava ficando diferente. Por isso resolvi fazer o post e alertar o pessoal =)
Abraço,
Luiz Vitor
Eu uso o rowcount para controle de quantidade máxima de transação
Por exemplo, suponha uma tabela tb1 com milhões de linhas e preciso limpar dados dela com algum critério:
declare @qtd=1
set rowcount= 10000
while @qtd>0
begin
begin tran
delete from tb1 where condicao
select @qtd = @@rowcount
commit tran
end
Nesse caso ele vai commitar a cada 10 mil linhas e evitar estourar o tempdb, melhorando o desempenho e não afetando tantos os outros usuários com problemas de lentidão.
Quando não houver mais registros, @@rowcount será zero e sairá do looping
Boa Jota,
Interessante a forma que usou o rowcount. Quando faço DELETE com LOOP normalmente utilizo alguma variável para fazer esse controle.
Obrigado por compartilhar o seu caso e realmente é uma boa prática fazer o DELETE aos poucos para a transação não ficar muito grande.
Abraço,
Luiz Lima