Archive

Posts Tagged ‘Cursor’

Desafio T-SQL

Ola pessoal, tudo bem?

Essa semana presenciei um caso aonde era necessário ajudar a desenvolver uma instrução T-SQL que fugia um pouco do nosso dia-a-dia, devido a isso resolvir postar no blog um desafio para compartilhar com todos a solução depois. Espero que muitas pessoas participem.

Vamos ao cenário.

Imagine que o Sistema X realiza o “track” de contratos de uma instituição financeira que são gerados pelo sistema Y, no entanto,o sistema Y gera mais de UMA LINHA para cada contrato toda vez que esse contrato é renegociado. Isso significa que, cada conjunto de contrato no sistema Y terá mais de uma linha baseando-se no número de contrato mais codigo de movimentação do mesmo (Contrato + Mov). Vejam um sample dos dados, conforme a seguir:

O sistema X precisa transformar o resulte set da imagem anterior em um único registro levando as seguintes regras em consideração:

  1. O status 0 significa – Precisa ser avaliado. O Status 1 significa: Registro Válido e o Status 2 significa que o registro pode ser descartado.
  2. Se o os valores em Dt1 e Dt2 for NULOS para para um conjunto de registro (Contrato + Processo) utilizo o registro com a MENOR DtReal.
    1. Obs: O print não atende essa condição, mas, consideraria asegunda linha da minha tabela:

       

       

  3. Se algum dos valores em Dt1 e Dt2 não for NULO para um conjunto de registro (Contrato + Processo) utilizo o registro com a MAIOR DtReal como base.
  4. Quando a regra de número 3 for atendida é necessário “montar” o registro com a seguinte “sub-regra”:
    1. Se o MAX VALUE da coluna Dt1 do conjunto de registro (Contrato + Processo) for maior ou igual a minha DtReal da linha atual, considero o MAX (no caso o valor 25/08/2013) para essa coluna. (Nesse caso minha coluna DtReal seria 22/08/2013 + Dt1 = 25/08/2013). No entanto, temos outra “regrinha” que no caso a regra “4.b”.
    2. Se o MAX VALUE da coluna Dt1 do conjunto de registro (Contrato + Processo) for maior ou igual ao MAX VALUE da DtReal do conjunto de registro (Contrato + Processo) o valor da coluna DtReal DEVE ser a menor data.O registro deve ser como a seguir:

       

  5. Se o MAX VALUE da coluna Dt1 do conjunto de registro (Contrato + Processo) NÃO for maior que a minha linha BASE (no caso a DtReal=22/08/2013, seguindo a regra de número 3) , conforme print:

    Nesse caso minha linha deve ser montada da seguinte maneira:

  6. Minha última regra consiste no seguinte: Se o MAX VALUE da coluna Dt1 do conjunto de registro (Contrato + Processo) NÃO for maior que a minha linha BASE (no caso a DtReal=22/08/2013, seguindo a regra de número 3) E o valor da Coluna Dt1 da linha BASE (no caso a linha que possi o valor DtReal = 22/08/2013) NÃO for NULA, minha linha consiste na DtReal + Dt1 daquela linha. Veja o cenário:

     

    Para a regra de número 6 o meu registro deve ser algo mais o menos assim:

     

     

Observações:

  • Os registros que devem aparecer na solução no final devem ter o valor da coluna Status modificado para 1
  • A solução pode ser escrita nas seguintes versões do SQL Server: 2008,200R2, 2012 e 2014.
  • A solução pode ser feita em mais de uma consulta para atender as regras.
  • Não é permitido a ulização de abordagens linha-a-linha (While e CUSRSOR)

Estou disponibilizando o script que monta os cenários com todos os registros. A ideeia inicial é desenvolver um pouco a lógica e fazer exercícios de T-SQL. No futuro podemos utilizar o mesmo cenário e avaliar o desempenho das sugestões que serão enviadas e discutir todas elas.

use tempdb

go

–Create sample datas

CREATE
TABLE #T
(Contrato VARCHAR(50), Mov INT, DtReal date, Dt1 date, Dt2 date,[Status] tinyint)

–DROP TABLE #T

INSERT
INTO #T(Contrato, Mov,DtReal,Dt1,Dt2, [Status])


VALUES


(’00XXXXX.25.0000′, 80193, ‘20130701’,
NULL        ,NULL,0)


,(’00XXXXX.25.0000′, 80193, ‘20130617’,
NULL        ,NULL,0)


,(’00XXXXX.25.0000′, 80193, ‘20130821’, ‘20130825’    ,NULL,0)


,(’00XXXXX.25.0000′, 80193, ‘20130822’,
NULL        ,NULL,0)

 

            ,(’00YYYYY.26.0000′, 80192, ‘20130701’, ‘20130820’    ,NULL,0)


,(’00YYYYY.26.0000′, 80192, ‘20130617’,
NULL        ,NULL,0)


,(’00YYYYY.26.0000′, 80192, ‘20130821’,
NULL        ,NULL,0)


,(’00YYYYY.26.0000′, 80192, ‘20130822’,
NULL        ,NULL,0)

 


,(’00ZZZZZ.27.0000′, 80194, ‘20130701’,
NULL        ,NULL,0)


,(’00ZZZZZ.27.0000′, 80194, ‘20130617’, ‘20130825’    ,NULL,0)


,(’00ZZZZZ.27.0000′, 80194, ‘20130821’,
NULL        ,NULL,0)


,(’00ZZZZZ.27.0000′, 80194, ‘20130822’, ‘20130821’    ,NULL,0)

 


,(’00WWWWW.28.0000′, 80195, ‘20130701’,
NULL        ,NULL,0)


,(’00WWWWW.28.0000′, 80195, ‘20130617’, ‘20130820’    ,NULL,0)


,(’00WWWWW.28.0000′, 80195, ‘20130821’,
NULL        ,NULL,0)


,(’00WWWWW.28.0000′, 80195, ‘20130822’, ‘20130821’    ,NULL,0)

 

            ,(’00KKKKK.29.0000′, 80196, ‘20130701’,
NULL        ,NULL,0)


,(’00KKKKK.29.0000′, 80196, ‘20130617’,
NULL        ,NULL,0)


,(’00KKKKK.29.0000′, 80196, ‘20130821’,
NULL        ,NULL,0)


,(’00KKKKK.29.0000′, 80196, ‘20130822’,
NULL        ,NULL,0)

 

            ,(’00AAAAA.30.0000′, 80196, ‘20130701’,
NULL        ,NULL,0)


,(’00AAAAA.30.0000′, 80196, ‘20130602’,
NULL        ,NULL,0)


,(’00AAAAA.30.0000′, 80196, ‘20130821’,
NULL        ,NULL,0)


,(’00AAAAA.30.0000′, 80196, ‘20130822’,
NULL        ,NULL,0)

 

            ,(’00BBBBB.19.0000′, 80196, ‘20130701’,
NULL        ,NULL,0)


,(’00BBBBB.19.0000′, 80196, ‘20130502’,
NULL        ,NULL,0)


,(’00BBBBB.19.0000′, 80196, ‘20130821’,
NULL        ,NULL,0)


,(’00BBBBB.19.0000′, 80196, ‘20130822’,
NULL        ,NULL,0)

 

Aguardo o script com a solução por e-mail. Fiquem a vontade para discutirmos. O Resultado da consulta deve ser o da imagem abaixo:

Thiago Carlos [TC] de Alencar

Advertisements

Removendo Cursor – Dica Rápida

Ola Galera, blz? Como o tempo está um pouco corrido essa vai ser uma dica rápida que pode ser usada no dia-a-dia.
Imaginem o cenário que é necessário criar uma string com diversos ID’s. E esses ID’s estão dentro de uma tabela. A idéia inicial seria realizar um loop para incrementar uma variavel e montar a nossa string.. Vamos ao exemplo:
O script abaixo cria uma tabela com os códigos do cliente que iremos usar para montar a nossa string.
use tempdb
go
if OBJECT_ID(‘dbo.tb_cliente’) is not null
drop table dbo.tb_cliente
go
create table dbo.tb_cliente(id int identity, codigoCliente int , nome varchar(50))
go
–Bloco que popula a tabela com informações dos clientes
set nocount on
declare @i int = 1
while @i <= 100
begin
insert into dbo.tb_cliente(codigoCliente,nome)values(@i,replicate(‘Thiago’,2))
set @i = @i + 1 *(2)
end
go

Declaramos as variaveis @count, @total para realizar o nosso loop. As variáveis @idCliente e @idClienteString serão usadas para armazenar os codigos concatenados. A tabela temporaria #CodigosCliente é usada para guardar as informações pertinentes ao predicado IN. O script abaixo preenche a tabela temporária com os registros que serão montados para o loop. Após a inserção, o total de registros e colocado em uma variável para que iniciemos a montagem da string.
declare @count int , @total int
declare @idCliente varchar(max), @idClienteString varchar(max)
create table #CodigosCliente(id int identity, codigoCliente int)
–Popula a temporaria com as informacoes que sera colocadas em uma string
insert into #CodigosCliente(codigoCliente)
select codigoCliente
from dbo.tb_cliente where codigoCliente between 10 and 50

set @idCliente = ‘ ‘
set @idClienteString = ‘ ‘
select  @total =count(id) from #CodigosCliente
set @count = 1
if @total > 0
begin
set @idClienteString = ‘codigoCliente IN ( ‘
end
else
begin
set @idClienteString = ‘codigoCliente IN (0 ‘
end
while @count <= @total
begin
if  @count > 1
begin
set @idClienteString = @idClienteString + ‘, ‘
end
select @idCliente=codigoCliente from #CodigosCliente where id=@count
set @idClienteString = @idClienteString + @idCliente
set @count = @count + 1
end
set @idClienteString = @idClienteString + ‘ )’
print @idClienteString
go
Após a execução do script acima, o SQL Server irá montar o predicado in com os valores, conforme imagem:

Conforme imagem acima, temos o resultado esperado, porém, operações linha-a-linha são mais custosas para o SQL Server. Nós poderíamos obter o mesmo resultado com o script abaixo:

declare @idCliente varchar(max )
declare @idClienteString varchar(max)
SET @idCliente =
SET @idClienteString =
SET @idClienteString = ‘codigoCliente IN (‘
SELECT @idCliente=@idCliente +‘,’+CONVERT(varchar(max),codigoCliente)from#CodigosCliente
SELECT @idCliente =substring(@idCliente,2,LEN(@idCliente))
SET @idClienteString = @idClienteString + @idCliente +‘)’
PRINT @idClienteString
Como podemos ver, temos o mesmo resultado com uma menor quantidade de linhas uma instrução única evitando o uso de um loop.

Créditos: Alexandre José Malachias….Valeu Boss

Constraints vs Query Performance

Neste post irei demonstrar a importancia de  criar constraints no SQL Server. Graças as Foreign keys e check constraints o optimizer pode criar planos mais eficientes para as querys. Dado o script abaixo da criação das tabelas temos a tabela de Customers e de Orders. Na modelagem proposta que dizer que um Customer pode ter uma ou mais Orders. Notem que o script de criacao da constraint fisica nessa tabela e feita na tabela “filha” no nosso caso a de Orders.
CREATE TABLEdbo.Customers (CustomerID INT  PRIMARYKEY)
CREATE TABLE dbo.Orders(OrderID INT  PRIMARYKEY,CustomerID INT NOT  NULL CONSTRAINT FKOrdersCustomers REFERENCES dbo.Customers(CustomerID)).
Executando a query abaixo, podemos notar que temos duas tabelas na consulta, mas se vc executar a query com o plano de execução o sql server uma apenas o operador fisico para acessar uma das tabelas. Esse compportamento acontece, pois, o otimizador sabe que não é necessario executar um teste de existencia dos registros, pois a FK garante que serao requeridas todas orders para referenciar com o customer. Conforme print abaixo:
SELECT O.OrderID,o.CustomerID
FROM dbo.Orders AS o
WHERE EXISTS(SELECT CustomerID FROM dbo.Customers AS c
WHERE c.CustomerID=o.CustomerID)

Agora o que aconteceria se desabilitarmos a constraint? Vamos aos testes (Amo muito tudo isso..rs)
ALTER TABLE dbo.Orders NOCHECK CONSTRAINT FKOrdersCustomers

Execute novamente  a query, pressione o CTRL+M antes para habilitar o Include Actual Execution Plan. Agora perceba que  o plano de execucao mudou, isso porque a constraint foi desabilitada e o SQL Server nao pode garantir que todas as Orders  tem um customer valido.
SELECT O.OrderID,o.CustomerID
FROM dbo.Orders AS o
WHERE EXISTS(SELECT CustomerID FROM dbo.Customers AS c WHERE c.CustomerID=o.CustomerID)

Para voltarmos ao plano anterior devemos habilitar novamente a constraint, conforme instrução abaixo:
ALTER TABLE dbo.Orders CHECK CONSTRAINT FKOrdersCustomers

Execute a query novamente e veja o plano de execucao. Note que o plano de execucao continua o mesmo de quando a constraint foi desabilitada.

Esse comportamento se da ao devido fato que: O SQL server nao pode garantir que nao foi inserido um registro nao valido enquanto a FK estava desabilitada. Vc pode verificar isso atraves da dmv sys.foreign_keys. A FK foi marcada como “nao confiavel“. Veja a query abaixo:
select name , is_not_trusted from sys.foreign_keys
Com o resultado da query acima  vc vera que a coluna is_not_trusted esta marcada com o valor 1  , indica que a FK nao e confiavel.
O que pode ser feito nesse caso e adicionar a opção WITH CHECK para a clausula alter table que habilita a constraint:

alter table dbo.Orders with check check constraint FKOrdersCustomers

Se a query for executada novamente vera que o plano de execucao sera o visto da primeira implementacao.

Se nesse meio tempo que a FK foi  desabilitada algum registro que nao condiz com a  implementacao da constraint fosse inserido . Uma mensagem de erro seria retornado para o SQL Server. Por exemplo se uma order fosse inserida com um CustomerID NULL.

Referencia: Microsoft Database Developer 2008 TK 70-433