Ao aplicar a UNPIVOT
função a dados que não são normalizados, o SQL Server exige que o tipo de dados e o comprimento sejam os mesmos. 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 tenho os seguintes dados de exemplo que preciso desarticular:
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 Lastname
semelhantes a:
select PersonId, ColumnName, Value
from People
unpivot
(
Value
FOR ColumnName in (FirstName, LastName)
) unpiv;
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 converter primeiro 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;
Veja SQL Fiddle com demonstração
Antes de o UNPIVOT ser introduzido no SQL Server 2005, eu usaria um SELECT
com UNION ALL
para descompactar as colunas firstname
/ lastname
e a consulta seria executada sem a necessidade de converter as colunas no 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 descompactar os dados com êxito 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 o MSDN, mas não encontrei nada explicando o motivo de forçar o comprimento do tipo de dados para o mesmo.
Qual é a lógica por trás da exigência do mesmo comprimento ao usar o UNPIVOT?
fonte
Respostas:
Esta pergunta só pode ser verdadeiramente respondida pelas pessoas que trabalharam na implementação do
UNPIVOT
. Você pode conseguir isso entrando em contato com eles para obter suporte . A seguir, é minha compreensão do raciocínio, que pode não ser 100% exato:O T-SQL contém várias instâncias de semântica estranha e outros comportamentos contra-intuitivos. Algumas delas acabarão desaparecendo como parte dos ciclos de depreciação, mas outras nunca poderão ser 'melhoradas' ou 'fixadas'. 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 a derivação do tipo de expressão representam 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 (em todas as combinações de
SET
valores de sessão e assim por diante) para novas versões.Dito isto, não há boas razões para não fazer melhorias e evitar erros do passado ao introduzir novos recursos de idioma (obviamente sem bagagem de compatibilidade com versões anteriores). Novos recursos, como expressões recursivas de tabela comum (como mencionado por Andriy M em um comentário),
UNPIVOT
eram livres para ter semântica relativamente sã e regras claramente definidas.Haverá uma variedade de pontos de vista sobre se a inclusão do comprimento no tipo está levando a digitação explícita muito longe, mas, pessoalmente, eu aprecio isso. Na minha opinião, os tipos
varchar(25)
e nãovarchar(50)
são os mesmos, mais do que e são. A conversão especial do tipo de string 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 explicitamente declaradas, mas também existem casos extremos. Por fim, uma conversão será necessária, portanto, podemos também explicitá-la.
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 comuns eSET
definindo sensibilidades. Por que não tornar a implementação a mais simples e explícita possível? (Nada é perfeito, no entanto, e é uma pena que se escondervarchar(25)
evarchar(50)
dentro de umasql_variant
é permitido.)Reescrever o
UNPIVOT
comAPPLY
eUNION ALL
evita o (melhor) comportamento do tipo porque as regras paraUNION
estão sujeitas a 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 arcanas do tipo de dados precedem são usados e assim por diante).A solução alternativa envolve ser explícito sobre os tipos de dados e adicionar conversões explícitas, quando necessário. Parece-me progresso :)
Uma maneira de escrever a solução alternativa digitada explicitamente:
Exemplo recursivo de CTE:
Por fim, observe que a reescrita usada
CROSS APPLY
na pergunta não é a mesma que aUNPIVOT
, porque ela não rejeitaNULL
atributos.fonte
O
UNPIVOT
operador utiliza oIN
operador. As especificações para o operador IN (captura de tela abaixo) indicam que ambostest_expression
(neste caso, à esquerda daIN
) e cadaexpression
(à direita daIN
) devem ser do mesmo tipo de dados. Graças à propriedade transitiva da igualdade, cada expressão também deve ter o mesmo tipo de dados.fonte