Ao aplicar a UNPIVOT
função a dados não normalizados, o SQL Server exige que o tipo de dados e o comprimento sejam iguais. Entendo por que o tipo de dados deve ser o mesmo, mas por que o UNPIVOT exige que o comprimento seja o mesmo?
Digamos que eu tenha os seguintes dados de amostra que preciso desfazer:
CREATE TABLE People
(
PersonId int,
Firstname varchar(50),
Lastname varchar(25)
)
INSERT INTO People VALUES (1, 'Jim', 'Smith');
INSERT INTO People VALUES (2, 'Jane', 'Jones');
INSERT INTO People VALUES (3, 'Bob', 'Unicorn');
Se eu tentar UNPIVOT as colunas Firstname
e semelhantes a:Lastname
select PersonId, ColumnName, Value
from People
unpivot
(
Value
FOR ColumnName in (FirstName, LastName)
) unpiv;
O SQL Server gera o erro:
Msg 8167, Nível 16, Estado 1, Linha 6
O tipo de coluna "Sobrenome" está em conflito com o tipo de outras colunas especificadas na lista UNPIVOT.
Para resolver o erro, devemos usar uma subconsulta para primeiro converter a Lastname
coluna para ter o mesmo comprimento que Firstname
:
select PersonId, ColumnName, Value
from
(
select personid,
firstname,
cast(lastname as varchar(50)) lastname
from People
) d
unpivot
(
Value FOR
ColumnName in (FirstName, LastName)
) unpiv;
Consulte SQL Fiddle com demonstração
Antes de o UNPIVOT ser introduzido no SQL Server 2005, eu usaria um SELECT
with UNION ALL
para desdinamizar as colunas firstname
/ lastname
e a consulta seria executada sem a necessidade de converter as colunas para o mesmo comprimento:
select personid, 'firstname' ColumnName, firstname value
from People
union all
select personid, 'LastName', LastName
from People;
Consulte SQL Fiddle com demonstração .
Também podemos desativá-los com sucesso usando CROSS APPLY
sem ter o mesmo comprimento no tipo de dados:
select PersonId, columnname, value
from People
cross apply
(
select 'firstname', firstname union all
select 'lastname', lastname
) c (columnname, value);
Consulte SQL Fiddle com demonstração .
Eu li através do MSDN, mas não encontrei nada explicando o raciocínio para forçar o comprimento do tipo de dados a ser o mesmo.
Qual é a lógica por trás de exigir o mesmo comprimento ao usar o UNPIVOT?
Esta questão só pode ser verdadeiramente respondida pelas pessoas que trabalharam na implementação do
UNPIVOT
. Você pode obter isso entrando em contato com eles para obter suporte . O seguinte é o meu entendimento do raciocínio, que pode não ser 100% preciso:T-SQL contém qualquer número de instâncias de semântica estranha e outros comportamentos contra-intuitivos. Alguns deles acabarão desaparecendo como parte dos ciclos de descontinuação, mas outros podem nunca ser 'melhorados' ou 'consertados'. Além de qualquer outra coisa, existem aplicativos que dependem desses comportamentos, portanto, a compatibilidade com versões anteriores deve ser preservada.
As regras para conversões implícitas e derivação de tipo de expressão respondem por uma proporção significativa da estranheza mencionada acima. Não invejo os testadores que precisam garantir que os comportamentos estranhos (e muitas vezes não documentados) sejam preservados (sob todas as combinações de
SET
valores de sessão e assim por diante) para novas versões.Dito isso, não há um bom motivo para não fazer melhorias e evitar erros do passado ao introduzir novos recursos de linguagem (obviamente sem bagagem de compatibilidade com versões anteriores). Novos recursos como expressões de tabela comuns recursivas (como mencionado por Andriy M em um comentário) e
UNPIVOT
estavam livres para ter uma semântica relativamente sã e regras claramente definidas.Haverá uma variedade de pontos de vista sobre se incluir o comprimento no tipo está levando a digitação explícita longe demais, mas pessoalmente eu a acolho. Na minha opinião, os tipos
varchar(25)
e nãovarchar(50)
são os mesmos, não mais do que e são. A conversão especial do tipo de cadeia de revestimento complica as coisas desnecessariamente e não agrega valor real, na minha opinião.decimal(8)
decimal(10)
Pode-se argumentar que apenas as conversões implícitas que podem perder dados devem ser declaradas explicitamente, mas também existem casos extremos. Em última análise, uma conversão será necessária, portanto, podemos torná-la explícita.
Se a conversão implícita de
varchar(25)
paravarchar(50)
fosse permitida, seria apenas outra conversão implícita (provavelmente oculta), com todos os casos extremos estranhos usuais eSET
sensibilidades de configuração. Por que não tornar a implementação o mais simples e explícita possível? (Nada é perfeito, no entanto, e é uma pena que escondervarchar(25)
evarchar(50)
dentro de umsql_variant
seja permitido.)Reescrever o
UNPIVOT
comAPPLY
eUNION ALL
evita o (melhor) tipo de comportamento porque as regras paraUNION
estão sujeitas à compatibilidade com versões anteriores e estão documentadas nos Manuais Online como permitindo tipos diferentes, desde que sejam comparáveis usando conversão implícita (para a qual as regras misteriosas de precedência de tipo de dados são usados, e assim por diante).A solução envolve ser explícito sobre os tipos de dados e adicionar conversões explícitas quando necessário. Isso parece um progresso para mim :)
Uma maneira de escrever a solução alternativa digitada explicitamente:
Exemplo de CTE recursivo:
Por fim, observe que reescrever usando
CROSS APPLY
na pergunta não é exatamente o mesmo queUNPIVOT
, porque não rejeitaNULL
atributos.O
UNPIVOT
operador utiliza oIN
operador. As especificações para o operador IN (captura de tela abaixo) indicam que tanto otest_expression
(neste caso, à esquerda doIN
) quanto cadaexpression
(no lado direito doIN
) devem ser do mesmo tipo de dados. Graças à propriedade transitiva de igualdade, cada expressão também deve ser do mesmo tipo de dados.