Por que o SQL Server exige que o comprimento do tipo de dados seja o mesmo ao usar o UNPIVOT?

28

Ao aplicar a UNPIVOTfunçã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 Firstnamee Lastnamesemelhantes 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 Lastnamecoluna 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 SELECTcom UNION ALLpara descompactar as colunas firstname/ lastnamee 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 APPLYsem 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?

Taryn
fonte
4
(Possivelmente não relacionado, mas ...) O mesmo rigor é aplicado ao comparar tipos de colunas das duas partes de um CTE recursivo.
precisa

Respostas:

25

Qual é a lógica por trás da exigência do mesmo comprimento ao usar o UNPIVOT?

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 SETvalores 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), UNPIVOTeram 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)para varchar(50)fosse permitida, seria apenas outra conversão implícita (provavelmente oculta), com todos os casos extremos estranhos comuns e SETdefinindo 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 esconder varchar(25)e varchar(50)dentro de uma sql_varianté permitido.)

Reescrever o UNPIVOTcom APPLYe UNION ALLevita o (melhor) comportamento do tipo porque as regras para UNIONestã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:

SELECT
    U.PersonId,
    U.ColumnName,
    U.Value
FROM dbo.People AS P
CROSS APPLY
(
    VALUES (CONVERT(varchar(50), Lastname))
) AS CA (Lastname)
UNPIVOT
(
    Value FOR
    ColumnName IN (P.Firstname, CA.Lastname)
) AS U;

Exemplo recursivo de CTE:

-- Fails
WITH R AS
(
    SELECT Dummy = 'A row'
    UNION ALL
    SELECT 'Another row'
    FROM R
    WHERE Dummy = 'A row'
)
SELECT Dummy
FROM R;

-- Succeeds
WITH R AS
(
    SELECT Dummy = CONVERT(varchar(11), 'A row')
    UNION ALL
    SELECT CONVERT(varchar(11), 'Another row')
    FROM R
    WHERE Dummy = 'A row'
)
SELECT Dummy
FROM R;

Por fim, observe que a reescrita usada CROSS APPLYna pergunta não é a mesma que a UNPIVOT, porque ela não rejeita NULLatributos.

Paul White diz que a GoFundMonica
fonte
1

O UNPIVOToperador utiliza o INoperador. As especificações para o operador IN (captura de tela abaixo) indicam que ambos test_expression(neste caso, à esquerda da IN) e cada expression(à direita da IN) 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.

insira a descrição da imagem aqui

dev_etter
fonte
Certo, entendo o requisito de tipo de dados, mas a questão é por que o comprimento precisa ser o mesmo.
Taryn
Eu negligenciei isso e, sim, o operador IN normalmente não se importa com o comprimento.
precisa saber é o seguinte
Uma alternativa que lhe permite ignorar a necessidade de especificar o comprimento é lançar cada um como Sql_variant: sqlfiddle.com/#!3/13b9a/2/0
dev_etter