Transformando colunas em linhas sem UNPIVOT

Olá pessoas,

Após conhecer um pouco do UNPIVOT, percebe-se que este comando não é muito flexível, mesmo sendo um pouco mais maleável que o comando PIVOT. Por desconhecer este comando, ou pela falta de flexibilidade, é comum encontrar outras alternativas para ‘transformar colunas em linhas’, como o exemplo abaixo:

-- Criando uma tabela de teste:

DECLARE @CONTAS TABLE (
    [BANCO] VARCHAR(100),
    [ANO] SMALLINT,
    [INVESTIMENTOS] MONEY,
    [DESPESAS] MONEY
)

INSERT INTO @CONTAS VALUES
('BANCO ALVORADA S/A', 2010, 9613906084.01, 8102644.84),
('BANCO ALVORADA S/A', 2011, 174343.35, 7935411.15),
('BANCO ARBI S/A', 2010, 8202652.29, 114215.13),
('BANCO ARBI S/A', 2011, 8407843.72, 81746.25)

-- Uso do UNION ALL para transformar colunas em linhas:

SELECT [BANCO], [ANO], 'INVESTIMENTOS' AS [TIPO], C.[INVESTIMENTOS] AS [VALOR] FROM @CONTAS C
UNION ALL
SELECT [BANCO], [ANO], 'DESPESAS' AS [TIPO], C.[DESPESAS] AS [VALOR] FROM @CONTAS C
ORDER BY [BANCO], [ANO], [TIPO]

Esta estratégia com UNION ALL requer uma leitura da tabela a mais para cada coluna transformada em linha, ou seja, a tabela no exemplo acima foi lida duas vezes para obter o mesmo resultado que poderia ser obtido lendo a tabela uma única vez com UNPIVOT:

SELECT [BANCO], [ANO], [TIPO], [VALOR]
FROM @CONTAS C
UNPIVOT (
    [VALOR] FOR [TIPO] IN (
        [INVESTIMENTOS],
        [DESPESAS]
    )
) AS U
ORDER BY [BANCO], [ANO], [TIPO]

Com o STATISTICS IO, podemos obter as seguintes informações:

Consulta com UNION ALL:

(8 row(s) affected)
Table ‘#A255182D’. Scan count 2, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Consulta com UNPIVOT:

(8 row(s) affected)
Table ‘#A255182D’. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Mas como obter flexibilidade, tendo um número de leituras semelhante ao UNPIVOT, e até mesmo ter um plano de execução um pouco melhor?

A resposta pode ser encontrada no CROSS APPLY – que para quem não conhece, este comando funciona de forma um pouco semelhante ao JOIN, executando uma determinada subquery para cada registro da tabela com a qual ele faz o ‘JOIN’ –, que pode ser utilizado com UNION ALL para transformar colunas em linhas:

SELECT [BANCO], [ANO], [TIPO], [VALOR]
FROM @CONTAS C

CROSS APPLY (
    SELECT 'INVESTIMENTOS' AS [TIPO], C.[INVESTIMENTOS] AS [VALOR]
    UNION ALL
    SELECT 'DESPESAS' AS [TIPO], C.[DESPESAS] AS [VALOR]
)
AS U

ORDER BY [BANCO], [ANO], [TIPO]

Ou combinado ao VALUES:

SELECT [BANCO], [ANO], [TIPO], [VALOR]
FROM @CONTAS C

CROSS APPLY (
    VALUES
    ('INVESTIMENTOS', C.[INVESTIMENTOS]),
    ('DESPESAS', C.[DESPESAS])
)
AS U ([TIPO], [VALOR])

ORDER BY [BANCO], [ANO], [TIPO]

Em breve, vou publicar um comparativo mais detalhado entre o UNPIVOT e o CROSS APPLY!

Anúncios

Transformando colunas em linhas com UNPIVOT

Olá pessoas,

O UNPIVOT é um pouco menos conhecido que o PIVOT, por razões óbvias, dentre elas, “é fácil transformar colunas em linhas”. Mas isso não limita o fato de quem já está acostumado com PIVOT, também utilize o UNPIVOT como recurso, principalmente por apresentarem sintaxes bem semelhantes.

UNPIVOT não utiliza funções de agregação como o PIVOT, mas é necessária capacidade abstrair duas colunas, sendo a primeira, a coluna que apresentará o nome da coluna que virou linha, e a segunda, a coluna que apresentará o valor da coluna que virou linha.

Para o primeiro exemplo, temos uma tabela com as colunas INVESTIMENTOS e DESPESAS, e transformo ‘investimentos’ e ‘despesas’ em valores da coluna TIPO, e os valores destas colunas coloco na coluna VALOR:

DECLARE @CONTAS TABLE (
	[BANCO] VARCHAR(100), 
	[ANO] SMALLINT,
	[INVESTIMENTOS] MONEY, 
	[DESPESAS] MONEY
)

INSERT INTO @CONTAS VALUES
('BANCO ALVORADA S/A', 2010, 9613906084.01, 8102644.84),
('BANCO ALVORADA S/A', 2011, 174343.35, 7935411.15),
('BANCO ARBI S/A', 2010, 8202652.29, 114215.13),
('BANCO ARBI S/A', 2011, 8407843.72, 81746.25)

SELECT [BANCO], [ANO], [TIPO], [VALOR]
FROM @CONTAS C
UNPIVOT (
	[VALOR] FOR [TIPO] IN (	
		[INVESTIMENTOS], 
		[DESPESAS]
	)
) AS U
ORDER BY [BANCO], [ANO], [TIPO]

Transformando colunas em linhas com UNPIVOT

Para o segundo exemplo, temos uma tabela com as colunas 2010 e 2011, e transformo ‘2010’ e ‘2011’ em valores da coluna ANO, e os valores destas colunas coloco na coluna VALOR:

DECLARE @CONTAS TABLE (
	[BANCO] VARCHAR(100), 
	[TIPO] VARCHAR(100),
	[2010] MONEY, 
	[2011] MONEY
)

INSERT INTO @CONTAS VALUES
('BANCO ALVORADA S/A', 'INVESTIMENTOS', 9613906084.01, 174343.35),
('BANCO ALVORADA S/A', 'DESPESAS', 8102644.84, 7935411.15),
('BANCO ARBI S/A', 'INVESTIMENTOS', 8202652.29, 8407843.72),
('BANCO ARBI S/A', 'DESPESAS', 114215.13, 81746.25)

SELECT [BANCO], [ANO], [TIPO], [VALOR]
FROM @CONTAS C
UNPIVOT (
	[VALOR] FOR [ANO] IN (	
		[2010], 
		[2011]
	)
) AS U
ORDER BY [BANCO], [ANO], [TIPO]

Transformando colunas em linhas com UNPIVOT

Para este terceiro exemplo, temos colunas juntos os nomes são formados tanto pelo tipo da conta e o ano, o que requer algumas transformações nos valores da coluna CONTA, para identificarmos o ano e o tipo da conta.

DECLARE @CONTAS TABLE (
	[BANCO] VARCHAR(100), 
	[INVESTIMENTOS_2010] MONEY, 
	[INVESTIMENTOS_2011] MONEY, 
	[DESPESAS_2010] MONEY, 
	[DESPESAS_2011] MONEY
)

INSERT INTO @CONTAS VALUES
('BANCO ALVORADA S/A', 9613906084.01, 8102644.84, 
174343.35, 7935411.15),
('BANCO ARBI S/A',8202652.29, 114215.13, 
8407843.72, 81746.25)

SELECT [BANCO], 
[ANO] = RIGHT([CONTA], CHARINDEX('_', REVERSE([CONTA])) - 1), 
[TIPO] = LEFT([CONTA], CHARINDEX('_', [CONTA]) - 1), 
[VALOR]
FROM @CONTAS C
UNPIVOT (
	[VALOR] FOR [CONTA] IN (	
		[INVESTIMENTOS_2010], 
		[INVESTIMENTOS_2011], 
		[DESPESAS_2010], 
		[DESPESAS_2011]
	)
) AS U
ORDER BY [BANCO], [ANO], [TIPO]

Transformando colunas em linhas com UNPIVOT

Agora, neste quarto exemplo, temos a transformação de diversas colunas em registros, o que requer unicamente que estas colunas sejam do mesmo tipo, no caso VARCHAR(100), para não dar o erro:

The type of column “xxxxx” conflicts with the type of other columns specified in the UNPIVOT list.

DECLARE @CONTAS TABLE (
	[BANCO] VARCHAR(100), 
	[ANO] SMALLINT,
	[INVESTIMENTOS] MONEY, 
	[DESPESAS] MONEY
)

INSERT INTO @CONTAS VALUES
('BANCO ALVORADA S/A', 2010, 9613906084.01, 8102644.84),
('BANCO ALVORADA S/A', 2011, 174343.35, 7935411.15),
('BANCO ARBI S/A', 2010, 8202652.29, 114215.13),
('BANCO ARBI S/A', 2011, 8407843.72, 81746.25)

SELECT [COLUNA], [VALOR]
FROM (
	SELECT 
		[BANCO],
		[ANO] = CAST([ANO] AS VARCHAR(100)),
		[INVESTIMENTOS] = CAST([INVESTIMENTOS] AS VARCHAR(100)),
		[DESPESAS] = CAST([DESPESAS] AS VARCHAR(100))
	FROM @CONTAS
) C
UNPIVOT (
	[VALOR] FOR [COLUNA] IN (	
		[INVESTIMENTOS], 
		[DESPESAS], 
		[BANCO],
		[ANO]
	)
) AS U

Transformando colunas em linhas com UNPIVOT

Transformando linhas em colunas sem PIVOT

Olá pessoas,

Na semana passada, nós vimos como transformar linhas em colunas no SQL Server com PIVOT. Mas como nem sempre dá para contar com este recurso, e algumas vezes, ele não supre 100% das nossas necessidades, vou apresentar alguns exemplos de como fazer PIVOT sem utilizar PIVOT.

Para estes exemplos, estarei utilizando da mesma massa de dados da semana anterior:

DECLARE @CONTAS TABLE (
	ANO SMALLINT, 
	BANCO VARCHAR(100), 
	TIPO VARCHAR(100), 
	VALOR MONEY
)

INSERT INTO @CONTAS VALUES
(2009,'BANCO ALVORADA S/A','INVESTIMENTOS',6175979775.42),
(2010,'BANCO ALVORADA S/A','INVESTIMENTOS',6486892688.53),
(2011,'BANCO ALVORADA S/A','INVESTIMENTOS',7905663406.86),
(2012,'BANCO ALVORADA S/A','INVESTIMENTOS',9613906084.01),
(2009,'BANCO ARBI S/A','INVESTIMENTOS',8102644.84),
(2009,'BANCO ARBI S/A','OUTROS',174343.35),
(2010,'BANCO ARBI S/A','INVESTIMENTOS',7935411.15),
(2010,'BANCO ARBI S/A','OUTROS',119885.82),
(2011,'BANCO ARBI S/A','INVESTIMENTOS',8202652.29),
(2011,'BANCO ARBI S/A','OUTROS',114215.13),
(2012,'BANCO ARBI S/A','INVESTIMENTOS',8407843.72),
(2012,'BANCO ARBI S/A','OUTROS',81746.25)

Conforme o primeiro exemplo da semana anterior, transformamos os registros de contas do tipo INVESTIMENTOS e OUTROS em duas colunas, utilizando PIVOT:

SELECT U.ANO, U.BANCO, U.INVESTIMENTOS, U.OUTROS
FROM @CONTAS AS C
PIVOT (
	SUM(C.VALOR) FOR
	C.TIPO IN (INVESTIMENTOS, OUTROS)  
) AS U

Sem PIVOT, é possível fazer o mesmo procedimento com CASE WHEN:

SELECT C.ANO, C.BANCO, 
	INVESTIMENTOS = SUM(CASE WHEN C.TIPO = 'INVESTIMENTOS' THEN C.VALOR END),
	OUTROS = SUM(CASE WHEN C.TIPO = 'OUTROS' THEN C.VALOR END)
FROM @CONTAS AS C
GROUP BY C.ANO, C.BANCO

E no SQL Server 2012+, com IIF:

SELECT C.ANO, C.BANCO, 
	INVESTIMENTOS = SUM(IIF(C.TIPO = 'INVESTIMENTOS', C.VALOR, NULL)),
	OUTROS = SUM(IIF(C.TIPO = 'OUTROS', C.VALOR, NULL))
FROM @CONTAS AS C
GROUP BY C.ANO, C.BANCO

Também é possível fazer com subqueries, mas como não é performático, então ignoraremos esta alternativa.

O segundo exemplo, transformamos os registros de anos em colunas respectivas a estes anos, utilizando PIVOT:

SELECT U.BANCO, U.TIPO, U.[2009], U.[2010], U.[2011], U.[2012]
FROM @CONTAS AS C
PIVOT (
	SUM(C.VALOR) FOR
	C.ANO IN ([2009], [2010], [2011], [2012])  
) AS U

Sem PIVOT, é possível fazer o mesmo procedimento com CASE WHEN:

SELECT C.BANCO, C.TIPO,
	[2009] = SUM(CASE WHEN C.ANO = 2009 THEN C.VALOR END),
	[2010] = SUM(CASE WHEN C.ANO = 2010 THEN C.VALOR END),
	[2011] = SUM(CASE WHEN C.ANO = 2011 THEN C.VALOR END),
	[2012] = SUM(CASE WHEN C.ANO = 2012 THEN C.VALOR END)
FROM @CONTAS AS C
GROUP BY C.BANCO, C.TIPO

E no SQL Server 2012+, com IIF:

SELECT C.BANCO, C.TIPO,
	[2009] = SUM(IIF(C.ANO = 2009, C.VALOR, NULL)),
	[2010] = SUM(IIF(C.ANO = 2010, C.VALOR, NULL)),
	[2011] = SUM(IIF(C.ANO = 2011, C.VALOR, NULL)),
	[2012] = SUM(IIF(C.ANO = 2012, C.VALOR, NULL))
FROM @CONTAS AS C
GROUP BY C.BANCO, C.TIPO

E agora, algumas questões que são mais fáceis de resolver com CASE WHEN e IIF, como criar regras mais específicas para as colunas utilizando outras funções e outras regras de agrupamento:

SELECT U.BANCO, U.TIPO,
	U.[2008], U.[2009], U.[2010], U.[2011], U.[2012], 
	[2009_2010] = U.[2009] + U.[2010],
	[2011_2012] = U.[2011] + U.[2012], 
	TOTAL = U.[2009] + U.[2010] + U.[2011] + U.[2012]
FROM @CONTAS AS C
PIVOT (
	SUM(C.VALOR) FOR
	C.ANO IN ([2008], [2009], [2010], [2011], [2012])  
) AS U

SELECT C.BANCO, C.TIPO,
	[2008] = SUM(CASE WHEN C.ANO = 2008 THEN C.VALOR ELSE 0 END),
	[2009] = SUM(CASE WHEN C.ANO = 2009 THEN C.VALOR ELSE 0 END),
	[2010] = SUM(CASE WHEN C.ANO = 2010 THEN C.VALOR ELSE 0 END),
	[2011] = SUM(CASE WHEN C.ANO = 2011 THEN C.VALOR ELSE 0 END),
	[2012] = SUM(CASE WHEN C.ANO = 2012 THEN C.VALOR ELSE 0 END),
	[!2012] = SUM(CASE WHEN C.ANO <> 2012 THEN C.VALOR ELSE 0 END),
	[<2012] = SUM(CASE WHEN C.ANO < 2012 THEN C.VALOR ELSE 0 END),
	[2009_2010] = SUM(CASE WHEN C.ANO IN (2009, 2010) THEN C.VALOR ELSE 0 END),
	[2011_2012] = SUM(CASE WHEN C.ANO BETWEEN 2011 AND 2012 THEN C.VALOR ELSE 0 END),
	TOTAL = SUM(C.VALOR),
	MEDIA = AVG(C.VALOR)
FROM @CONTAS AS C
GROUP BY C.BANCO, C.TIPO

SELECT C.BANCO, C.TIPO,
	[2008] = SUM(IIF(C.ANO = 2008, C.VALOR, 0)),
	[2009] = SUM(IIF(C.ANO = 2009, C.VALOR, 0)),
	[2010] = SUM(IIF(C.ANO = 2010, C.VALOR, 0)),
	[2011] = SUM(IIF(C.ANO = 2011, C.VALOR, 0)),
	[2012] = SUM(IIF(C.ANO = 2012, C.VALOR, 0)),
	[!2012] = SUM(IIF(C.ANO <> 2012, C.VALOR, 0)),
	[<2012] = SUM(IIF(C.ANO < 2012, C.VALOR, 0)),
	[2009_2010] = SUM(IIF(C.ANO IN (2009, 2010), C.VALOR, 0)),
	[2011_2012] = SUM(IIF(C.ANO BETWEEN 2011 AND 2012, C.VALOR, 0)),
	TOTAL = SUM(C.VALOR),
	MEDIA = AVG(C.VALOR)
FROM @CONTAS AS C
GROUP BY C.BANCO, C.TIPO

E a composição de um agrupamento de linhas para colunas com considerando mais de uma coluna:

SELECT BANCO = COALESCE(U_I.BANCO, U_O.BANCO), 
	INV_2009 = U_I.[2009], 
	INV_2010 = U_I.[2010], 
	INV_2011 = U_I.[2011], 
	INV_2012 = U_I.[2012], 
	OUT_2009 = U_O.[2009], 
	OUT_2010 = U_O.[2010], 
	OUT_2011 = U_O.[2011], 
	OUT_2012 = U_O.[2012]
FROM 
(SELECT BANCO, ANO, VALOR FROM @CONTAS WHERE TIPO = 'INVESTIMENTOS') AS C_I
PIVOT (
	SUM(C_I.VALOR) FOR
	C_I.ANO IN ([2009], [2010], [2011], [2012])  
) AS U_I
FULL OUTER JOIN
(SELECT BANCO, ANO, VALOR FROM @CONTAS WHERE TIPO = 'OUTROS') AS C_O
PIVOT (
	SUM(C_O.VALOR) FOR
	C_O.ANO IN ([2009], [2010], [2011], [2012])  
) AS U_O
ON U_I.BANCO = U_O.BANCO
SELECT C.BANCO,
	INV_2009 = SUM(CASE WHEN C.ANO = 2009 AND TIPO = 'INVESTIMENTOS' THEN C.VALOR ELSE 0 END),
	INV_2010 = SUM(CASE WHEN C.ANO = 2010 AND TIPO = 'INVESTIMENTOS' THEN C.VALOR ELSE 0 END),
	INV_2011 = SUM(CASE WHEN C.ANO = 2011 AND TIPO = 'INVESTIMENTOS' THEN C.VALOR ELSE 0 END),
	INV_2012 = SUM(CASE WHEN C.ANO = 2012 AND TIPO = 'INVESTIMENTOS' THEN C.VALOR ELSE 0 END), 
	OUT_2009 = SUM(CASE WHEN C.ANO = 2009 AND TIPO = 'OUTROS' THEN C.VALOR ELSE 0 END), 
	OUT_2010 = SUM(CASE WHEN C.ANO = 2010 AND TIPO = 'OUTROS' THEN C.VALOR ELSE 0 END), 
	OUT_2011 = SUM(CASE WHEN C.ANO = 2011 AND TIPO = 'OUTROS' THEN C.VALOR ELSE 0 END), 
	OUT_2012 = SUM(CASE WHEN C.ANO = 2012 AND TIPO = 'OUTROS' THEN C.VALOR ELSE 0 END)
FROM @CONTAS AS C
GROUP BY C.BANCO
SELECT C.BANCO,
	INV_2009 = SUM(IIF(C.ANO = 2009 AND TIPO = 'INVESTIMENTOS', C.VALOR, 0)),
	INV_2010 = SUM(IIF(C.ANO = 2010 AND TIPO = 'INVESTIMENTOS', C.VALOR, 0)),
	INV_2011 = SUM(IIF(C.ANO = 2011 AND TIPO = 'INVESTIMENTOS', C.VALOR, 0)),
	INV_2012 = SUM(IIF(C.ANO = 2012 AND TIPO = 'INVESTIMENTOS', C.VALOR, 0)), 
	OUT_2009 = SUM(IIF(C.ANO = 2009 AND TIPO = 'OUTROS', C.VALOR, 0)), 
	OUT_2010 = SUM(IIF(C.ANO = 2010 AND TIPO = 'OUTROS', C.VALOR, 0)), 
	OUT_2011 = SUM(IIF(C.ANO = 2011 AND TIPO = 'OUTROS', C.VALOR, 0)), 
	OUT_2012 = SUM(IIF(C.ANO = 2012 AND TIPO = 'OUTROS', C.VALOR, 0))
FROM @CONTAS AS C
GROUP BY C.BANCO

Transformando linhas em colunas com PIVOT

Olá pessoas,

A mágica de transformar linhas em colunas no SQL Server com PIVOT não é tão simples, o que requer um pouco de domínio de T-SQL, para saber como utiliza-la, além de não ser pego por alguns erros comuns.

Neste primeiro artigo, vou apresentar alguns exemplos simples, e apresentar um problema comum de se trabalhar com PIVOT.

De início, temos uma massa de dados:

DECLARE @CONTAS TABLE (
  ANO SMALLINT,
  BANCO VARCHAR(100),
  TIPO VARCHAR(100),
  VALOR MONEY
)

INSERT INTO @CONTAS VALUES
(2009,'BANCO ALVORADA S/A','INVESTIMENTOS',6175979775.42),
(2010,'BANCO ALVORADA S/A','INVESTIMENTOS',6486892688.53),
(2011,'BANCO ALVORADA S/A','INVESTIMENTOS',7905663406.86),
(2012,'BANCO ALVORADA S/A','INVESTIMENTOS',9613906084.01),
(2009,'BANCO ARBI S/A','INVESTIMENTOS',8102644.84),
(2009,'BANCO ARBI S/A','OUTROS',174343.35),
(2010,'BANCO ARBI S/A','INVESTIMENTOS',7935411.15),
(2010,'BANCO ARBI S/A','OUTROS',119885.82),
(2011,'BANCO ARBI S/A','INVESTIMENTOS',8202652.29),
(2011,'BANCO ARBI S/A','OUTROS',114215.13),
(2012,'BANCO ARBI S/A','INVESTIMENTOS',8407843.72),
(2012,'BANCO ARBI S/A','OUTROS',81746.25)

SELECT ANO, BANCO, TIPO, VALOR FROM @CONTAS

Para criar transformar as linhas de contas do tipo ‘INVESTIMENTOS’ e ‘OUTROS’ em linhas com uma coluna com os valores respectivos a ‘INVESTIMENTOS’ e outra a ‘OUTROS’, utilizaremos a expressão PIVOT sobre a tabela @CONTAS, somando os valores da coluna ‘VALOR’ para onde a coluna ‘TIPO’ apresenta os valores ‘INVESTIMENTOS’ e ‘OUTROS’, assim temos a primeira consulta e resultado:

SELECT U.ANO, U.BANCO, U.INVESTIMENTOS, U.OUTROS
FROM @CONTAS AS C
PIVOT (
  SUM(VALOR) FOR
  TIPO IN (INVESTIMENTOS, OUTROS)
) AS U

Desta forma, os registros da tabela @CONTAS se transformam na PIVOT com o alias/apelido U (pode ser outra alias, sem problemas), assim as colunas TIPO e VALOR deixaram de existir, e são agrupadas por ANO e BANCO nas colunas INVESTIMENTOS e OUTROS.

Obs.: Não é obrigatório especificar as colunas no SELECT, pois ele só reconhecerá as colunas do alias U, mas especifiquem para ficar organizado.

Abaixo, um exemplo utilizando a coluna VALOR agrupada por BANCO e TIPO para cada um dos anos da coluna ANO:

SELECT U.BANCO, U.TIPO, U.[2009], U.[2010], U.[2011], U.[2012]
FROM @CONTAS AS C
PIVOT (
  SUM(C.VALOR) FOR
  C.ANO IN ([2009], [2010], [2011], [2012])
) AS U

Uma preocupação que se deve ter ao utilizar PIVOT é especificar claramente as colunas que serão agrupadas e quais as colunas que não serão, pois o SQL Server ainda não lê pensamentos, o que gera um erro bem comum, como apresentado abaixo.

“Então, estou com aquele script de PIVOT (A), mas quando removi a coluna ANO, para agrupar pelas colunas BANCO, INVESTIMENTOS e OUTROS, mas não deu certo, começou a aparecer duplicado a coluna BANCO (B).”

(A):

SELECT U.BANCO, U.INVESTIMENTOS, U.OUTROS
FROM @CONTAS AS C
PIVOT (
  SUM(VALOR) FOR
  TIPO IN (INVESTIMENTOS, OUTROS)
) AS U

(B):

Este problema se dá ao fato, que independente de você tirar as colunas especificadas no SELECT, a PIVOT continuará sendo feita sobre todas as colunas da tabela, ou seja, tirou a coluna ‘ANO’ do SELECT, não quer dizer que a PIVOT vai esquecer que a coluna ‘ANO’ existe.

Para evitar este problema, ao invés de utilizar diretamente a tabela para a PIVOT, especifique em uma subconsulta, quais colunas a PIVOT utilizará, exemplo:

SELECT U.BANCO, U.INVESTIMENTOS, U.OUTROS
FROM (
  SELECT BANCO, TIPO, VALOR
  FROM @CONTAS
) AS C
PIVOT (
  SUM(C.VALOR) FOR
  C.TIPO IN (INVESTIMENTOS, OUTROS)
) AS U

Próximas semanas, eu vou apresentar o UNPIVOT e também ensinar as limitações destas cláusulas e mágicas, assim como explicar como fazer as mesmas coisas sem utilizar PIVOT e UNPIVOT.

Entendendo EXCEPT, INTERSECT e UNION do SQL

Olá pessoas!

Não é de hoje que vejo o pessoal que está iniciando com SQL tendo problemas como EXCEPT, INTERSECT e UNION, ou não sabendo que eles existem. Desta forma, vamos à explicação!

A principal ideia destas cláusulas é ‘somar’ e ‘subtrair’ os registros de duas ou mais consultas.

___

UNION ALL e UNION

Iniciando com o UNION ALL, ele permite unir os registros de duas consultas. No exemplo abaixo, temos a tabela @A com os registros José, Maria e João, e a tabela @B com os registros Maria, Guilherme e Vitória.

Como no caso o registro Maria com ID 2 está presente nas duas tabelas, com UNION ALL, o registro irá se repetir. Caso queira que os registros iguais não se repitam, basta usar UNION, conforme o exemplo abaixo.

Internamente no SQL Server, o que o UNION faz é um UNION ALL seguindo de um DISTINCT, ou seja, as duas consultas abaixo tem o mesmo plano de execução no SQL Server e funcionam da mesma forma.

SELECT ID, NOME FROM @A
UNION
SELECT ID, NOME FROM @B
SELECT DISTINCT ID, NOME
FROM (
	SELECT ID, NOME FROM @A
	UNION ALL
	SELECT ID, NOME FROM @B
) AS C

Plano de execução:

Desta forma, evite usar UNION quando UNION ALL já é o suficiente.

___

EXCEPT

Quando se quer que apareçam os registros da tabela A que não são iguais aos da tabela B, temos a opção de ‘subtrair’ os registros utilizando EXCEPT.

___

INTERSECT

Por fim, o uso de INTERSECT permite uma intersecção entre as consultas. Que diabos é intersecção? Então, seria como exibir somente os registros que as duas consultas têm em comum.

___

Considerações finais

Abaixo a consulta completa deste post:

DECLARE @A TABLE (
	ID INT NOT NULL,
	NOME VARCHAR(10) NOT NULL
)

DECLARE @B TABLE (
	ID INT NOT NULL,
	NOME VARCHAR(10) NOT NULL
)

INSERT @A
VALUES (1, 'José'), (2, 'Maria'), (3, 'João')

INSERT @B
VALUES (2, 'Maria'), (3, 'Guilherme'), (4, 'Vitória')

SELECT Título = 'UNION ALL'

SELECT ID, NOME FROM @A
UNION ALL
SELECT ID, NOME FROM @B

SELECT Título = 'UNION'

SELECT ID, NOME FROM @A
UNION
SELECT ID, NOME FROM @B

SELECT Título = 'EXCEPT'

SELECT ID, NOME FROM @A
EXCEPT
SELECT ID, NOME FROM @B

SELECT Título = 'INTERSECT'

SELECT ID, NOME FROM @A
INTERSECT
SELECT ID, NOME FROM @B

___

Artigos relacionados:

Entendendo os ‘JOIN’s do SQL

Introdução ao SQL Server – Aula 2.2

Nesta segunda aula veremos como trabalhar com tipos de dados para armazenar Texto e Data.

Data/Hora

Armazenar data no banco de dados requer saber o intervalo de data/hora que se pretende armazenar e também a precisão desejada.

Os tipos DataTime, DataTime2 e SmallDataTime permitem armazenar datas e horas com intervalos específicos.

Datetime (8 bytes) de 1753-01-01 a 9999-12-31
Datetime2 (6-8 bytes) de 0001-01-01 a 9999-12-31
SmallDateTime (4 bytes) de 1900-01-01 a 2079-06-06

Já o tipo DateTimeOffset, permite armazenar datas e horas e também o UTC (fuso horário) da localidade em questão, permitindo cálculos precisos para sistemas que trabalham com mais de um fuso horário.

DateTimeOffset (8-10 bytes) 0001-01-01 a 9999-12-31

Para situações que se deseja somente armazenar data ou somente hora, existem os tipos Date e Time, reduzindo em alguns bytes o tamanho dos dados a serem armazenados.

Date (1-3 bytes) de 0001-01-01 a 9999-12-31
Time (3-5 bytes) de 00:00:00.0000000 a 23:59:59.9999999

Introdução ao SQL Server – Aula 2.1

Nesta segunda aula veremos como trabalhar com tipos de dados para armazenar Texto e Data.

Texto

Armazenar texto no banco de dados pode não ser uma tarefa fácil se não se sabe o tamanho dos textos que deseja armazenar e se não se sabe se o texto terá tamanho variável ou não.

O tipo de dados CHAR, permite armazenar textos com tamanho fixo, exemplo siglas como as de UF (AC, PR, SP, RJ…) e IATA (CWB, GRU, GIG…), de forma a utilizar 1 byte por caractere, podendo ter tamanho fixo para até 8000 caracteres.

Sintaxe: CHAR(n. de caracteres)

O tipo de dados VARCHAR, permite armazenar textos com tamanho variável, exemplo nomes e logradouros, de forma a utilizar 1 byte por caractere e 2 bytes para identificar o “final” do texto, podendo armazenar textos com mais de 8000 caracteres.

Sintaxe: VARCHAR(n. de caracteres), acima de 8000: VARCHAR(MAX)

Também há tipos de dados específicos para armazenar caracteres de outros idiomas, como árabe, hebraico, chinês e coreano, tendo um prefixo N, como NCHAR e NVARCHAR, utilizando 2 bytes para cada caractere, tendo assim tamanho fixo limitado à 4000 caracteres.

Sintaxe: NCHAR(n. de caracteres)

Sintaxe: NVARCHAR(n. de caracteres), acima de 4000: NVARCHAR(MAX)