Vous êtes sur la page 1sur 11

Triguer baixar o Estoque

CREATE TRIGGER TG_BAIXA_ESTOQUE ON ITEMSAIDA FOR INSERT AS DECLARE @PRO_QUANT int DECLARE @ISA_QUANT int DECLARE @PRO_CODIGO int SELECT @ISA_QUANT = ISA_QUANT, @PRO_CODIGO = PRO_CODIGO FROM INSERTED UPDATE PRODUTO SET PRO_QUANT = PRO_QUANT - @ISA_QUANT WHERE PRO_CODIGO = @PRO_CODIGO

declare @qtd float,

@codigo int Select @codigo=CodPro,@Qtd = QtdPro From ProdVendido where CodVen = @CodVenda
Update produtos set qtd = qtd-@qtd where codprod = @codigo

Meu Sisconv
CREATE PROCEDURE uspEstoqueBaixar ON Vendas @procodigo float, @proestoque float, @DataVenda date, @Quantidade float AS Begin SELECT @Provodigo = pro_codigo, @proestoque = pro_estoque, @DataVenda = ven_data FROM INSERTED UPDATE Produto SET pro_estoque = pro_estoque UltimaAtualizacao = @DataVenda WHERE pro_codigo = @procodigo @Quantidade,

end

2 Dica

Aps uma tempestuosa semana e iniciada a contagem regressiva para o show doPaul Van Dyk, estou dando uma passadinha por aqui. J faz um tempinho que no posto nada na categoria "Piores Prticas". Hoje vi no frum MSDN uma dvida a respeito de triggers e

fiquei pensando um pouco sobre elas. No exatamente sobre as questes de desempenho, mas sobre a forma como elas normalmente so construdas e exemplificadas. A receita bem simples. Basta construir a trigger, capturar o valor de cada coluna da INSERTED ou da DELETED em uma varivel e fazer as devidas manipulaes. Embora a receita bsica funcione na maioria das situaes e seja impressionante o quanto se codifica dessa forma, a verdade que ela deficiente. Trabalhar com variveis em triggers restring-las a registros e operaes linha a linha. Toda vez que se constri uma trigger com essa lgica, normalmente se retira a capacidade delas em trabalhar com mltiplos registros, pois, haver problemas ou BUGs de aplicao. Vejamos essas limitaes um pouco mais de perto.
Muda o contexto para o TempDB Use TempDB Cria um tabela de Produtos CREATE TABLE Produto ( ProdutoID INT NOT NULL, NomeProduto VARCHAR(50) NOT NULL, Estoque INT NOT NULL, UltimaAtualizacao DATE NOT NULL, CONSTRAINT PK_Produto PRIMARY KEY (ProdutoID)) Cria uma tabela de Vendas CREATE TABLE Venda ( VendaID INT NOT NULL, DataVenda DATE NOT NULL, ProdutoID INT NOT NULL, Quantidade INT NOT NULL, Preco SMALLMONEY NOT NULL, CONSTRAINT PK_Venda PRIMARY KEY (VendaID))

O raciocnio bem simples. A cada venda realizada, o estoque do produto deve ser diminudo, pois, uma venda representa uma sada de estoque. Isso pode ser feito via aplicao ou via stored procedure, mas esse um caso clssico para utilizao de triggers. Vejamos um exemplo bem tpico de implementao para esse caso.
Cria uma trigger para atualizar o estoque CREATE TRIGGER trgBaixaEstoque ON Venda FOR INSERT AS BEGIN Declara as variveis para atualizao DECLARE @ProdutoID INT, @Quantidade INT, @DataVenda DATE

Captura os valores das colunas para as variveis SELECT @ProdutoID = ProdutoID, @Quantidade = Quantidade, @DataVenda = DataVenda FROM INSERTED Atualiza o estoque UPDATE Produto SET Estoque = Estoque @Quantidade, UltimaAtualizacao = @DataVenda WHERE ProdutoID = @ProdutoID END

Vejamos agora a trigger em ao. O cdigo a seguir ir criar alguns produtos e efetuar algumas vendas. Se tudo funcionar como proposto, a cada venda o estoque deve ter a devida baixa.

Insere dois produtos INSERT INTO Produto (ProdutoID, NomeProduto, Estoque, UltimaAtualizacao) VALUES (1,MP3 Player,20,20100120) INSERT INTO Produto (ProdutoID, NomeProduto, Estoque, UltimaAtualizacao) VALUES (2,Pen Drive,17,20100120) Insere trs vendas INSERT INTO Venda (VendaID, DataVenda, ProdutoID, Quantidade, Preco) VALUES (1,20100121,1,3,150.00) INSERT INTO Venda (VendaID, DataVenda, ProdutoID, Quantidade, Preco) VALUES (2,20100121,1,2,145.00) INSERT INTO Venda (VendaID, DataVenda, ProdutoID, Quantidade, Preco) VALUES (3,20100122,2,1,90.00) Verifica a tabela de Produtos SELECT ProdutoID, NomeProduto, Estoque, UltimaAtualizacao FROM Produto

Antes de verificarmos os resultados provocados pela trigger, podemos fazer uma simples conta. O produto 1 (MP3 Player) possui 20 unidades e sua data de atualizao foi 20/01/2010. Como foram realizadas duas vendas no dia 21, e cada venda teve respectivamente a quantidade de 3 e 2 unidades, o estoque deve ficar em 15 unidades e a ltima atualizao deve ser dia 21/01/2010. No caso do produto 2 (Pen Drive), o estoque possua 17 unidades. Com a venda do dia 22/01/2010 em uma unidade, o estoque deve ficar em 16 unidades e a data da ltima atualizao deve ser 22/01/2010. O resultado do SELECT exibido conforme a tabela abaixo:

ProdutoID 1 2

NomeProduto MP3 Player Pen Drive

Estoque 15 16

UltimaAtualizacao 21/01/2010 22/01/2010

Os resultados bateram com a conta e a trigger parece estar funcionando adequadamente, mas ser que s isso suficiente ? A trigger est preparada para lidar com inseres individuais, mas o que aconteceria se houvesse uma carga em lote ?
Cria uma tabela para armazenar as vendas ao longo do dia (supostamente de outra loja) CREATE TABLE VendaFilial ( VendaID INT NOT NULL, DataVenda DATE NOT NULL, ProdutoID INT NOT NULL, Quantidade INT NOT NULL, Preco SMALLMONEY NOT NULL, CONSTRAINT PK_VendaFilial PRIMARY KEY (VendaID)) Insere duas vendas INSERT INTO VendaFilial (VendaID, DataVenda, ProdutoID, Quantidade, Preco) VALUES (4,20100122,1,2,147.50) INSERT INTO VendaFilial (VendaID, DataVenda, ProdutoID, Quantidade, Preco) VALUES (5,20100123,1,1,148.50) Insere todas as vendas da filial na tabela de vendas INSERT INTO Venda (VendaID, DataVenda, ProdutoID, Quantidade, Preco) SELECT VendaID, DataVenda, ProdutoID, Quantidade, Preco FROM VendaFilial Verifica a tabela de Produtos SELECT ProdutoID, NomeProduto, Estoque, UltimaAtualizacao FROM Produto

As duas vendas realizadas na filial e registradas nas tabelas de vendas de filial foram referente ao produto 1 (MP3 Player) e totalizam 3 unidades. Se o estoque do produto 1 era de 15 unidades e houve uma baixa de 3 unidades sendo a ltima venda do dia 23/01/2010, o natural esperar que o estoque fique em 12 unidades e a data de atualizao seja a do dia 23/01/2010. Vejamos o resultado da ltima consulta:

ProdutoID 1 2

NomeProduto MP3 Player Pen Drive

Estoque 13 16

UltimaAtualizacao 22/01/2010 22/01/2010

Ao contrrio do esperado, o produto 1 (MP3 Player) ficou com o estoque em 13 unidades (e no 12) e a data de atualizao foi 22/01/2010 e no 23/01/2010. A lgica da trigger no parece ter erros mas o que ser que aconteceu ? H algumas respostas possveis, mas o fato que a trigger foi preparada para trabalhar com linhas e no com conjuntos. O trecho de cdigo simula o mesmo problema da trigger.
Declara as variveis para atualizao DECLARE @ProdutoID INT, @Quantidade INT, @DataVenda DATE

Captura os valores das colunas para as variveis SELECT @ProdutoID = ProdutoID, @Quantidade = Quantidade, @DataVenda = DataVenda FROM VendaFilial Recupera o valor das variveis SELECT @ProdutoID As ProdutoID, @Quantidade As Quantidade, @DataVendaAs DataVenda

O resultado interessante. Embora a tabela VendaFilial tenha duas linhas, a atribuio das variveis capturou apenas uma das linhas (no meu caso a do dia 23). Isso compreensvel, pois, as variveis esto preparadas para receber um valor e no mltiplos valores. Se h por exemplo duas quantidades, no h como a variavl @quantidade capturar as duas quantidades (2 e 1). Se h duas datas, no h como a varivel @DataVenda capturar as duas datas (22/01/2010 e 23/01/2010). por isso que a trigger no funcionou. Por que ela estava preparada para trabalhar com uma linha por vez e quando foram submetidas duas linhas, ou melhor um conjunto de duas linhas, a trigger se perdeu e no pode calcular corretamente o estoque. Ainda que trocssemos o SELECT por SET, o problema iria persistir.
Atualiza o produto 1 em relao ao estoque UPDATE Produto SET Estoque = 15, UltimaAtualizacao = 20100121 WHERE ProdutoID = 1 Exclui as duas ltimas vendas para no ocorrer violao de PK DELETE FROM Venda WHERE VendaID > 3 Troca o SELECT da trigger por SET ALTER TRIGGER trgBaixaEstoque ON Venda FOR INSERT AS BEGIN Declara as variveis para atualizao DECLARE @ProdutoID INT, @Quantidade INT, @DataVenda DATE Captura os valores das colunas para as variveis SET @ProdutoID = (SELECT ProdutoID FROM INSERTED) SET @Quantidade = (SELECT Quantidade FROM INSERTED) SET @DataVenda = (SELECT DataVenda FROM INSERTED) Atualiza o estoque UPDATE Produto SET Estoque = Estoque @Quantidade, UltimaAtualizacao = @DataVenda WHERE ProdutoID = @ProdutoID END O Campo UltimaAtualizao Tabela Produto O Campo quantidade e da tabela VendaItens

Insere todas as vendas da filial na tabela de vendas INSERT INTO Venda (VendaID, DataVenda, ProdutoID, Quantidade, Preco) SELECT VendaID, DataVenda, ProdutoID, Quantidade, Preco FROM VendaFilial

A substituio do SELECT pelo SET no muda o fato de que variveis s podem receber um nico valor. Como as inseres so feitas da tabela de filial para a tabela de vendas e a tabela de filial contm duas linhas, a tabela INSERTED fatalmente ter duas linhas (as triggers so disparadas por evento e no por linhas). No momento da atribuio da varivel @Quantidade, a tabela INSERTED ter duas quantidades (2 e 1). No momento da atribuio da varivel @DataVenda, a tabela INSERTED ter duas datas (22/01/2010 e 23/01/2010). Mesmo o ProdutoID sendo repetido, no possvel armazenar o ID 1 duas vezes na mesma varivel em um mesma instruo. Aps a execuo, um erro gerado:

Msg 512, Level 16, State 1, Procedure trgBaixaEstoque, Line 10 Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression. The statement has been terminated.

O erro s evidencia o que j foi colocado. O fato das trs subqueries retornarem dois valores cada e tentarem atribuir os resultados s variveis @ProdutoID, @Quantidade e @DataVenda faz com que mltiplos valores sejam atribudos s variveis incorrendo no erro "Subquery returned more than 1 value". Isso mostra que em algumas situaes usar o SET pode ser mais "inteligente" que o SELECT, pois, o SELECT repassou um clculo errado enquanto que o SET impediu o clculo. Entretanto, ambas as implementaes foram preparadas para utilizar linhas e no conjuntos. Alguns praticantes do ORACLE vo dizer que "se fosse no ORACLE, teramos o comando FOR EACH ROW dentro da trigger". Concordo que o FOR EACH ROW tem sua utilidade e que possivelmente veremos alguma coisa desse tipo em uma release futura do SQL Server, mas no acho que ele seja a soluo para os problemas. Na verdade acho que ela a continuao para a filosofia "linha a linha" que performaticamente no a melhor situao para esse caso. Como o SQL Server ainda no possui o recurso de FOR EACH ROW dentro da trigger, a soluo utilizar os cursores que podem trabalhar linha a linha.
"Prepara" a trigger para operar mltiplas linhas ALTER TRIGGER trgBaixaEstoque ON Venda FOR INSERT AS BEGIN Declara as variveis para atualizao DECLARE @ProdutoID INT, @Quantidade INT, @DataVenda DATE

Declara um cursor para "varrer" a INSERTED DECLARE cVendas CURSOR FAST_FORWARD FOR SELECT ProdutoID, Quantidade, DataVenda FROM INSERTED necessrio ordenar para evitar que uma venda mais antiga atualize a data erroneamente ORDER BY ProdutoID, DataVenda Abre o cursor OPEN cVendas L o primeiro registro FETCH NEXT FROM cVendas INTO @ProdutoID, @Quantidade, @DataVenda Varre os demais registros WHILE @@FETCH_STATUS = 0 BEGIN Atualiza o estoque UPDATE Produto SET Estoque = Estoque @Quantidade, UltimaAtualizacao = @DataVenda WHERE ProdutoID = @ProdutoID Passa para o prximo registro FETCH NEXT FROM cVendas INTO @ProdutoID, @Quantidade, @DataVenda END Fecha o cursor CLOSE cVendas Desaloca o cursor DEALLOCATE cVendas END Insere todas as vendas da filial na tabela de vendas INSERT INTO Venda (VendaID, DataVenda, ProdutoID, Quantidade, Preco) SELECT VendaID, DataVenda, ProdutoID, Quantidade, Preco FROM VendaFilial Verifica a tabela de Produtos SELECT ProdutoID, NomeProduto, Estoque, UltimaAtualizacao FROM Produto

Como o cursor faz a varredura linha a linha, dessa vez o estoque e a ltima atualizao foram atualizados corretamente:

ProdutoID 1 2

NomeProduto MP3 Player Pen Drive

Estoque 12 16

UltimaAtualizacao 23/01/2010 22/01/2010

Dizem que cursores no so performticos. Falam o mesmo sobre as triggers. Se cursores no so performticos e triggers tambm no, bem provvel que colocar um cursor dentro

de uma trigger no seja nada performtico. Vejamos ento como resolver isso de uma maneira simples e performtica.
Exclui as vendas da filial DELETE FROM VendaFilial Insere duas vendas INSERT INTO VendaFilial (VendaID, DataVenda, ProdutoID, Quantidade, Preco) VALUES (6,20100124,1,2,146.00) INSERT INTO VendaFilial (VendaID, DataVenda, ProdutoID, Quantidade, Preco) VALUES (7,20100125,2,3,85.50) Altera a trigger ALTER TRIGGER trgBaixaEstoque ON Venda FOR INSERT AS BEGIN Atualiza a tabela de produtos UPDATE Produto SET Estoque = Estoque Quantidade, UltimaAtualizacao = DataVenda FROM Produto As Prod INNER JOIN INSERTED As Ins ON Prod.ProdutoID = Ins.ProdutoID END Insere todas as vendas da filial na tabela de vendas INSERT INTO Venda (VendaID, DataVenda, ProdutoID, Quantidade, Preco) SELECT VendaID, DataVenda, ProdutoID, Quantidade, Preco FROM VendaFilial Verifica a tabela de Produtos SELECT ProdutoID, NomeProduto, Estoque, UltimaAtualizacao FROM Produto

Se a conta for feita, o produto 1 (MP3 Player) tinha 12 unidades e a ltima atualizao era 23/01/2010. No caso do produto 2, o estoque era de 16 unidades e a ltima atualizao era de 22/01/2010. Com as ltimas vendas, o produto 1 teve uma baixa de 2 unidades no dia 24/01/2010 e o produto 2 teve uma baixa de 3 unidades no dia 25, o que deixa os produtos com 10 e 13 unidades respectivamente.

ProdutoID 1 2

NomeProduto MP3 Player Pen Drive

Estoque 10 13

UltimaAtualizacao 24/01/2010 25/01/2010

Se o cdigo dessa nova verso da trigger for comparado com o cdigo anterior h notrias vantagens. O tamanho do cdigo muito inferior o que acaba tornando-o muito mais simples e fcil de manter. Substituir o uso do cursor por um nico comando, sem declarao de variveis, e atualizaes linha a linha tambm muito mais performtico.

Embora a ltima verso da trigger tenha melhorado a qualidade do cdigo, ela ainda possui uma falha. Vejamos o exemplo abaixo:
Exclui as vendas da filial DELETE FROM VendaFilial Insere duas vendas INSERT INTO VendaFilial (VendaID, DataVenda, ProdutoID, Quantidade, Preco) VALUES (8,20100127,2,1,90.00) INSERT INTO VendaFilial (VendaID, DataVenda, ProdutoID, Quantidade, Preco) VALUES (9,20100126,2,4,87.50) Insere todas as vendas da filial na tabela de vendas INSERT INTO Venda (VendaID, DataVenda, ProdutoID, Quantidade, Preco) SELECT VendaID, DataVenda, ProdutoID, Quantidade, Preco FROM VendaFilial Verifica a tabela de Produtos SELECT ProdutoID, NomeProduto, Estoque, UltimaAtualizacao FROM Produto

As duas vendas so do produto 2 (Pen Drive) e totalizam 5 unidades. Embora seja o primeiro registro, a venda mais recente do dia 27/01/2010. Se a trigger funcionar corretamente, o estoque deve ficar em 8 unidades e a data da ltima atualizao deve ser 27/01/2010.

ProdutoID 1 2

NomeProduto MP3 Player Pen Drive

Estoque 10 9

UltimaAtualizacao 24/01/2010 26/01/2010

A trigger no funcionou corretamente, pois, o estoque ficou em 9 unidades e a data da ltima atualizao ficou em 26/01/2010. Visualmente possvel perceber que a primeira venda no foi considerada. Se o estoque possua 13 unidades e ficou com apenas 9 unidades e a data de atualizao foi 26/01/2010 porque apenas a segunda venda foi considerada. Isso ocorre porque um comando de updade s pode atualizar um registro apenas um vez. A presena de duas vendas para o produto 2 (Pen Drive) iria requerer duas atualizaes para esse produto, mas no possvel fazer isso em um nico update. Para evitar essa restrio, basta fazer um pequeno ajuste na trigger.
Atualiza o produto 2 em relao ao estoque UPDATE Produto SET Estoque = 13, UltimaAtualizacao = 20100125 WHERE ProdutoID = 2 Exclui as duas ltimas vendas (8 e 9) DELETE FROM Venda WHERE VendaID IN (8,9)

Altera a trigger ALTER TRIGGER trgBaixaEstoque ON Venda FOR INSERT AS BEGIN Totaliza as vendas por produto ;WITH UltimasVendas (ProdutoID, QuantidadeTotal, UltimaVenda) As ( SELECT ProdutoID, SUM(Quantidade), MAX(DataVenda) FROM INSERTED GROUP BY ProdutoID) Atualiza a tabela de produtos UPDATE Produto SET Estoque = Estoque QuantidadeTotal, UltimaAtualizacao = UltimaVenda FROM Produto As Prod INNER JOIN UltimasVendas As UV ON Prod.ProdutoID = UV.ProdutoID END Insere todas as vendas da filial na tabela de vendas INSERT INTO Venda (VendaID, DataVenda, ProdutoID, Quantidade, Preco) SELECT VendaID, DataVenda, ProdutoID, Quantidade, Preco FROM VendaFilial Verifica a tabela de Produtos SELECT ProdutoID, NomeProduto, Estoque, UltimaAtualizacao FROM Produto

A primeira parte do script retorna a situao do produto para o momento anterior ao das vendas 8 e 9 realizadas na filial. Essa alterao foi feita, porque o estoque tinha sido incorretamente calculado no pela verso anterior da trigger. Ainda nessa primeira parte, as vendas 8 e 9 so excludas da tabela Vendas para serem reinseridas com o disparo da trigger. A segunda parte do script altera o cdigo da trigger e faz a reinsero das vendas da filial para que o estoque e data da ltima atualizao dos produtos seja recalculada.

ProdutoID 1 2

NomeProduto MP3 Player Pen Drive

Estoque 10 8

UltimaAtualizacao 24/01/2010 27/01/2010

Dessa vez a atualizao do produto foi feita corretamente. O estoque ficou em 8 unidades e a data da ltima atualizao foi 27/01/2010. Como a trigger totalizou os dados de produto somando a quantidade e calculado a ltima data, cada produto ter apenas uma nica ocorrncia na CTE UltimasVendas. Isso possibilita que o UPDATE seja feito corretamente e o estoque seja atualizado. Mesmo com o passo adicional o cdigo ainda se torna simples e enxuto. No foi declarado um nico cursor e nem sequer uma varivel. Enquanto o primeiro cdigo estava preparado para operar apenas um linha por vez, esse cdigo est

apto a trabalhar com quantas vendas ocorrerem. Essa a diferena entre trabalhar em uma filosofia linha a linha e uma filosofia baseada em conjuntos. A filosofia em conjuntos alm de mais performtica, consegue trabalhar com linhas individuais e o contrrio no verdadeiro. Haver aqueles que questionaro esse exemplo. Se existir uma tabela de filial, a soluo seria bem simples. Bastaria incluir a trigger na tabela de vendas da filial e assim os produtos seriam atualizados automaticamente. Para combinar as vendas um UNION ALL bastaria. A verdade que dificilmente haver uma tabela de vendas da filial. A idia de crila foi apenas para mostrar as deficincias de triggers preparadas para lidar linha a linha principalmente em processos de carga. Podemos imaginar as mesmas consequncias, se por exemplo, houvesse uma carga de vendas a partir de um arquivo texto, um job, um processo de replicao, ou um pacote SSIS. Independente do mtodo, o fato que triggers linha a linha no esto prontas para operar operaes que envolvam mais de um registro e triggers com lgica baseadas em conjunto sempre estaro aptas a trabalhar com qualquer situao seja um ou registro por vez ou vrios de uma nica s vez. Por essas e outras que o mais recomendvel sempre pensar em conjuntos e nunca em linhas quando estiver codificando uma trigger. Santigo, existe uma ferramenta no MYSQL (mysql tools que tem nele o Migration Tool Kit). com ele voc pode exportar diretamente no Mysql para o MSSQL.

Vous aimerez peut-être aussi