none
Cursor ou variável table? RRS feed

  • Pergunta

  • O que é mais performático o cursor ou a variável table?

    Para exemplificar montarei uma consulta com a variável table (como geralmente eu faço).

    Declare @tabela as table (codigo int, nome varchar(30), usou bit)

    Insert Into @tabela (codigo,Nome,Usou) Select Codigo, Nome,0 From Produto

    While (Select Count(*) From @tabela Where usou = 0) > 0

    Begin

    declare @codigo int = 0

    select top 1 @codigo = codigo from @tabela where usou = 0

    update @tabela set

    usou = 1

    where codigo = @codigo

    End

    Bem essa é a estrutura que eu uso para fazer loop. Aceito sugestões de melhorias.


    Concatenado
    sexta-feira, 18 de novembro de 2011 11:40

Respostas

  • Boa Tarde,

    Normalmente eu não uso nenhum nem outro quando você pode fazer tudo em um comando só. Muitas vezes, os implementadores constroem soluções que pegam um conjunto de registros e lê linha a linha para fazer algo que poderia ser feito de uma vez só. Nessas situações, o uso de tabelas do tipo TABLE e cursores costuma gerar mais código e comporta-se de forma menos performática.

    Entretanto, há situações em que isso não é possível, pois, se você tiver que executar uma SP para cada registro então o loop é mesmo necessário (ainda assim a revisão da SP pode trazer diversas melhorias).

    Já vi situações onde variáveis do tipo TABLE ganharam e outras que o cursor ganhou (normalmente para muitas linhas). Ainda assim, é importante fazer as coisas de forma performática independente da implementação. Se for usar cursores, coloque o FAST_FORWARD se aplicável. No seu hipotético exemplo com tabelas do tipo TABLE há duas considerações a fazer:

    - Retire o COUNT do WHILE. Crie uma variável, calcule a quantidade de linhas uma única vez e use essa variável
    - Na variável @tabela use apenas as colunas necessárias. Se você vai usar só a coluna codigo não crie a tabela com a coluna nome
    - Utilize uma coluna Identity para efetuar o controle ao invés de uma coluna bit. Isso evita a recontagem múltiplas vezes. Isso é particularmente importante para tabelas com muitos registros já que você não pode criar um índice na variável e cada COUNT(*) seria um SCAN

    Sugiro mudar a implementação:

    Declare @tabela as table (Seq INT IDENTITY(1,1) PRIMARY KEY, codigo int)
    Insert Into @tabela (codigo) Select Codigo From Produto

    DECLARE @TotalLinhas INT, @IntContador INT
    SET @TotalLinhas = (SELECT COUNT(*) FROM @Tabela)
    SET @IntContador = 0

    While @IntContador < @TotalLinhas
     
    Begin
     
    --Aqui eu coloco o procedimento que eu desejo realizar para cada item da tabela
    SET @IntContador = @IntContador + 1
     
    End

    [ ]s,

    Gustavo Maia Aguiar
    Blog: http://gustavomaiaaguiar.wordpress.com
    Vídeos: http://www.youtube.com/user/gmasql


    Classifique as respostas. O seu feedback é imprescindível
    • Marcado como Resposta Thiago, Moura segunda-feira, 21 de novembro de 2011 10:26
    sexta-feira, 18 de novembro de 2011 16:36

Todas as Respostas

  • Thiago

    Acredito que sua pergunta é referente a Cursor x While

    eu diria que qq coisa serializado será mais lento do que algo feito em batch, neste seu exemplo vc esta atualizando o campo usou para 1 onde era =0 sendo assim basta fazer

    update

     

    @tabela set usou =1 where usou =

    0


    Att.
    Marcelo Fernandes

    MCP, MCDBA, MCSA, MCTS.
    Se útil, classifique!!!
    Me siga no twitter: @marcelodba

    sexta-feira, 18 de novembro de 2011 12:08
  • Não, isso é a minha estrutura de loop que eu utilizo no sql server. Eu monto sempre essa estrutura e insiro o que eu preciso fazer no meio do batch. Esse campo usou é apenas para uma maneira que eu possuo para fazer com que todos os itens da tabela sejam percorridos, entendeu???

    Exemplo:

    Declare @tabela as table (codigo int, nome varchar(30), usou bit)

    Insert Into @tabela (codigo,Nome,Usou) Select Codigo, Nome,0 From Produto

    While (Select Count(*) From @tabela Where usou = 0) > 0

    Begin

    declare @codigo int = 0

    select top 1 @codigo = codigo from @tabela where usou = 0

    --Aqui eu coloco o procedimento que eu desejo realizar para cada item da tabela

    update @tabela set

    usou = 1

    where codigo = @codigo

    End

     


    Concatenado
    sexta-feira, 18 de novembro de 2011 15:54
  • Boa Tarde,

    Normalmente eu não uso nenhum nem outro quando você pode fazer tudo em um comando só. Muitas vezes, os implementadores constroem soluções que pegam um conjunto de registros e lê linha a linha para fazer algo que poderia ser feito de uma vez só. Nessas situações, o uso de tabelas do tipo TABLE e cursores costuma gerar mais código e comporta-se de forma menos performática.

    Entretanto, há situações em que isso não é possível, pois, se você tiver que executar uma SP para cada registro então o loop é mesmo necessário (ainda assim a revisão da SP pode trazer diversas melhorias).

    Já vi situações onde variáveis do tipo TABLE ganharam e outras que o cursor ganhou (normalmente para muitas linhas). Ainda assim, é importante fazer as coisas de forma performática independente da implementação. Se for usar cursores, coloque o FAST_FORWARD se aplicável. No seu hipotético exemplo com tabelas do tipo TABLE há duas considerações a fazer:

    - Retire o COUNT do WHILE. Crie uma variável, calcule a quantidade de linhas uma única vez e use essa variável
    - Na variável @tabela use apenas as colunas necessárias. Se você vai usar só a coluna codigo não crie a tabela com a coluna nome
    - Utilize uma coluna Identity para efetuar o controle ao invés de uma coluna bit. Isso evita a recontagem múltiplas vezes. Isso é particularmente importante para tabelas com muitos registros já que você não pode criar um índice na variável e cada COUNT(*) seria um SCAN

    Sugiro mudar a implementação:

    Declare @tabela as table (Seq INT IDENTITY(1,1) PRIMARY KEY, codigo int)
    Insert Into @tabela (codigo) Select Codigo From Produto

    DECLARE @TotalLinhas INT, @IntContador INT
    SET @TotalLinhas = (SELECT COUNT(*) FROM @Tabela)
    SET @IntContador = 0

    While @IntContador < @TotalLinhas
     
    Begin
     
    --Aqui eu coloco o procedimento que eu desejo realizar para cada item da tabela
    SET @IntContador = @IntContador + 1
     
    End

    [ ]s,

    Gustavo Maia Aguiar
    Blog: http://gustavomaiaaguiar.wordpress.com
    Vídeos: http://www.youtube.com/user/gmasql


    Classifique as respostas. O seu feedback é imprescindível
    • Marcado como Resposta Thiago, Moura segunda-feira, 21 de novembro de 2011 10:26
    sexta-feira, 18 de novembro de 2011 16:36