Leitores amigos,
A pouco mais de um ano iniciei o E Agora DBA? com esse post. Um dos objetivos desse blog era um desafio pessoal - "Conseguir uma certificação oficial da Microsoft sem a necessidade de cursar os caros cursos oficiais".
Eu sempre tive muito receio em investir dois ou tres mil reais em formação e cair em uma turma onde o orientador apenas lê o material oficial e repete os exercicios em um data show. Ler eu ja sei e faço bem mais barato. Mas como sempre acontece, o corre corre nos faz deixar alguns planos de lado. Um bom tempo se passou e o assunto na verdade tinha ficado até esquecido.
Foi mais ou menos na mesma época que retomei esse blog que a idéia voltou ao meu radar.
Então eu arrisquei. Tirei o escorpião do bolso e paguei os US$ 80,00 e comecei uma revisão com esse livro aqui. Eram tres semanas para preencher as lacunas que faltavam para chegar na MCTS: Database Development.
Então, em uma segunda feira chuvosa estava eu na Avenida Paulista aguardando o horário do exame, tomando um suco de laranja e nervoso como um vestibulando. Subi ao centro de exames. E cai dentro do teste. E passei!!
A alegria de conseguir passar em um teste como esse não é puro exibicionismo. O profissional de TI é por natureza um entusiasta que gosta de desafios, a aprovação é colocar a prova tudo o que voce sabe sobre o assunto e converter isso em uma métrica. A meta são os 700 pontos. Passa-los é uma vitória.
Estou feliz meus amigos. Mas nao existe felicidade eterna. Portanto.. MCITPRO aqui vou eu!
Nossos primeiros erros
/*
O objetivo deste post é bem simples. Quando iniciamos nossa vida de DBA/Desenvolvedor nos deparamos com erros simples que nos roubam um tempo precioso na procura pela resposta. Acredito que esse tempo se perde pela mensagem de erro dificil ou pela tradução para o portugues que é terrivel no SQL. (eu prefiro usar tudo no inglês mesmo)
*/
-- Crie essa simples tabela.
CREATE TABLE PrimeirosErros (ErroID int identity(1,1) PRIMARY KEY,Erro varchar(5))
-- Primeiro erro
INSERT INTO PrimeirosErros (Erro) VALUES ('123456')
/*
-- Erro --------------
Msg 8152, Level 16, State 14, Line 1
String or binary data would be truncated.
OU
Msg 8152, Level 16, State 14, Line 1
Dados de cadeia ou binários seriam truncados.
----------------------
Explicação: Voce está inserindo um valor que estoura o tamanho maximo da coluna (Valor com 6 caracteres, campo definido para 5 caracteres)
Basta reduzir o tamanho que voce está inserindo ou aumentar a capacidade da coluna que voce está Parece besta, mas ja vi muito cara bom ficar um pouco perdido com essa mensagem esquisita.
*/
-- Segundo erro:
INSERT INTO PrimeirosErros VALUES (1,'Teste' )
/*
-- Erro --------------
Msg 8101, Level 16, State 1, Line 1
Um valor explícito para a coluna de identidade na tabela 'PrimeirosErros' só pode ser especificado quando uma lista de colunas é usada e o valor de IDENTITY_INSERT é ON.
OU
Msg 8101, Level 16, State 1, Line 1
An explicit value for the identity column in table 'PrimeirosErros' can only be specified when a column list is used and IDENTITY_INSERT is ON.
*/
/*
Explicação: A coluna é identity (no popular auto-numérica). Como voce está forçando um valor para a coluna identity, voce deve ligar o INDENTY_INSERT da tabela para permitir a inclusão
*/
SET IDENTITY_INSERT PrimeirosErros ON
INSERT INTO PrimeirosErros VALUES (1,'Teste' )
/*
Ué...o erro continuou! E agora DBA?
A resposta continua simples, além de habilitar a inserção de valores explicitos na coluna voce deve especificar o insert da maneira mais explicita possivel, isto é,
incluindo a lista de colunas que receberão os dados
*/
INSERT INTO PrimeirosErros (ErroID,Erro) VALUES (1,'Teste' )
/*
Mas, por favor, não pare por ai. Nunca se esqueça de desligar o identity_insert. Senão ao executar o de baixo....
*/
INSERT INTO PrimeirosErros (Erro) VALUES ('Teste2' )
/*
-- Erro --------------
Msg 545, Level 16, State 1, Line 1
Explicit value must be specified for identity column in table 'PrimeirosErros' either when IDENTITY_INSERT is set to ON or when a replication user is inserting into a NOT FOR REPLICATION identity column.
OU
Msg 545, Level 16, State 1, Line 1
Deve ser especificado um valor explícito para a coluna de identidade na tabela 'PrimeirosErros' quando IDENTITY_INSERT está definido como ON ou quando um usuário de replicação insere em uma coluna de identidade NOT FOR REPLICATION.
----------------------
Voce deve desligar o IDENTITY_INSERT da tabela para voltar tudo ao normal.
*/
SET IDENTITY_INSERT PrimeirosErros OFF
/*
Espero que essa pequena explicação tenha poupado seu tempo, os erros fazem parte de nossas vidas. Se eles não existissem, talvez metade de nós estivesse desempregada :D
Abraços!
Felipe Antunes
AVISO:ESSE POST É UM SCRIPT! SE VOCE DESEJAR, VOCE PODE COPIA-LO E COLA-LO DIRETAMENTE NO SEU MANAGEMENT STUDIO
*/CTE Recursiva - Uma consulta elegante
Pessoal, muito obrigado pelo retorno do post anterior, no total foram duas centenas de pageviews,cinco emails, três comentarios, um telefonema e um visitante croata(sim é verdade!).
Motivado por esse retorno sigo com a continuação:
CTE com Recursividade!!!
Veja o caso “CTE no Busão”:
Imagine uma determinada linha de onibus que realiza 10 paradas entre seu trajeto de Guarulhos a Campinas. O onibus possui um sistema que registra a data/hora de cada parada e qual foi a distancia percorrida entre uma parada e outra. Scrip Exemplo
DataParada datetime, -- Data/hora da parada
Distancia int, -- distancia percorrida da ultima parada até agora
ParadaAnteriorID int -- id da parada anterior
)
-- Populando
INSERT INTO Paradas (DataParada,Distancia)
VALUES (GETDATE(),0)
GO
INSERT INTO Paradas (DataParada,Distancia,ParadaAnteriorID)
SELECT TOP 1
DATEADD(MINUTE ,(RAND() * 100)+ 5,DataParada) ,
CAST(RAND() * 33 as Int )+ 5 ,
@@IDENTITY
FROM
Paradas
ORDER BY DataParada DESC
GO 10
1 2011-06-07 17:23:59.543 0 NULL
2 2011-06-07 18:10:59.543 36 1
3 2011-06-07 19:31:59.543 30 2
4 2011-06-07 20:17:59.543 5 3
5 2011-06-07 21:37:59.543 27 4
6 2011-06-07 23:04:59.543 21 5
7 2011-06-07 23:19:59.543 37 6
8 2011-06-08 00:31:59.543 21 7
9 2011-06-08 01:13:59.543 6 8
10 2011-06-08 02:18:59.543 23 9
11 2011-06-08 02:37:59.543 28 10
SELECT
DataParada,
Distancia,
ParadaID
FROM
Paradas
WHERE
ParadaAnteriorID IS NULL
Na sequência via UNION ALL montamos uma query semelhante fazendo um Join com a própria CTE , veja abaixo
-- Ancora
SELECT
DataParada,
Distancia,
ParadaID
FROM
Paradas
WHERE
ParadaAnteriorID IS NULL
UNION ALL
-- Recursão
SELECT
P.DataParada,
P.Distancia + C.KMRodados,
/* Pulo do Gato! Acima é que se soma a distancia atual com a distancia da anterior */
P.ParadaID
FROM
Paradas P
INNER JOIN CTE_Viagem C -- NOTE AQUI A CTE!!!
ON P.ParadaAnteriorID = C.ParadaID
)
SELECT
DataParada,KMRodados,ParadaID
FROM
CTE_Viagem
----------------------- ----------- -----------
2011-06-07 17:23:59.543 0 1
2011-06-07 18:10:59.543 36 2
2011-06-07 19:31:59.543 66 3
2011-06-07 20:17:59.543 71 4
2011-06-07 21:37:59.543 98 5
2011-06-07 23:04:59.543 119 6
2011-06-07 23:19:59.543 156 7
2011-06-08 00:31:59.543 177 8
2011-06-08 01:13:59.543 183 9
2011-06-08 02:18:59.543 206 10
2011-06-08 02:37:59.543 234 11
Não contente (e preguiçoso em fazer contas) o mesmo gerente te pede para colocar no relatório a quantidade em minutos que a viagem demorou até aquele momento. E voce simplesmente adiciona o seguinte:
(
-- Ancora
SELECT
DataParada,
Distancia,
0 AS TempoViagem,
ParadaID
FROM
Paradas
WHERE
ParadaAnteriorID IS NULL
UNION ALL
-- Recursão
SELECT
P.DataParada,
P.Distancia + C.KMRodados,
C.TempoViagem + DATEDIFF(minute,C.DataParada,P.DataParada),--Pulo do Gato!
P.ParadaID
FROM
Paradas P
INNER JOIN CTE_Viagem C ON P.ParadaAnteriorID = C.ParadaID
)
SELECT
DataParada,KMRodados,TempoViagem,ParadaID
FROM
CTE_Viagem
E resultado:
2011-06-07 17:23:59.543 0 0 1
2011-06-07 18:10:59.543 36 47 2
2011-06-07 19:31:59.543 66 128 3
2011-06-07 20:17:59.543 71 174 4
2011-06-07 21:37:59.543 98 254 5
2011-06-07 23:04:59.543 119 341 6
2011-06-07 23:19:59.543 156 356 7
2011-06-08 00:31:59.543 177 428 8
2011-06-08 01:13:59.543 183 470 9
2011-06-08 02:18:59.543 206 535 10
2011-06-08 02:37:59.543 234 554 11
Motivado por esse retorno sigo com a continuação:
CTE com Recursividade!!!
Veja o caso “CTE no Busão”:
Imagine uma determinada linha de onibus que realiza 10 paradas entre seu trajeto de Guarulhos a Campinas. O onibus possui um sistema que registra a data/hora de cada parada e qual foi a distancia percorrida entre uma parada e outra. Scrip Exemplo
-- Estrutura
CREATE TABLE Paradas (
ParadaID int identity (1,1) PRIMARY KEY, -- PKDataParada datetime, -- Data/hora da parada
Distancia int, -- distancia percorrida da ultima parada até agora
ParadaAnteriorID int -- id da parada anterior
)
-- Populando
INSERT INTO Paradas (DataParada,Distancia)
VALUES (GETDATE(),0)
GO
INSERT INTO Paradas (DataParada,Distancia,ParadaAnteriorID)
SELECT TOP 1
DATEADD(MINUTE ,(RAND() * 100)+ 5,DataParada) ,
CAST(RAND() * 33 as Int )+ 5 ,
@@IDENTITY
FROM
Paradas
ORDER BY DataParada DESC
GO 10
Dando um SELECT simples temos o seguinte resultado
ParadaID DataParada Distancia ParadaAnteriorID
----------- ----------------------- ----------- ----------------1 2011-06-07 17:23:59.543 0 NULL
2 2011-06-07 18:10:59.543 36 1
3 2011-06-07 19:31:59.543 30 2
4 2011-06-07 20:17:59.543 5 3
5 2011-06-07 21:37:59.543 27 4
6 2011-06-07 23:04:59.543 21 5
7 2011-06-07 23:19:59.543 37 6
8 2011-06-08 00:31:59.543 21 7
9 2011-06-08 01:13:59.543 6 8
10 2011-06-08 02:18:59.543 23 9
11 2011-06-08 02:37:59.543 28 10
--------------------------------------------------------------------
Eis então que chega o Gerente da companhia de Viação e te pede um relatório que exiba cada parada e quantos KMs foram percorridos do inicio da viagem até aquele momento. Ai que entra o CTE. Uma CTE recursiva começa com a chamada Ancora. O registro Ancora é o fio por onde voce puxa a recursão. No nosso caso começamos com o registro inicial da viagem.
-- AncoraSELECT
DataParada,
Distancia,
ParadaID
FROM
Paradas
WHERE
ParadaAnteriorID IS NULL
Na sequência via UNION ALL montamos uma query semelhante fazendo um Join com a própria CTE , veja abaixo
WITH CTE_Viagem (DataParada,KMRodados,ParadaID)
AS (-- Ancora
SELECT
DataParada,
Distancia,
ParadaID
FROM
Paradas
WHERE
ParadaAnteriorID IS NULL
UNION ALL
-- Recursão
SELECT
P.DataParada,
P.Distancia + C.KMRodados,
/* Pulo do Gato! Acima é que se soma a distancia atual com a distancia da anterior */
P.ParadaID
FROM
Paradas P
INNER JOIN CTE_Viagem C -- NOTE AQUI A CTE!!!
ON P.ParadaAnteriorID = C.ParadaID
)
SELECT
DataParada,KMRodados,ParadaID
FROM
CTE_Viagem
E olha que maravilha de resultado!!! E como é elegante.
DataParada KMRodados ParadaID----------------------- ----------- -----------
2011-06-07 17:23:59.543 0 1
2011-06-07 18:10:59.543 36 2
2011-06-07 19:31:59.543 66 3
2011-06-07 20:17:59.543 71 4
2011-06-07 21:37:59.543 98 5
2011-06-07 23:04:59.543 119 6
2011-06-07 23:19:59.543 156 7
2011-06-08 00:31:59.543 177 8
2011-06-08 01:13:59.543 183 9
2011-06-08 02:18:59.543 206 10
2011-06-08 02:37:59.543 234 11
Não contente (e preguiçoso em fazer contas) o mesmo gerente te pede para colocar no relatório a quantidade em minutos que a viagem demorou até aquele momento. E voce simplesmente adiciona o seguinte:
WITH CTE_Viagem (DataParada,KMRodados,TempoViagem,ParadaID)
AS(
-- Ancora
SELECT
DataParada,
Distancia,
0 AS TempoViagem,
ParadaID
FROM
Paradas
WHERE
ParadaAnteriorID IS NULL
UNION ALL
-- Recursão
SELECT
P.DataParada,
P.Distancia + C.KMRodados,
C.TempoViagem + DATEDIFF(minute,C.DataParada,P.DataParada),--Pulo do Gato!
P.ParadaID
FROM
Paradas P
INNER JOIN CTE_Viagem C ON P.ParadaAnteriorID = C.ParadaID
)
SELECT
DataParada,KMRodados,TempoViagem,ParadaID
FROM
CTE_Viagem
E resultado:
DataParada KMRodados TempoViagem ParadaID
----------------------- ----------- ----------- -----------2011-06-07 17:23:59.543 0 0 1
2011-06-07 18:10:59.543 36 47 2
2011-06-07 19:31:59.543 66 128 3
2011-06-07 20:17:59.543 71 174 4
2011-06-07 21:37:59.543 98 254 5
2011-06-07 23:04:59.543 119 341 6
2011-06-07 23:19:59.543 156 356 7
2011-06-08 00:31:59.543 177 428 8
2011-06-08 01:13:59.543 183 470 9
2011-06-08 02:18:59.543 206 535 10
2011-06-08 02:37:59.543 234 554 11
Demais!
Explicações técnicas, detalhadas e sem emoção a seguir no nosso amigo books online
http://msdn.microsoft.com/en-us/library/ms186243.aspxAbraços!!!
Marcadores:
common table expression,
CTE,
recursive,
SQL 2008,
T-SQL
Voce já usou uma CTE?
CTE é um recurso adicionado no SQL 2005 e pouquissimo utilizado por nossas bandas. Elas são parecidas com subqueries,você cria e nomeia um SELECT qualquer e ela passa a se comportar como uma tabela dentro do seu batch.
Diferentemente da subquery, a CTE pode se autoreferenciar e ser reaproveitado diversas vezes em um mesmo comando. Além de fornecer recursividade de um jeito bem bacana
Imagine que temos uma tabela padrão de clientes e uma tabela que armazena todas as compras que o cliente armazenou. Segue os scripts
ClienteID int identity(1,1) PRIMARY KEY,
Cliente varchar(100) NOT NULL
)
GO
CREATE TABLE Compras
(
CompraID int identity(1,1) PRIMARY KEY,
ClienteID int NOT NULL FOREIGN KEY REFERENCES Cliente(ClienteID),
DataCompra datetime
)
Cliente.ClienteID,
CAST(dateadd(MINUTE,(RAND(clienteid) * 100000),creation_time) as DATE)
FROM sys.dm_os_threads
CROSS JOIN Cliente
GROUP BY
CAST(dateadd(MINUTE,(RAND(clienteid) * 100000),creation_time) as DATE),Cliente.ClienteID
------------------------------------------------------------ Imagine a necessidade de um campo DataUltimaCompra na tabela Cliente. Um UPDATE entre as tabelas mais um Group By não é possivel. Com uma CTE a coisa fica mais simples. Veja abaixo:
AS
( SELECT
ClienteID,
MAX(DataCompra) UltimaCompra
FROM Compras
GROUP BY
SET C.DataUltimaCompra = CTE.UltimaCompra
FROM Cliente C
INNER JOIN CTE_UltimaCompra CTE ON C.ClienteID = CTE.ClienteID
FOR Insert,Update
AS
WITH CTE_UltimaCompra (ClienteID,UltimaCompra)
AS
( SELECT
C.ClienteID,
MAX(C.DataCompra) UltimaCompra
FROM Compras C
inner join inserted I ON C.ClienteID = I.ClienteID
-- Atenção ao pulo do gato acima
GROUP BY
C.ClienteID
)
UPDATE C SET C.DataUltimaCompra = CTE.UltimaCompra
FROM Cliente C
INNER JOIN CTE_UltimaCompra CTE ON C.ClienteID = CTE.ClienteID
No próximo post coloco um exemplo de recursividade com CTE.
Abaixo o link do nosso querido Books Online com uma descrição completa
Abraços!
Diferentemente da subquery, a CTE pode se autoreferenciar e ser reaproveitado diversas vezes em um mesmo comando. Além de fornecer recursividade de um jeito bem bacana
Em um exemplo pratico.
Imagine que temos uma tabela padrão de clientes e uma tabela que armazena todas as compras que o cliente armazenou. Segue os scripts
-- Criando as tabelas
CREATE TABLE Cliente
(ClienteID int identity(1,1) PRIMARY KEY,
Cliente varchar(100) NOT NULL
)
GO
CREATE TABLE Compras
(
CompraID int identity(1,1) PRIMARY KEY,
ClienteID int NOT NULL FOREIGN KEY REFERENCES Cliente(ClienteID),
DataCompra datetime
)
-- Populando as tabelas (opcional, voce pode inventar seu proprio metodo de população)
INSERT INTO Cliente (Cliente)
SELECT UPPER(name) FROM master.sys.objects INSERT INTO Compras (ClienteID,DataCompra)
SELECT Cliente.ClienteID,
CAST(dateadd(MINUTE,(RAND(clienteid) * 100000),creation_time) as DATE)
FROM sys.dm_os_threads
CROSS JOIN Cliente
GROUP BY
CAST(dateadd(MINUTE,(RAND(clienteid) * 100000),creation_time) as DATE),Cliente.ClienteID
-- Adiciona o campo
ALTER TABLE Cliente ADD DataUltimaCompra datetime -- Declaração da CTE
;WITH CTE_UltimaCompra (ClienteID,UltimaCompra)AS
( SELECT
ClienteID,
MAX(DataCompra) UltimaCompra
FROM Compras
GROUP BY
ClienteID
) -- Comando utilizando a CTE como se fosse uma tabela
UPDATE C SET C.DataUltimaCompra = CTE.UltimaCompra
FROM Cliente C
INNER JOIN CTE_UltimaCompra CTE ON C.ClienteID = CTE.ClienteID
-- Com uma pequena modificação voce pode até colocar esse script em uma trigger
-- e manter esse campo atualizado na inserção de uma nova compraCREATE TRIGGER tr_AtualizaUltimaCompra
ON ComprasFOR Insert,Update
AS
WITH CTE_UltimaCompra (ClienteID,UltimaCompra)
AS
( SELECT
C.ClienteID,
MAX(C.DataCompra) UltimaCompra
FROM Compras C
inner join inserted I ON C.ClienteID = I.ClienteID
-- Atenção ao pulo do gato acima
GROUP BY
C.ClienteID
)
FROM Cliente C
INNER JOIN CTE_UltimaCompra CTE ON C.ClienteID = CTE.ClienteID
No próximo post coloco um exemplo de recursividade com CTE.
Abaixo o link do nosso querido Books Online com uma descrição completa
Abraços!
Marcadores:
common table expression,
CTE,
SQL SERVER 2008,
T-SQL
Assinar:
Postagens (Atom)