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.
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.
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.
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.
-- 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.
-- 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.
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!
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.
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.
-- 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).
-- 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?”
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