SQL Server — включить NULL с помощью UNPIVOT

UNPIVOT не вернет NULLs, но они мне нужны в сравнении query. Я пытаюсь избежать использования ISNULL в следующем примере (поскольку в реальном sql более 100 полей):

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

Любые альтернативы?


person JeffO    schedule 16.06.2009    source источник


Ответы (7)


Это настоящая боль. Вы должны переключить их перед UNPIVOT, потому что нет строки, созданной для работы ISNULL() - здесь вам поможет генерация кода.

У меня тоже проблема с PIVOT. Отсутствующие строки превращаются в NULL, которые вы должны обернуть в ISNULL() по всей строке, если отсутствующие значения такие же, как, например, 0.0.

person Cade Roux    schedule 16.06.2009
comment
CROSS JOIN ... CASE сохранит нули. См. пример ниже. - person Peter Radocchia; 17.06.2009

Чтобы сохранить значения NULL, используйте CROSS JOIN ... CASE:

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)

Вместо:

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

Текстовый редактор с режимом столбца упрощает написание таких запросов. Он есть в UltraEdit, как и в Emacs. В Emacs это называется прямоугольным редактированием.

Возможно, вам потребуется написать сценарий для 100 столбцов.

person Peter Radocchia    schedule 17.06.2009
comment
Я бы заскриптовал более 5 колонок, но мне лень :-). Вот пример: select 'select ''' + column_name + ''' UNION ALL' FROM information_schema.columns WHERE table_name = 'table1' and table_schema = 'dbo' - person Anssssss; 28.12.2015

Я столкнулся с той же проблемой. Использование CROSS APPLY (SQL Server 2005 и более поздние версии) вместо Unpivot решило проблему. Я нашел решение на основе этой статьи Альтернативный (лучший?) метод для UNPIVOT, и я сделал следующий пример, чтобы продемонстрировать, что CROSS APPLY НЕ будет игнорировать значения NULL, такие как 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(MeasureType, Measure) 


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


 drop table #Orders
person d.emiliou    schedule 27.04.2017

или, в SQLServer 2008 короче:

...
cross join 
(values('col1'), ('col2'), ('col3'), ('col4')) column_names(column_name)
person Lukaszo    schedule 14.10.2010

Я обнаружил, что левое внешнее соединение результата UNPIVOT с полным списком полей, удобно извлеченным из INFORMATION_SCHEMA, является практическим решением этой проблемы в некоторых контекстах.

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

output looks like:

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

person Beej    schedule 08.12.2012
comment
Единственным недостатком этого является то, что все ваши исходные поля должны быть введены непрерывно. - person Ozziemedes; 21.11.2014
comment
@Ozziemedes Я думаю, вы, возможно, упускаете суть, даже OP преобразует исходные данные в varchar ... в какой-то момент во всех этих методах исходные типы должны быть потеряны из-за фундаментальной природы unpivot, возвращающего то, что раньше было другим столбцы, под один столбец. Кроме того, вы уверены, что правильно используете слово «смежный»? Определение этого слова означает рядом друг с другом, а не то, что вы, кажется, имели в виду? - person Beej; 22.11.2014
comment
РЖУ НЕ МОГУ. В контексте: я искал способ развернуть менее строго типизированные данные, когда наткнулся на этот пост. Я не критиковал - просто констатировал. Я, конечно, не ожидал, что мне оторвут голову, но добро пожаловать в интернет и все такое. вздох - person Ozziemedes; 23.11.2014

Используя динамический SQL и COALESCE, я решил проблему следующим образом:

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 )
person Charlie    schedule 09.01.2012

ISNULL — это половина ответа. Используйте NULLIF для преобразования обратно в NULL. Например.

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;

Здесь я использую «0Null» в качестве промежуточного значения. Вы можете использовать все, что вам нравится. Однако вы рискуете столкнуться с пользовательским вводом, если выберете что-то реальное, например «Null». Мусор отлично работает "!@#34())0", но может быть более запутанным для будущих программистов. Я уверен, что вы получите картину.

person John Kelly    schedule 10.10.2014
comment
ОП спрашивает о unpivot, а не pivot. Пожалуйста, исправьте свой ответ, если это возможно. - person Conduit; 10.10.2014
comment
Если вы знаете что-нибудь об этой теме... вы бы поняли, что решение одинаково хорошо работает и для UNPIVOT. Я не буду вносить тривиальные изменения, пока вы не попросите других отозвать свои решения, которые не имеют ничего общего с PIVOT или UNPIVOT, например. Cross Joins, Dynamic SQL и шутки про аудиторов. Дело в том, что мое решение единственное дает ответ на исходный вопрос. - person John Kelly; 13.10.2014
comment
Ответ вверху работал достаточно хорошо, чтобы OP был принят. Независимо от тривиальности (что я, как аналитик, ежедневно работающий с MSSQL, понимаю), вы не смогли ответить на вопрос в соответствии с правилами этого сайта, как определено в справочный центр. Посты других не диктуют, что разрешено/не разрешено. Я не пытаюсь быть придурком — сообщество специально поручило мне просмотреть этот пост, так как он был одним из ваших первых. - person Conduit; 13.10.2014
comment
Я достаточно хорошо понимаю, как отвечать на заданный конкретный вопрос. Я считаю, что другие авторы нарушили ваши правила. Я придерживался этого мнения еще до того, как предложил свое решение. Представьте мое удивление, когда меня помечают! В любом случае, я не изменю свой пост. Дайте ему -1, если необходимо, или удалите его. Тем не менее, я думаю, что любой, кто придет на этот сайт за ответом о том, как сохранить NULL в UNPIVOT или PIVOT, найдет мой ответ полезным. - person John Kelly; 13.10.2014
comment
Некоторые другие посты также были отмечены флажками. Редактирование может быть незначительным, но оно важно важно... у нас есть много новичков, которые приходят сюда за ответами и могут не сразу увидеть связь между вашим ответом и первоначальным сообщением. Если вы внесете изменения, я смогу удалить -1, и мод, назначенный для проверки моего флага, скорее всего, отклонит его. - person Conduit; 13.10.2014
comment
Опять же, мой ответ был/есть по теме. Вы можете проверить принятый ответ от Кейда Ру, который представил PIVOT как часть своего ответа, тем самым включив PIVOT в рамки этой темы. Мой ответ был об этом. Примечание: это был ваш собственный рецензент, который убрал эту ветку не по теме --> CROSS JOIN ... CASE сохранит нулевые значения. См. пример ниже. — Питер Радоккиа Теперь я могу сказать вам, что CROSS JOIN не имеет ничего общего с сохранением NULL в операторе PIVOT. На самом деле перекрестные соединения — это старый метод, который PIVOT должен был заменить в SQL '05. - person John Kelly; 27.10.2014