2009-06-16 31 views
16

UNPIVOT no devolverá valores NULL, pero los necesito en una consulta de comparación. Estoy tratando de evitar el uso de ISNULL el siguiente ejemplo (Porque en el SQL real hay más de 100 campos .:Servidor SQL: incluya NULL con UNPIVOT

Select ID, theValue, column_name 
From 
(select ID, 
    ISNULL(CAST([TheColumnToCompare] AS VarChar(1000)), '') as TheColumnToCompare 
    from MyView 
    where The_Date = '04/30/2009' 
) MA 
UNPIVOT 
    (theValue FOR column_name IN 
    ([TheColumnToCompare]) 
) AS unpvt 

alguna alternativa?

Respuesta

11

Es un dolor real. Tienes que cambiar a cabo antes de la UNPIVOT, porque no hay una fila producido por ISNULL() para operar en - la generación de código es tu amigo aquí

tengo el problema de PIVOT así filas desaparecidos a su vez en NULL, que hay que envolver en ISNULL() todo el camino.. a través de la fila si falta los valores son los mismos que 0.0 por ejemplo.

+0

CROSS JOIN ... CASE preservará los nulos. Vea abajo para un ejemplo. –

15

Para conservar los valores NULL, el uso combinación cruzada ... CASO:

select a.ID, b.column_name 
, column_value = 
    case b.column_name 
     when 'col1' then a.col1 
     when 'col2' then a.col2 
     when 'col3' then a.col3 
     when 'col4' then a.col4 
    end 
from (
    select ID, col1, col2, col3, col4 
    from table1 
) a 
cross join (
    select 'col1' union all 
    select 'col2' union all 
    select 'col3' union all 
    select 'col4' 
) b (column_name) 

En lugar de:

select ID, column_name, column_value 
From (
    select ID, col1, col2, col3, col4 
    from from table1 
) a 
unpivot (
    column_value FOR column_name IN (
    col1, col2, col3, col4) 
) b 

Un editor de texto con el modo de columna hace que este tipo de consultas más fácil de escribir. UltraEdit lo tiene, al igual que Emacs. En Emacs se llama edición rectangular.

Es posible que necesite crear un script para 100 columnas.

+0

Lo escribiría en más de 5 columnas, pero soy flojo de esa manera :-). Aquí hay un ejemplo: 'select 'select' '' + column_name + '' 'UNION ALL' FROM information_schema.column WHERE table_name = 'table1' y table_schema = 'dbo'' – Anssssss

3

o, en SQL Server 2008 en camino más corto:

... 
cross join 
(values('col1'), ('col2'), ('col3'), ('col4')) column_names(column_name) 
1

El uso de SQL dinámico y se unen, que resolvió el problema como este:

DECLARE @SQL NVARCHAR(MAX) 
DECLARE @cols NVARCHAR(MAX) 
DECLARE @dataCols NVARCHAR(MAX) 

SELECT 
    @dataCols = COALESCE(@dataCols + ', ' + 'ISNULL(' + Name + ',0) ' + Name , 'ISNULL(' + Name + ',0) ' + Name) 
FROM Metric WITH (NOLOCK) 
ORDER BY ID 

SELECT 
    @cols = COALESCE(@cols + ', ' + Name , Name) 
FROM Metric WITH (NOLOCK) 
ORDER BY ID 

SET @SQL = 'SELECT ArchiveID, MetricDate, BoxID, GroupID, ID MetricID, MetricName, Value 
      FROM 
       (SELECT ArchiveID, [Date] MetricDate, BoxID, GroupID, ' + @dataCols + ' 
       FROM MetricData WITH (NOLOCK) 
       INNER JOIN Archive WITH (NOLOCK) 
        ON ArchiveID = ID 
       WHERE BoxID = ' + CONVERT(VARCHAR(40), @BoxID) + ' 
       AND GroupID = ' + CONVERT(VARCHAR(40), @GroupID) + ') p 
      UNPIVOT 
       (Value FOR MetricName IN 
        (' + @cols + ') 
      )AS unpvt 
      INNER JOIN Metric WITH (NOLOCK) 
       ON MetricName = Name 
      ORDER BY MetricID, MetricDate' 

EXECUTE(@SQL) 
2

que he encontrado externo izquierdo unirse el resultado UNPIVOT a la lista completa de campos, convenientemente extraídos de INFORMATION_SCHEMA, para ser una respuesta práctica a este problema en algunos contextos.

-- test data 
CREATE TABLE _t1(name varchar(20),object_id varchar(20),principal_id varchar(20),schema_id varchar(20),parent_object_id varchar(20),type varchar(20),type_desc varchar(20),create_date varchar(20),modify_date varchar(20),is_ms_shipped varchar(20),is_published varchar(20),is_schema_published varchar(20)) 
INSERT INTO _t1 SELECT 'blah1', 3, NULL, 4, 0, 'blah2', 'blah3', '20100402 16:59:23.267', NULL, 1, 0, 0 

-- example 
select c.COLUMN_NAME, Value 
from INFORMATION_SCHEMA.COLUMNS c 
left join (
    select * from _t1 
) q1 
unpivot (Value for COLUMN_NAME in (name,object_id,principal_id,schema_id,parent_object_id,type,type_desc,create_date,modify_date,is_ms_shipped,is_published,is_schema_published) 
) t on t.COLUMN_NAME = c.COLUMN_NAME 
where c.TABLE_NAME = '_t1' 
</pre> 

salida será similar a:

+----------------------+-----------------------+ 
| COLUMN_NAME  |  Value   | 
+----------------------+-----------------------+ 
| name     | blah1     | 
| object_id   | 3      | 
| principal_id   | NULL     | <====== 
| schema_id   | 4      | 
| parent_object_id  | 0      | 
| type     | blah2     | 
| type_desc   | blah3     | 
| create_date   | 20100402 16:59:23.26 | 
| modify_date   | NULL     | <====== 
| is_ms_shipped  | 1      | 
| is_published   | 0      | 
| is_schema_published | 0      | 
+----------------------+-----------------------+ 

+0

El único inconveniente de esto es que todos sus campos de origen deben escribirse contiguamente. – Ozziemedes

+0

@Ozziemedes Creo que puede estar perdiendo el sentido, incluso el OP arroja los datos originales a varchar ... en algún punto de todas estas técnicas, los tipos originales deben perderse debido a la naturaleza fundamental de un unpivot que devuelve lo que era previamente diferentes columnas, bajo una sola columna. Además, ¿estás seguro de que estás usando la palabra "contiguo" correctamente? La definición de esa palabra significa "uno al lado del otro", no "el mismo" que parece que pretendía? – Beej

+0

LOL. En contexto: estaba buscando una manera de desvotar los datos menos fuertemente tipados cuando me encontré con esta publicación. No estaba haciendo una crítica, solo una observación. Ciertamente no esperaba que me arrancaran la cabeza, sino "bienvenidos a Internet" y todo eso. * suspiro * – Ozziemedes

-1

ISNULL es la mitad de la respuesta. Use NULLIF para volver a traducir a NULL. P.ej.

DECLARE @temp TABLE(
    Foo varchar(50), 
    Bar varchar(50) NULL 
    ); 

INSERT INTO @temp(Foo,Bar)VALUES('licious',NULL); 

SELECT * FROM @temp; 

SELECT 
    Col, 
    NULLIF(Val,'0Null') AS Val 
FROM(
    SELECT 
     Foo, 
     ISNULL(Bar,'0Null') AS Bar 
    FROM 
     @temp 
    ) AS t 
UNPIVOT(
    Val FOR Col IN(
     Foo, 
     Bar 
     ) 
    ) up; 

Aquí uso "0Null" como mi valor intermedio. Puedes usar lo que quieras. Sin embargo, corre el riesgo de colisión con la entrada del usuario si elige algo del mundo real como "nulo". La basura funciona bien "! @ # 34()) 0" pero puede ser más confuso para los codificadores futuros. Estoy seguro de que entiendes la imagen.

+1

OP pregunta por 'unpivot', no' pivot'. Por favor revise su respuesta si es posible. – Conduit

+1

Si sabe algo sobre este tema ... entenderá que la solución funciona igual de bien para UNPIVOT. No haré el cambio trivial hasta que solicite a otros que retiren sus soluciones que no tienen nada que ver con PIVOT o UNPIVOT, p. Cross Joins, SQL dinámico y chistes de auditor. El hecho es que mi solución es la * única * que proporciona una respuesta a la pregunta original. –

+0

La respuesta en la parte superior funcionó lo suficientemente bien como para que OP sea aceptado. Independientemente de la trivialidad (que, como analista que trabaja con MSSQL a diario, me doy cuenta), no ha respondido la pregunta de acuerdo con las reglas de este sitio como se define en [el centro de ayuda] (http://stackoverflow.com/help). Las publicaciones de otros no dictan lo que está/no está permitido. No intento ser un imbécil aquí. La comunidad me encargó específicamente que revisara esta publicación, ya que fue una de las primeras. – Conduit

2

I Corre hacia el mismo problema, al utilizar CROSS APPLY (SQL Server 2005 y posterior) en lugar de Unpivot Resolvió el problema. Encontré la solución basada en este artículo An Alternative (Better?) Method to UNPIVOT e hice el siguiente ejemplo para demostrar que CROSS APPLY NO ignorará valores nulos como Unpivot.

create table #Orders (OrderDate datetime, product nvarchar(100), ItemsCount float,GrossAmount float, employee nvarchar(100)) 

insert into #Orders 
select getutcdate(),'Windows',10,10.32,'Me' 
union 
select getutcdate(),'Office',31,21.23,'you' 
union 
select getutcdate(),'Office',31,55.45,'me' 
union 
select getutcdate(),'Windows',10,null,'You' 

SELECT OrderDate, product,employee,Measure,MeasureType 
from #Orders orders 
CROSS APPLY (
    VALUES ('ItemsCount',ItemsCount),('GrossAmount',GrossAmount) 
    ) 
    x(Measure, MeasureType) 


SELECT OrderDate, product,employee,Measure,MeasureType 
from #Orders orders 
UNPIVOT 
    (Measure FOR MeasureType IN 
     (ItemsCount,GrossAmount) 
)AS unpvt; 


drop table #Orders