Al aplicar la UNPIVOT
funció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 Firstname
y Lastname
similares 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 Lastname
columna 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;
Antes de que UNPIVOT se introdujera en SQL Server 2005, usaría un SELECT
con UNION ALL
para desenredar las columnas firstname
/ lastname
y 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 APPLY
sin 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?
fuente
Respuestas:
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
SET
valores 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
UNPIVOT
eran 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)
avarchar(50)
, sería simplemente otra conversión implícita (muy probablemente oculta), con todos los casos extraños habituales ySET
sensibilidades 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 escondersevarchar(25)
yvarchar(50)
dentro de asql_variant
).Reescribiendo
UNPIVOT
conAPPLY
yUNION ALL
evita el comportamiento de tipo (mejor) porque las reglasUNION
está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:
Ejemplo de CTE recursivo:
Finalmente, tenga en cuenta que la reescritura que se usa
CROSS APPLY
en la pregunta no es exactamente la misma que laUNPIVOT
, porque no rechaza losNULL
atributos.fuente
El
UNPIVOT
operador utiliza elIN
operador. Las especificaciones para el operador IN (captura de pantalla a continuación) indican que tantotest_expression
(en este caso, a la izquierda delIN
) como cada unoexpression
(en el lado derecho delIN
) 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.fuente