I have a question about SQL Server.
If any column does not have values, then need to provide which column does not have a value.
If data is not available in one column, then output column value not available.
If data not available more than one column, then output those columns value are not available.
Concatenate multiple columns when values not exists.
Sample data :
CREATE TABLE [dbo].[EmpDetails]
(
[Empid] [int] NULL,
[Empname] [varchar](50) NULL,
[Location] [varchar](50) NULL,
[Deptid] [int] NULL,
[Deptname] [varchar](50) NULL
)
INSERT INTO [dbo].[EmpDetails] ([Empid], [Empname], [Location], [Deptid], [Deptname])
VALUES (1, NULL, N'che', 10, N'hr')
INSERT INTO [dbo].[EmpDetails] ([Empid], [Empname], [Location], [Deptid], [Deptname])
VALUES (2, N'hari', N'pune', NULL, N'pm')
INSERT INTO [dbo].[EmpDetails] ([Empid], [Empname], [Location], [Deptid], [Deptname])
VALUES (3, N'var', NULL, 30, NULL)
INSERT INTO [dbo].[EmpDetails] ([Empid], [Empname], [Location], [Deptid], [Deptname])
VALUES (4, NULL, NULL, NULL, N'hr')
INSERT INTO [dbo].[EmpDetails] ([Empid], [Empname], [Location], [Deptid], [Deptname])
VALUES (NULL, N'venu', N'pune', NULL, NULL)
INSERT INTO [dbo].[EmpDetails] ([Empid], [Empname], [Location], [Deptid], [Deptname])
VALUES (NULL, N'kumar', N'pune', 20, NULL)
INSERT INTO [dbo].[EmpDetails] ([Empid], [Empname], [Location], [Deptid], [Deptname])
VALUES (8, 'ravi', NULL, 10, N'hr')
INSERT INTO [dbo].[EmpDetails] ([Empid], [Empname], [Location], [Deptid], [Deptname])
VALUES (10, N'k', N'pune', 20, N'hr')
Based on above data I want output like below :
empid | Empname | Location | Deptid| Deptname | Validate
------+---------+----------+-------+------------+---------------------------------------
1 | NULL |Che |10 | hr | Empname value is not available
2 | hari |pune |NULL | pm | Deptid value is not available
3 | var |NULL |30 | NULL | location and deptname values are not available
4 | NULL |NULL |NULL | hr | empname and location and deptid values are not available
NULL | venu |pune |NULL | NULL | empid and deptid and deptname values are not available
NULL | kumar |pune |20 | NULL | empid and deptname values are not available
8 | ravi |NULL |10 | hr | location value is not available
10 | k |pune |20 | hr |
I tried like below :
SELECT
empid, empname, location, deptid, deptname,
CASE
WHEN COALESCE(empid, '') = '' THEN 'Empid'
ELSE ''
END + ' '+
CASE
WHEN COALESCE(empname, '') = ''
THEN 'Empname'
ELSE ''
END + ' '+
CASE
WHEN COALESCE(Location, '') = ''
THEN 'Location'
ELSE ''
END + ' '+
CASE
WHEN COALESCE(Deptid, '') = ''
THEN 'Deptid'
ELSE ''
END + ' '+
CASE
WHEN COALESCE(Deptname, '') = ''
THEN 'Deptname'
ELSE ''
END + ' ' +
+ 'value not available' AS Validate
FROM
[Test].[dbo].[EmpDetails]
But this query is not returning the expected output.
Please tell me how to write query to achieve this task in SQL Server
Please try the following solution.
It is based on XML and XQuery.
It will work starting from SQL Server 2012 onwards.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE
(
Empid int NULL,
Empname varchar(50) NULL,
Location varchar(50) NULL,
Deptid int NULL,
Deptname varchar(50) NULL
);
INSERT INTO #tbl (Empid, Empname, Location, Deptid, Deptname) VALUES
(1, NULL, N'che', 10, N'hr'),
(2, N'hari', N'pune', NULL, N'pm'),
(3, N'var', NULL, 30, NULL),
(4, NULL, NULL, NULL, N'hr'),
(NULL, N'venu', N'pune', NULL, NULL),
(NULL, N'kumar', N'pune', 20, NULL),
(8, 'ravi', NULL, 10, N'hr'),
(10, N'k', N'pune', 20, N'hr');
-- DDL and sample data population, end
SELECT t.*
, s
, Validate = REPLACE(c.query('
for $x in /root/source/*
let $name := local-name($x)
return if (/root/target/*[local-name(.)=$name]) then ()
else $name
').value('.','VARCHAR(MAX)'),SPACE(1), ' and ') +
CASE WHEN s=5 THEN ''
WHEN s=4 THEN ' value is not available'
WHEN s<4 THEN ' values are not available'
END
FROM #tbl AS t
CROSS APPLY (SELECT TRY_CAST('<root><source><Empid/><Empname/><Location/><Deptid/><Deptname/></source>' +
(SELECT Empid, Empname, Location, Deptid, Deptname
FOR XML PATH(''), ROOT('target')) + '</root>' AS XML)) AS t1(c)
CROSS APPLY (SELECT c.value('count(/root/target/*)', 'INT')) AS t2(s);
Output
+-------+---------+----------+--------+----------+---+----------------------------------------------------------+
| Empid | Empname | Location | Deptid | Deptname | s | Validate |
+-------+---------+----------+--------+----------+---+----------------------------------------------------------+
| 1 | NULL | che | 10 | hr | 4 | Empname value is not available |
| 2 | hari | pune | NULL | pm | 4 | Deptid value is not available |
| 3 | var | NULL | 30 | NULL | 3 | Location and Deptname values are not available |
| 4 | NULL | NULL | NULL | hr | 2 | Empname and Location and Deptid values are not available |
| NULL | venu | pune | NULL | NULL | 2 | Empid and Deptid and Deptname values are not available |
| NULL | kumar | pune | 20 | NULL | 3 | Empid and Deptname values are not available |
| 8 | ravi | NULL | 10 | hr | 4 | Location value is not available |
| 10 | k | pune | 20 | hr | 5 | |
+-------+---------+----------+--------+----------+---+----------------------------------------------------------+
Related
I have difficulties to write a SQL script.
I have a table like this:
And I want to have a result like this:
I used the min and max functions but that doesn't work.
Do you have any idea?
Thank you for your help
MIN() and MAX() do appear to get you what you want. FYI, I have converted your dates to yyyy-MM-dd format.
IF OBJECT_ID('tempdb..#YourTable','U') IS NOT NULL DROP TABLE #YourTable; --SELECT * FROM #YourTable
CREATE TABLE #YourTable (
Business_Key int NOT NULL,
[Name] varchar(10) NOT NULL,
[Attribute] varchar(10) NOT NULL,
ValidFrom date NOT NULL,
ValidTo date NOT NULL,
Primary_Key int NOT NULL,
);
INSERT INTO #YourTable (Business_Key, [Name], Attribute, ValidFrom, ValidTo, Primary_Key)
VALUES (1, 'Toto', 'Child', '2020-01-01', '2020-01-03', 1)
, (1, 'Toto', 'Child', '2020-01-03', '2020-01-10', 2)
, (1, 'Toto', 'Man' , '2020-01-10', '2020-01-15', 3)
, (2, 'Tata', 'Woman', '2020-01-01', '2020-01-15', 4)
, (3, 'Titi', 'Man' , '2020-01-01', '2020-01-15', 5)
, (3, 'Titi', 'Man' , '2020-01-05', '2020-01-17', 6)
SELECT Business_Key
, [Name]
, [Attribute]
, ValidFrom = MIN(ValidFrom)
, ValidTo = MAX(ValidTo)
, Primary_Key = MAX(Primary_Key)
FROM #YourTable yt
GROUP BY Business_Key, [Name], [Attribute]
Returns:
| Business_Key | Name | Attribute | ValidFrom | ValidTo | Primary_Key |
|--------------|------|-----------|------------|------------|-------------|
| 1 | Toto | Child | 2020-01-01 | 2020-01-10 | 2 |
| 1 | Toto | Man | 2020-01-10 | 2020-01-15 | 3 |
| 2 | Tata | Woman | 2020-01-01 | 2020-01-15 | 4 |
| 3 | Titi | Man | 2020-01-01 | 2020-01-17 | 6 |
I am tracking data in my SCD table as shown below image using the SSIS package.
I need to add a new column, the "Column Updated" (as depicted above) which represents what columns were updated between N and N-1 transaction. This can be achieved by Cursor however I am looking for suggestions to do this in an efficient way. Would it be possible to perform within SCD or any other inbuilt SQL server function?
adding script:
Create table SCDtest
(
id int ,
empid int ,
Deptid varchar(10),
Ename varchar(50),
DeptName varchar(50),
city varchar(50),
startdate datetime,
Enddate datetime ,
ColumnUpdated varchar(500)
)
Insert into SCDtest values (1, 1, 'D1', 'Mike', 'Account', 'Atlanta', '7/31/2020', '8/3/2020','' )
Insert into SCDtest values (2, 2, 'D2', 'Roy', 'IT', 'New York', '7/31/2020', '8/5/2020','' )
Insert into SCDtest values (3, 1, 'D1', 'Ross', 'Account', 'Atlanta', '8/4/2020', '8/7/2020','' )
Insert into SCDtest values (4, 2, 'D2', 'Roy', 'IT', 'Los angeles', '8/5/2020',NULL ,'' )
Insert into SCDtest values (5, 1, 'D1', 'John', 'Marketing', 'Boston', '8/8/2020', NULL,'')
Thank you
Honestly I don't really know why you need this functionality as you can very easily just look at the two rows to see any changes, on the off chance that you do actually need to see them. I've never needed a ColumnUpdated type value and I don't think the processing required to generate one and the storage to hold the data is worth having it.
That said, here is one way you can calculate the desired output from your given test data. Ideally you would do this in a more efficient way as part of your ETL process that is updating the rows as they come in rather than all at once. Though this obviously required info about your ETL that you haven't included in your question:
Query
declare #SCDtest table(id int,empid int,Deptid varchar(10),Ename varchar(50),DeptName varchar(50),city varchar(50),startdate datetime,Enddate datetime);
Insert into #SCDtest values(1, 1, 'D1', 'Mike', 'Account', 'Atlanta', '7/31/2020', '8/3/2020'),(2, 2, 'D2', 'Roy', 'IT', 'New York', '7/31/2020', '8/5/2020'),(3, 1, 'D1', 'Ross', 'Account', 'Atlanta', '8/4/2020', '8/7/2020'),(4, 2, 'D2', 'Roy', 'IT', 'Los angeles', '8/5/2020',NULL),(5, 1, 'D1', 'John', 'Marketing', 'Boston', '8/8/2020', NULL);
with l as
(
select *
,lag(id,1) over (partition by empid order by id) as l
from #SCDtest
)
select l.id
,l.empid
,l.Deptid
,l.Ename
,l.DeptName
,l.city
,l.startdate
,l.Enddate
,stuff(concat(case when l.Deptid <> t.Deptid then ', Deptid' end
,case when l.Ename <> t.Ename then ', Ename' end
,case when l.DeptName <> t.DeptName then ', DeptName' end
,case when l.city <> t.city then ', city' end
)
,1,2,''
) as ColumnUpdated
from l
left join #SCDtest as t
on l.l = t.id
order by l.empid
,l.startdate;
Output
+----+-------+--------+-------+-----------+-------------+-------------------------+-------------------------+-----------------------+
| id | empid | Deptid | Ename | DeptName | city | startdate | Enddate | ColumnUpdated |
+----+-------+--------+-------+-----------+-------------+-------------------------+-------------------------+-----------------------+
| 1 | 1 | D1 | Mike | Account | Atlanta | 2020-07-31 00:00:00.000 | 2020-08-03 00:00:00.000 | NULL |
| 3 | 1 | D1 | Ross | Account | Atlanta | 2020-08-04 00:00:00.000 | 2020-08-07 00:00:00.000 | Ename |
| 5 | 1 | D1 | John | Marketing | Boston | 2020-08-08 00:00:00.000 | NULL | Ename, DeptName, city |
| 2 | 2 | D2 | Roy | IT | New York | 2020-07-31 00:00:00.000 | 2020-08-05 00:00:00.000 | NULL |
| 4 | 2 | D2 | Roy | IT | Los angeles | 2020-08-05 00:00:00.000 | NULL | city |
+----+-------+--------+-------+-----------+-------------+-------------------------+-------------------------+-----------------------+
My question is very similar to Efficiently convert rows to columns in sql server. For every FieldName that exists, I need a column for it. The issue I am having is
I am creating many rows for each ID
I have an uncertain amount of columns. There are at least 2000 different FieldNames so I need something that is efficient
I need to have conditions based on if it's a string, numeric, or date field.
Original table:
CREATE TABLE [UWFieldTable]
(
[FieldName] nvarchar(25),
[StringValue] nvarchar(25),
[DateValue] date,
[NumericValue] nvarchar(25),
[Id] nvarchar(5)
)
INSERT INTO [UWFieldTable] VALUES ('UWName', 'Kim', NULL, NULL, 'A1')
INSERT INTO [UWFieldTable] VALUES ('UWDate', NULL, '1/9/2020', NULL, 'A1')
INSERT INTO [UWFieldTable] VALUES ('UWNumber', '3.3', NULL, '3.3', 'A2')
INSERT INTO [UWFieldTable] VALUES ('CloseName', 'Billy', NULL, NULL, 'A2')
INSERT INTO [UWFieldTable] VALUES ('CloseDate', NULL, '1/6/2020', NULL, 'A3')
INSERT INTO [UWFieldTable] VALUES ('CloseNumber', '30.6', NULL, '30.6', 'A3')
INSERT INTO [UWFieldTable] VALUES ('UWDate', NULL, '1/10/2020', NULL, 'A3')
FieldName | StringValue | DateValue | NumericValue | Id |
-------------------------------------------------------------
UWName | Kim | NULL | NULL | A1 |
UWDate | NULL | 2020-01-09 | NULL | A1 |
UWNumber | 3.3 | NULL | 3.3 | A2 |
CloseName | Billy | NULL | NULL | A2 |
CloseDate | NULL | 2020-01-06 | NULL | A3 |
CloseNumber | 30.6 | NULL | 30.6 | A3 |
UWDate | NULL | 2020-01-10 | NULL | A3 |
...
Desired output:
Id | UWName | UWDate | UWNumber | CloseName | CloseDate | CloseNumber |
--------------------------------------------------------------------------------
A1 | Kim | 2020-01-09 | NULL | NULL | NULL | NULL |
A2 | NULL | NULL | 3.3 | Billy | NULL | NULL |
A3 | NULL | 2020-01-01 | NULL | NULL | 2020-01-10 | 30.6 |
Attempted code:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME([FieldName])
FROM [UWFieldTable]
GROUP BY [FieldName]
ORDER BY [FieldName]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query = 'SELECT [Id], ' + #cols + ' from
(
select *
from [UWFieldTable]
) x
PIVOT
(
MAX([StringValue])
FOR [FieldName] in (' + #cols + ')
) p order by [Id]'
EXECUTE(#query);
Try this:
SELECT [Id], [UWName],[UWDate],[UWNumber],[CloseName],[CloseDate],[CloseNumber] from
(
SELECT [Id],[FieldName], Val
FROM (SELECT [FieldName], [StringValue], CAST([DateValue] AS nvarchar(25)) AS DateValue, [NumericValue], [Id]
FROM [UWFieldTable]) AS srcUnpivot
UNPIVOT ( Val FOR ColType IN ([StringValue], DateValue, [NumericValue])) AS unpvt
) x
PIVOT
(
MAX([Val])
FOR [FieldName] in ([UWName],[UWDate],[UWNumber],[CloseName],[CloseDate],[CloseNumber])
) AS pv1
ref: SQL Server Pivot on multiple fields
for such case, it is easier to use conditional case statement with GROUP BY
SELECT Id,
UWName = MAX(CASE WHEN [FieldName] = 'UWName' THEN [StringValue] END),
UWDate = MAX(CASE WHEN [FieldName] = 'UWDate' THEN [DateValue] END),
UWNumber = MAX(CASE WHEN [FieldName] = 'UWNumber' THEN [NumericValue] END),
CloseName = MAX(CASE WHEN [FieldName] = 'CloseName' THEN [StringValue] END),
CloseDate = MAX(CASE WHEN [FieldName] = 'CloseDate' THEN [DateValue] END),
CloseNumber = MAX(CASE WHEN [FieldName] = 'CloseNumber' THEN [NumericValue] END)
FROM [UWFieldTable]
GROUP BY Id
The report data we receive from analysts come in Table format with arbitrary structure. All we know is that each row has a CustomerId column. But the others, we do not know and can vary every time.
The destination system that receives this data only does in Key/Value format so we have to convert the report tables into Key/Value.
So, if for instance, the source report table has the following structure:
CREATE TABLE [dbo].[SampleSourceTable](
[CustomerId] [bigint] NULL,
[Column1] [nchar](10) NULL,
[Column2] [int] NULL,
[Column3] [datetime] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[SampleSourceTable] ([CustomerId], [Column1], [Column2], [Column3]) VALUES (1, N'aaa', 123, CAST(N'2019-01-01T00:00:00.000' AS DateTime))
GO
INSERT [dbo].[SampleSourceTable] ([CustomerId], [Column1], [Column2], [Column3]) VALUES (2, N'bbb', 456, CAST(N'2018-01-01T00:00:00.000' AS DateTime))
GO
We would like this data to be converted into the following structure:
CREATE TABLE [dbo].[SampleDestinationTable](
[CustomerId] [bigint] NULL,
[Attribute] [nvarchar](255) NULL,
[Value] [nvarchar](max) NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
INSERT [dbo].[SampleDestinationTable] ([CustomerId], [Attribute], [Value]) VALUES (1, N'Column1', N'aaa')
GO
INSERT [dbo].[SampleDestinationTable] ([CustomerId], [Attribute], [Value]) VALUES (1, N'Column2', N'123')
GO
INSERT [dbo].[SampleDestinationTable] ([CustomerId], [Attribute], [Value]) VALUES (1, N'Column3', N'2019-01-01 00:00:00.000')
GO
INSERT [dbo].[SampleDestinationTable] ([CustomerId], [Attribute], [Value]) VALUES (2, N'Column1', N'bbb')
GO
INSERT [dbo].[SampleDestinationTable] ([CustomerId], [Attribute], [Value]) VALUES (2, N'Column2', N'456')
GO
INSERT [dbo].[SampleDestinationTable] ([CustomerId], [Attribute], [Value]) VALUES (2, N'Column3', N'2018-01-01 00:00:00.000')
GO
The challenge here, however, is that the source report table does not have a fixed structure.
At first, I thought about going through every row using a cursor and then using a nested cursor go through all the columns in that row. But apparently, there is no way of processing a row with an unknown structure using cursors. So for now, I am wondering if this is possible using PIVOT/UNPIVOT. But then again, I think they also require the column list.
I am running SQL Server 2017.
How do I do transform the data with an unknown structure?
One possible approach is to generate a dynamic statement using information from INFORMATION_SCHEMA.COLUMNS:
-- Declarations
DECLARE #stm nvarchar(max)
-- Dynamic part
SELECT
#stm = STUFF((
SELECT CONCAT(
N' UNION ALL SELECT CustomerID, ''',
[COLUMN_NAME],
N''' AS [Attribute], CONVERT(nvarchar(max), ',
QUOTENAME([COLUMN_NAME]),
CASE
WHEN DATA_TYPE = 'datetime' THEN N', 121'
-- Add additional conversion rules for other data types
ELSE N''
END,
N') AS [Value]',
N' FROM [SampleSourceTable]'
)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE (TABLE_NAME = 'SampleSourceTable') AND (COLUMN_NAME <> 'CustomerId')
FOR XML PATH('')
), 1, 11, N'')
-- Whole statement and execution
SET #stm = #stm + N'ORDER BY CustomerID'
PRINT #stm
EXEC (#stm)
Output:
CustomerID Attribute Value
1 Column1 aaa
1 Column2 123
1 Column3 2019-01-01 00:00:00.000
2 Column3 2018-01-01 00:00:00.000
2 Column2 456
2 Column1 bbb
Ah, you opened a second question, I just placed an answer at your first...
So I will use this place to provide the same technique as my other answer, but without any need of dynamically created SQL. Try this out:
DECLARE #xml XML =(SELECT TOP 10 o.object_id,o.* FROM sys.objects o FOR XML RAW, ELEMENTS XSINIL);
SELECT r.value('*[1]/text()[1]','nvarchar(max)') AS RowID
,c.value('local-name(.)','nvarchar(max)') AS ColumnKey
,c.value('text()[1]','nvarchar(max)') AS ColumnValue
FROM #xml.nodes('/row') A(r)
CROSS APPLY A.r.nodes('*[position()>1]') B(c);
The first column of the set will be returned as RowID. If this is not correct, you can force this by doing the same as I've done above to force the o.object_id in the first place. All Columns of your result will be returned as EAV.
Part of the result
+-------+---------------------+-------------------------+
| RowID | ColumnKey | ColumnValue |
+-------+---------------------+-------------------------+
| 3 | name | sysrscols |
+-------+---------------------+-------------------------+
| 3 | object_id | 3 |
+-------+---------------------+-------------------------+
| 3 | principal_id | NULL |
+-------+---------------------+-------------------------+
| 3 | schema_id | 4 |
+-------+---------------------+-------------------------+
| 3 | parent_object_id | 0 |
+-------+---------------------+-------------------------+
| 3 | type | S |
+-------+---------------------+-------------------------+
| 3 | type_desc | SYSTEM_TABLE |
+-------+---------------------+-------------------------+
| 3 | create_date | 2017-08-22T19:38:02.860 |
+-------+---------------------+-------------------------+
| 3 | modify_date | 2017-08-22T19:38:02.867 |
+-------+---------------------+-------------------------+
| 3 | is_ms_shipped | 1 |
+-------+---------------------+-------------------------+
| 3 | is_published | 0 |
+-------+---------------------+-------------------------+
| 3 | is_schema_published | 0 |
+-------+---------------------+-------------------------+
| 5 | name | sysrowsets |
+-------+---------------------+-------------------------+
| ... more rows ...
I want separate one column into multiple columns based on condition.
Table : emp
CREATE TABLE [dbo].[emp]
(
[name] [varchar](200) NULL,
[id] [int] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[emp] ([name], [id])
VALUES (N'lux-pen-oxo-mobile', 1),
(N'pne-soap', 2),
(N'hop-pen-mobile-soap-jad', 3),
(N'pen-soap-box', 4)
Based on the above data I want output like below :
id |prod1 |prod2 |prod3 |prod4 | Prod5
1 |lux |pen |oxo |mobile |
2 |pne |soap | | |
3 |hop |pen |mobile |soap |jad
4 |pen |soap |box |
I tried like this:
select
id,
case
when charindex('-', name) > 0
then substring(name, 1, charindex('-', [name]) - 1)
end prod1,
substring(name, charindex('-', [name], 2) + 1, len(name)) prod2,
substring(name, charindex('-', [name], 3) + 1, len(name)) prod3,
substring(name, charindex('-', [name], 4) + 1, len(name)) prod4,
substring(name, charindex('-', [name], 5) + 1, len(name)) prod4
from
[emp]
This query not returning the expected result.
Please tell me how to write a query to achieve this task in SQL Server.
You can do it using CTE like following example.
;WITH Split_Names (ID,xmlname)
AS
(
SELECT ID,
CONVERT(XML,'<Names><name>'
+ REPLACE(Name,'-', '</name><name>') + '</name></Names>') AS xmlname
FROM [dbo].[emp]
)
SELECT ID,
xmlname.value('/Names[1]/name[1]','varchar(100)') AS prod1,
xmlname.value('/Names[1]/name[2]','varchar(100)') AS prod2,
xmlname.value('/Names[1]/name[3]','varchar(100)') AS prod3,
xmlname.value('/Names[1]/name[4]','varchar(100)') AS prod4,
xmlname.value('/Names[1]/name[5]','varchar(100)') AS prod5
FROM Split_Names
OUTPUT
+----+-------+-------+--------+--------+-------+
| ID | prod1 | prod2 | prod3 | prod4 | prod5 |
+----+-------+-------+--------+--------+-------+
| 1 | lux | pen | oxo | mobile | NULL |
+----+-------+-------+--------+--------+-------+
| 2 | pne | soap | NULL | NULL | NULL |
+----+-------+-------+--------+--------+-------+
| 3 | hop | pen | mobile | soap | jad |
+----+-------+-------+--------+--------+-------+
| 4 | pen | soap | box | NULL | NULL |
+----+-------+-------+--------+--------+-------+
If you want to replace NULL with '', in that case you can change the columns in select like following.
ISNULL(xmlname.value('/Names[1]/name[1]','varchar(100)'),'') AS prod1 ,
Live Demo