¿Por qué SQL Server requiere que la longitud del tipo de datos sea la misma cuando se usa UNPIVOT?


28

Al aplicar la UNPIVOTfunción a datos que no están normalizados, SQL Server requiere que el tipo de datos y la longitud sean los mismos. Entiendo por qué el tipo de datos debe ser el mismo, pero ¿por qué UNPIVOT requiere que la longitud sea la misma?

Digamos que tengo los siguientes datos de muestra que necesito desconectar:

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');

Si intento UNPIVOT las columnas Firstnamey Lastnamesimilares a:

select PersonId, ColumnName, Value  
from People
unpivot
(
  Value 
  FOR ColumnName in (FirstName, LastName)
) unpiv;

SQL Server genera el error:

Mensaje 8167, Nivel 16, Estado 1, Línea 6

El tipo de columna "Apellido" entra en conflicto con el tipo de otras columnas especificadas en la lista UNPIVOT.

Para resolver el error, debemos usar una subconsulta para emitir primero la Lastnamecolumna para que tenga la misma longitud 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;

Ver SQL Fiddle con Demo

Antes de que UNPIVOT se introdujera en SQL Server 2005, usaría un SELECTcon UNION ALLpara desenredar las columnas firstname/ lastnamey la consulta se ejecutaría sin la necesidad de convertir las columnas a la misma longitud:

select personid, 'firstname' ColumnName, firstname value
from People
union all
select personid, 'LastName', LastName
from People;

Ver SQL Fiddle con Demo .

También podemos desenredar con éxito los datos usando CROSS APPLYsin tener la misma longitud en el tipo de datos:

select PersonId, columnname, value
from People
cross apply
(
    select 'firstname', firstname union all
    select 'lastname', lastname
) c (columnname, value);

Ver SQL Fiddle con Demo .

He leído a través de MSDN pero no encontré nada que explique el razonamiento para forzar que la longitud del tipo de datos sea la misma.

¿Cuál es la lógica detrás de requerir la misma longitud cuando se usa UNPIVOT?


44
(Posiblemente no relacionado, pero ...) Se aplica la misma rigurosidad al comparar los tipos de columna de las dos partes de un CTE recursivo.
Andriy M

Respuestas:


25

¿Cuál es la lógica detrás de requerir la misma longitud cuando se usa UNPIVOT?

Esta pregunta solo puede ser verdaderamente respondida por las personas que trabajaron en la implementación UNPIVOT. Puede obtener esto poniéndose en contacto con ellos para obtener asistencia . Lo siguiente es mi comprensión del razonamiento, que puede no ser 100% exacto:


T-SQL contiene cualquier número de instancias de semántica extraña y otros comportamientos contraintuitivos. Algunos de estos eventualmente desaparecerán como parte de los ciclos de desaprobación, pero otros nunca podrán ser 'mejorados' o 'reparados'. Aparte de cualquier otra cosa, existen aplicaciones que dependen de estos comportamientos, por lo que se debe preservar la compatibilidad con versiones anteriores.

Las reglas para las conversiones implícitas y la derivación del tipo de expresión representan una proporción significativa de la rareza mencionada anteriormente. No envidio a los evaluadores que tienen que asegurarse de que los comportamientos extraños (y a menudo indocumentados) se mantengan (en todas las combinaciones de SETvalores de sesión, etc.) para las nuevas versiones.

Dicho esto, no hay una buena razón para no realizar mejoras, y evitar errores pasados, al introducir nuevas funciones de lenguaje (obviamente sin equipaje de compatibilidad con versiones anteriores). Nuevas características como expresiones de tabla comunes recursivas (como lo menciona Andriy M en un comentario) y UNPIVOTeran libres de tener una semántica relativamente sensata y reglas claramente definidas.

Habrá una variedad de puntos de vista sobre si incluir la longitud en el tipo lleva demasiado tiempo la escritura explícita, pero personalmente lo agradezco. En mi opinión, los tipos varchar(25)y novarchar(50) son los mismos, más que y son. La conversión de tipo de cadena de carcasa especial complica las cosas innecesariamente y no agrega valor real, en mi opinión.decimal(8)decimal(10)

Se podría argumentar que solo las conversiones implícitas que podrían perder datos deberían estar explícitamente establecidas, pero también hay casos límite allí. En última instancia, será necesaria una conversión, por lo que podríamos hacerlo explícito.

Si se permitiera la conversión implícita de varchar(25)a varchar(50), sería simplemente otra conversión implícita (muy probablemente oculta), con todos los casos extraños habituales y SETsensibilidades establecidas. ¿Por qué no hacer que la implementación sea lo más simple y explícita posible? (Sin embargo, nada es perfecto, y es una pena que se permita esconderse varchar(25)y varchar(50)dentro de a sql_variant).

Reescribiendo UNPIVOTcon APPLYy UNION ALLevita el comportamiento de tipo (mejor) porque las reglas UNIONestán sujetas a compatibilidad con versiones anteriores y están documentadas en Books Online como que permiten diferentes tipos siempre que sean comparables usando la conversión implícita (para lo cual prevalecen las reglas arcanas de tipo de datos se usan, y así sucesivamente).

La solución alternativa implica ser explícito sobre los tipos de datos y agregar conversiones explícitas cuando sea necesario. Esto me parece un progreso :)

Una forma de escribir la solución explícitamente escrita:

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;

Ejemplo de CTE recursivo:

-- 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;

Finalmente, tenga en cuenta que la reescritura que se usa CROSS APPLYen la pregunta no es exactamente la misma que la UNPIVOT, porque no rechaza los NULLatributos.


1

El UNPIVOToperador utiliza el INoperador. Las especificaciones para el operador IN (captura de pantalla a continuación) indican que tanto test_expression(en este caso, a la izquierda del IN) como cada uno expression(en el lado derecho del IN) deben ser del mismo tipo de datos. Gracias a la propiedad transitiva de la igualdad, cada expresión debe ser del mismo tipo de datos también.

ingrese la descripción de la imagen aquí


Correcto, entiendo el requisito de tipo de datos, pero la pregunta es por qué la longitud tiene que ser la misma.
Taryn

Pasé por alto eso, y sí, al operador IN generalmente no le importa la longitud.
dev_etter

Una alternativa que le permite pasar por alto la necesidad de especificar la longitud es convertir cada uno como SQL_Variant: sqlfiddle.com/#!3/13b9a/2/0
dev_etter
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.