SQL Server: includere NULL tramite UNPIVOT
-
05-07-2019 - |
Domanda
UNPIVOT non restituirà NULL, ma ne ho bisogno in una query di confronto. Sto cercando di evitare di usare ISNULL nell'esempio seguente (Perché nel sql reale ci sono oltre 100 campi .:
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
Qualche alternativa?
Soluzione
È un vero dolore. Devi spegnerli prima di UNPIVOT
, perché non esiste alcuna riga prodotta per il funzionamento di ISNULL ()
- la generazione del codice è il tuo amico qui.
Ho anche il problema su PIVOT
. Le righe mancanti si trasformano in NULL
, che devi racchiudere in ISNULL ()
su tutta la riga se i valori mancanti sono uguali a 0.0
per esempio.
Altri suggerimenti
Per preservare i NULL, usa 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)
Invece di:
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 di testo con modalità colonna semplifica la scrittura di tali query. UltraEdit ce l'ha, così come Emacs. In Emacs si chiama modifica rettangolare.
Potrebbe essere necessario copiarlo per 100 colonne.
Ho riscontrato lo stesso problema. L'uso di CROSS APPLY
(SQL Server 2005 e versioni successive) anziché Unpivot
ha risolto il problema. Ho trovato la soluzione basata su questo articolo Un metodo alternativo (migliore?) su UNPIVOT
e ho fatto il seguente esempio per dimostrare che CROSS APPLY NON ignorerà i NULL come 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
o, in SQL Server 2008 in modo più breve:
...
cross join
(values('col1'), ('col2'), ('col3'), ('col4')) column_names(column_name)
Ho trovato l'esterno sinistro che unisce il risultato UNPIVOT all'elenco completo dei campi, opportunamente estratto da INFORMATION_SCHEMA, per essere una risposta pratica a questo problema in alcuni contesti.
-- 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>
l'output è simile 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 | +----------------------+-----------------------+
Usando SQL dinamico e COALESCE, ho risolto il problema in questo modo:
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 )
ISNULL è metà della risposta. Usa NULLIF per tradurre di nuovo in NULL. Per esempio.
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;
Qui uso " 0Null " come mio valore intermedio. Puoi usare tutto quello che ti piace. Tuttavia, rischi di scontrarti con l'input dell'utente se scegli qualcosa di reale come "Null". La spazzatura funziona bene "! @ # 34 ()) 0 " ma potrebbe essere più fonte di confusione per i programmatori futuri. Sono sicuro che avrai la foto.