Want to create logic in SQL Server to create a derived column based on comparing two comma-separated columns.
Sample table data -
Create table ##table1 (ID INT Identity Primary Key, FulfillmentChannelStatus varchar(255),RoleAlternateSourcingChannel varchar (255))
insert into ##table1 values ('Filled,Open,In-process','Internal,Recruiter,Contractor')
,('Open,In-process,New','Contractor,Internal,Recruiter')
,('New,Filled','Contractor,Recruiter ')
,('Filled','Recruiter')
,('Open,New,Filled','Internal,Recruiter,Contractor')
,('Filled,Filled,Filled','Internal,Contractor,Recruiter')
,('Open ,Filled, In-proces','Contractor,Internal,Recruiter')
,('Filled','Others')
,('Cancelled,Filled','Contractor,Recruiter')
,('Cancelled, Filled, Cancel - In Process','Contractor,Recruiter,Internal')
Logic for new column--
--select * from ##tble
DECLARE #separator CHAR(1) = ','
SELECT
[Role Id],[RoleAlternateSourcingChannel],[FulfillmentChannelStatus] , [Filled fulfil] = x.value('(/root/r[sql:column("t.pos")]/text())[1]', 'VARCHAR(10)')
into ##temp FROM ##tble
CROSS APPLY (SELECT x = TRY_CAST('<root><r><![CDATA[' +
REPLACE([FulfillmentChannelStatus], #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)
.query('
for $x in /root/r[text()="Filled"][1]
return count(root/r[. << $x]) + 1
').value('text()[1]','INT')) AS t(pos)
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE([RoleAlternateSourcingChannel], #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t2(x)
Scenario: I have two comma-separated columns
1 . I need to calculate values for only "Filled" values (in column [Role Alternate Sourcing Channel])
2. In 1st first row- I have a Filled value for Internal Scheduling in column ([RoleAlternateSourcingChannel]) so in the output column – it will be Internal.
3. In 2nd row – I don’t have any Filled so the output will be Null.
4. in 3rd row - I have a Filled value for Recruiter so the output will be Recruiter.
And so on…
5.In Row 6 for all value is filled so the output will be a recruiter. because preference of Recruiter>Internal>Contractor
Other than Recruiter/Internal/ Contractor all filled values will be Null.
The position of Filled value is not fixed. It can be anywhere such as - either at 1st place/position or 2nd place or in 3rd place.
Expected Output -
|+----+------------------------+----------------------------+---------------+
| ID |FulfillmentChannelStatus|RoleAlternateSourcingChannel| Filled fulfil |
+----+------------------------+----------------------------+---------------+
| 1 | Filled,Open,In-process | Internal,Recruiter,Contractor | Internal |
| 2 | Open,In-process,New | Contractor,Internal,Recruiter | NULL |
| 3 | New,Filled | Contractor,Recruiter | Recruiter |
| 4 | Filled | Recruiter | Recruiter |
| 5 | Open,New,Filled | Internal,Recruiter,Contractor | Contractor |
| 6 | Filled,Filled,Filled | Internal,Contractor,Recruiter | Recruiter |
| 7 | Open ,Filled, In-process| Contractor,Internal,Recruiter | Internal |
| 8 | Filled | Others | Null
| 9 | Cancelled, Filled, Cancel - In Procecess|Contractor,Internal,Recruiter | Internal
| 10| Cancelled, Filled| Internal,Recruiter| Recruiter
+----+------------------------+-------+--------+----------------------------+
**Question:** I tried Query2, For all other cases it is working fine now but for Row 9 and 10 O/P is Null but it should be Internal and Recruiter respectively.
A minimal reproducible example ##1-4 is not provided.
Shooting from the hip.
Please try the following solution based on XQuery.
XML and XQuery data model is based on ordered sequences, exactly what we need.
You moved the goalposts in the middle of the game.
I made just the "Recruiter","Internal","Contractor" as a legitimate
values for the RoleAlternateSourcingChannel column. Everything
else is filtered out.
I don't see any easy way to handle the preference of
Recruiter>Internal>Contractor for the row #6.
SQL #1
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, FulfillmentChannelStatus VARCHAR(255), RoleAlternateSourcingChannel VARCHAR(255));
INSERT INTO #tbl (FulfillmentChannelStatus, RoleAlternateSourcingChannel) VALUES
('Filled,Open,In-process', 'Internal,Recruiter,Contractor'),
('Open,In-process,New', 'Contractor,Internal,Recruiter'),
('New,Filled', 'Contractor,Recruiter'),
('Filled', 'Recruiter'),
('Open,New,Filled', 'Internal,Recruiter,Contractor'),
('Filled,Filled,Filled', 'Internal,Contractor,Recruiter'),
('Open,Filled,In-process', 'Contractor,Internal,Recruiter'),
('Filled', 'Others');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = ',';
SELECT tbl.*
, Result = x.value('(/root/r[sql:column("t.pos")]/text())[1]', 'VARCHAR(10)')
FROM #tbl AS tbl
CROSS APPLY (SELECT x = TRY_CAST('<root><r><![CDATA[' +
REPLACE(FulfillmentChannelStatus, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)
.query('
if (count(/root/r[text()="Filled"]) eq 1) then
for $x in /root/r[text()="Filled"]
return count(root/r[. << $x]) + 1
else ()
').value('text()[1]','INT')) AS t(pos)
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(RoleAlternateSourcingChannel, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML).query('<root>
{
for $x in /root/r[text()=("Recruiter","Internal","Contractor")]
return $x
}
</root>
')) AS t2(x);
Output
+----+--------------------------+-------------------------------+------------+
| ID | FulfillmentChannelStatus | RoleAlternateSourcingChannel | Result |
+----+--------------------------+-------------------------------+------------+
| 1 | Filled,Open,In-process | Internal,Recruiter,Contractor | Internal |
| 2 | Open,In-process,New | Contractor,Internal,Recruiter | NULL |
| 3 | New,Filled | Contractor,Recruiter | Recruiter |
| 4 | Filled | Recruiter | Recruiter |
| 5 | Open,New,Filled | Internal,Recruiter,Contractor | Contractor |
| 6 | Filled,Filled,Filled | Internal,Contractor,Recruiter | NULL |
| 7 | Open,Filled,In-process | Contractor,Internal,Recruiter | Internal |
| 8 | Filled | Others | NULL |
+----+--------------------------+-------------------------------+------------+
SQL #2
DB fiddle
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, FulfillmentChannelStatus VARCHAR(255), RoleAlternateSourcingChannel VARCHAR(255));
INSERT INTO #tbl (FulfillmentChannelStatus, RoleAlternateSourcingChannel) VALUES
('Filled,Open,In-process', 'Internal,Recruiter,Contractor'),
('Open,In-process,New', 'Contractor,Internal,Recruiter'),
('New,Filled', 'Contractor,Recruiter'),
('Filled', 'Recruiter'),
('Open,New,Filled', 'Internal,Recruiter,Contractor'),
('Filled,Filled,Filled', 'Internal,Contractor,Recruiter'),
('Open,Filled,In-process', 'Contractor,Internal,Recruiter'),
('Filled', 'Others'),
('Cancelled,Filled','Contractor,Recruiter'),
('Cancelled, Filled, Cancel - In Process','Contractor,Recruiter,Internal');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = ',';
;WITH rs AS
(
SELECT ID, x
FROM #tbl
CROSS APPLY (SELECT TRY_CAST('<root>' +
'<source><r><![CDATA[' + REPLACE(REPLACE(FulfillmentChannelStatus,SPACE(1),''), #separator, ']]></r><r><![CDATA[') +
']]></r></source>' +
'<target><r><![CDATA[' + REPLACE(REPLACE(RoleAlternateSourcingChannel,SPACE(1),''), #separator, ']]></r><r><![CDATA[') +
']]></r></target>' +
'</root>' AS XML).query('<root>
{
for $x in /root/source/r
let $pos := count(root/source/r[. << $x]) + 1
return <r>
<s>{data($x)}</s><t>{data(/root/target/r[$pos])}</t>
</r>
}
</root>')) AS t(x)
), cte AS
(
SELECT ID
, c.value('(s/text())[1]', 'VARCHAR(30)') AS source
, c.value('(t/text())[1]', 'VARCHAR(30)') AS [target]
FROM rs
CROSS APPLY x.nodes('/root/r') AS t(c)
), cte2 AS
(
SELECT *
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY
CASE [target]
WHEN 'Recruiter' THEN 1
WHEN 'Internal' THEN 2
WHEN 'Contractor' THEN 3
END) AS seq
FROM cte
WHERE source = 'Filled'
AND [target] IN ('Recruiter','Internal','Contractor')
)
SELECT t.*
, c.[target] --, c.seq
FROM #tbl AS t
LEFT OUTER JOIN cte2 AS c ON c.ID = t.ID
WHERE c.seq = 1 OR c.seq is NULL
ORDER BY t.ID;
Output
+----+----------------------------------------+-------------------------------+------------+
| ID | FulfillmentChannelStatus | RoleAlternateSourcingChannel | target |
+----+----------------------------------------+-------------------------------+------------+
| 1 | Filled,Open,In-process | Internal,Recruiter,Contractor | Internal |
| 2 | Open,In-process,New | Contractor,Internal,Recruiter | NULL |
| 3 | New,Filled | Contractor,Recruiter | Recruiter |
| 4 | Filled | Recruiter | Recruiter |
| 5 | Open,New,Filled | Internal,Recruiter,Contractor | Contractor |
| 6 | Filled,Filled,Filled | Internal,Contractor,Recruiter | Recruiter |
| 7 | Open,Filled,In-process | Contractor,Internal,Recruiter | Internal |
| 8 | Filled | Others | NULL |
| 9 | Cancelled,Filled | Contractor,Recruiter | Recruiter |
| 10 | Cancelled, Filled, Cancel - In Process | Contractor,Recruiter,Internal | Recruiter |
+----+----------------------------------------+-------------------------------+------------+
I'm trying to split this field into columns separating at the '/' character.
The string contains the Company Name/Location/Cost Center/Department/Job
Here is a sample:
JSM MFG/Stearns Blg/Operations/Shipping/Packer
JSM MFG/Birch Lane Blg/Maintenance/Electrical/Electrician II
The desired output is:
JSM MFG, Stearns Blg, Operations, Shipping, Packer
into their respective separate columns.
This would be combined with other columns, too, employee number, rate of pay etc.. which are just direct selects.
I have found a few different SQL excerpts but nothing that I could see that covers multiple delimiters with variable length in one string.
Thanks in advance,
Doug
Here's a solution that maybe you can build on. It uses the SQL Server 2016+ string_split() table value function to split the string into fields, which are then mapped into columns on a table you want to update.
declare #empId int = 123456 -- Let's assume a table Employee you want to update.
-- You get the values you want to split from somewhere.
declare #example varchar(max) = 'JSM MFG/Stearns Blg/Operations/Shipping/Packer'
-- SQL Server 2016+ has a handy string_split() table function we can use.
-- We'll dump the split values into a temp table.
select value into #tmp from string_split(#example, '/')
-- We now retroactively add an identity column to the temp table,
-- so we can tell which positions the values map to.
alter table #tmp add i int identity(1,1)
-- And now, a cursor to iterate over the split values.
declare #i int
declare #value varchar(max)
declare split_cursor cursor for
select i, value from #tmp
open split_cursor
fetch next from split_cursor into #i, #value
while ##fetch_status = 0
begin
-- We know which values correspond to which positions in the Employee table,
-- thanks to the identity integer we added to the temp table above.
if (#i = 1)
begin
update Employee set Employer = #value where EmpId = #empId
end
else if (#i = 2)
begin
update Employee set Building = #value where EmpId = #empId
end
-- ETC...
fetch next from split_cursor into #i, #value
end
close split_cursor
deallocate split_cursor
There's more than 1 way to do this.
Here's a demonstration of some:
create table Test (
id int identity(1,1) primary key,
col nvarchar(100)
);
GO
✓
insert into Test (col) values
('abc/def/ghi/jkl/mno'),
('s/t/u/f/f'),
('w/h/y'),
(null);
GO
4 rows affected
SELECT id,
s1 AS [CompanyName],
s2 AS [Location],
s3 AS [Cost Center],
s4 AS [Department],
s5 AS [Job]
FROM Test t
OUTER APPLY
(
SELECT s1, s2, s3, s4, s5
FROM (VALUES (col+'/')) q(s0)
CROSS APPLY (select case when charindex('/',s0)>0 then left(s0, charindex('/',s0)-1) end s1, case when charindex('/',s0)>0 then charindex('/',s0) end p1) a1
CROSS APPLY (select case when p1>0 and charindex('/',s0,p1+1)>0 then substring(s0, p1+1, charindex('/',s0,p1+1)-p1-1) end s2, case when p1>0 then charindex('/',s0,p1+1) end p2) a2
CROSS APPLY (select case when p2>0 and charindex('/',s0,p2+1)>0 then substring(s0, p2+1, charindex('/',s0,p2+1)-p2-1) end s3, case when p2>0 then charindex('/',s0,p2+1) end p3) a3
CROSS APPLY (select case when p3>0 and charindex('/',s0,p3+1)>0 then substring(s0, p3+1, charindex('/',s0,p3+1)-p3-1) end s4, case when p3>0 then charindex('/',s0,p3+1) end p4) a4
CROSS APPLY (select case when p4>0 and charindex('/',s0,p4+1)>0 then substring(s0, p4+1, charindex('/',s0,p4+1)-p4-1) end s5) a5
) a;
GO
id | CompanyName | Location | Cost Center | Department | Job
-: | :---------- | :------- | :---------- | :--------- | :---
1 | abc | def | ghi | jkl | mno
2 | s | t | u | f | f
3 | w | h | y | null | null
4 | null | null | null | null | null
select id,
[1] AS [CompanyName],
[2] AS [Location],
[3] AS [Cost Center],
[4] AS [Department],
[5] AS [Job]
from Test t
outer apply (
select *
from
(
select value
, row_number() over (order by (select 0)) n
from string_split(t.col,'/') s
) src
pivot (
max(value)
for n in ([1],[2],[3],[4],[5])
) pvt
) a;
GO
id | CompanyName | Location | Cost Center | Department | Job
-: | :---------- | :------- | :---------- | :--------- | :---
1 | abc | def | ghi | jkl | mno
2 | s | t | u | f | f
3 | w | h | y | null | null
4 | null | null | null | null | null
select id,
s1 AS [CompanyName],
s2 AS [Location],
s3 AS [Cost Center],
s4 AS [Department],
s5 AS [Job]
from Test t
outer apply (
select
s1 = x0.value('/x[1]','nvarchar(max)')
, s2 = x0.value('/x[2]','nvarchar(max)')
, s3 = x0.value('/x[3]','nvarchar(max)')
, s4 = x0.value('/x[4]','nvarchar(max)')
, s5 = x0.value('/x[5]','nvarchar(max)')
from
(
select
cast(('<x>'+ replace(col,'/','</x><x>') +'</x>') as xml) x0
) q
) a;
GO
id | CompanyName | Location | Cost Center | Department | Job
-: | :---------- | :------- | :---------- | :--------- | :---
1 | abc | def | ghi | jkl | mno
2 | s | t | u | f | f
3 | w | h | y | null | null
4 | null | null | null | null | null
db<>fiddle here
I am looking for some advice or pointers on how to construct this. I have spent the last year self-learning SQL. I am at work and I only have access to the query interface in report builder. Which for me means, no procedures, no create tables and no IDE :(. So thats the limitations!
I am trying to reconstruct account balances. I have no intervening balances. I have the current balance and a table full of the transaction history
My current approach is to sum the transactions by posting week (Which I have done) in my CTE named
[SUMTRANSREF]
+--------------+------------+-----------+
| TNCY-SYS-REF | POSTING-WK | SUM-TRANS |
+--------------+------------+-----------+
| 1 | 47 | 37.95 |
| 1 | 46 | 37.95 |
| 1 | 45 | 37.95 |
| 2 | 47 | 50.00 |
| 2 | 46 | 25.00 |
| 2 | 45 | 25.00 |
+--------------+------------+-----------+
I then get the current balances in another CTE called
[CBAL]
+--------------+-------------+-----------+
| TNCY-SYS-REF | CUR-BALANCE | CURR-WEEK |
+--------------+-------------+-----------+
| 1 | 27.52 | 47 |
| 1 | 52.00 | 47 |
+--------------+-------------+-----------+
Now I am assuming I could create intervening CTEs to sum and then splice those altogether but is there a smarter (more automated) way?
Ideally my result should be
+--------------+-------------+----------+----------+
| TNCY-SYS-REF | CUR-BALANCE | BAL-WK46 | BAL-Wk45 |
+--------------+-------------+----------+----------+
| 1 | 27.52 | -10.43 | -48.38 |
| 2 | 52.00 | 2.00 | -48.00 |
+--------------+-------------+----------+----------+
I just am uncertain because each column requires the sum of intervening transactions
So BAL-WK46 is (CURR-BALANCE) - SUM(Transactions from 47)
So BAL-WK46 is (CURR-BALANCE) - SUM(Transactions 46+47)
So BAL-WK45 is (CURR-BALANCE) - SUM(Transactions 45+46+47)
and so on.
Normally I have an idea where to start but I am flummoxed by this one.
Any help you can give would be appreciated. Thank you
Here is some T-SQL that gets the result you require. Should be easy enough to play with to get what you want.
It makes use of Recursive CTE and a PIVOT
IF OBJECT_ID('Tempdb..#SUMTRANSREF') IS NOT NULL
DROP TABLE #SUMTRANSREF
IF OBJECT_ID('Tempdb..#CBAL') IS NOT NULL
DROP TABLE #CBAL
IF OBJECT_ID('Tempdb..#TEMP') IS NOT NULL
DROP TABLE #TEMP
CREATE TABLE #SUMTRANSREF
(
[TNCY-SYS-REF] int,
[POSTING-WK] int,
[SUM-TRANS] float
)
CREATE TABLE #CBAL
(
[TNCY-SYS-REF] int ,
[CUR-BALANCE] float , [CURR-WEEK] int
)
INSERT INTO #SUMTRANSREF
VALUES (1 ,47 , 37.95),
(1 ,46 , 37.95),
(1 ,45 , 37.95),
(2 ,47 , 50.00),
(2 ,46 , 25.00),
(2 ,45 , 25.00 )
INSERT INTO #CBAL
VALUES (1,27.52,47),(2,52.00,47);
WITH CBAL AS
(SELECT * FROM #CBAL),
SUMTRANSREF AS(SELECT * FROM #SUMTRANSREF),
RecursiveTotals([TNCY-SYS-REF],[CURR-WEEK],[CUR-BALANCE],RunningBalance)
AS
(
select C.[TNCY-SYS-REF], C.[CURR-WEEK],C.[CUR-BALANCE],C.[CUR-BALANCE] + S.RunningTotal RunningBalance from CBAL C
JOIN (select *,-SUM([SUM-TRANS]) OVER (PARTITION BY [TNCY-SYS-REF] ORDER BY [POSTING-WK] DESC) RunningTotal
from SUMTRANSREF) S
ON C.[CURR-WEEK]=S.[POSTING-WK] AND C.[TNCY-SYS-REF]=S.[TNCY-SYS-REF]
UNION ALL
select RT.[TNCY-SYS-REF], RT.[CURR-WEEK] -1 [CURR_WEEK],RT.[CUR-BALANCE],RT.[CUR-BALANCE] + S.RunningTotal RunningBalance FROM RecursiveTotals RT
JOIN (select *,-SUM([SUM-TRANS]) OVER (PARTITION BY [TNCY-SYS-REF] ORDER BY [POSTING-WK] DESC) RunningTotal
from #SUMTRANSREF) S ON RT.[TNCY-SYS-REF] = S.[TNCY-SYS-REF] AND RT.[CURR-WEEK]-1 = S.[POSTING-WK]
)
select [TNCY-SYS-REF],[CUR-BALANCE],[46] as 'BAL-WK46',[45] as 'BAL-WK45',[44] as 'BAL-WK44'
FROM (
select [TNCY-SYS-REF],[CUR-BALANCE],RunningBalance,BalanceWeek from (SELECT *,R.[CURR-WEEK]-1 'BalanceWeek' FROm RecursiveTotals R
) RT) AS SOURCETABLE
PIVOT
(
AVG(RunningBalance)
FOR BalanceWeek in ([46],[45],[44])
) as PVT
I have data in one column as below semi colon delimited. I want to parse this in separate rows.
9000389003; 9000389000; 9000389093; 9000383895; 9000490984; 9000389000
9000490980; 9000593580; 9000593599; 9000498085; 9000389003; 9000593580; 9000490990
9000489405; 9000435334; 9000535090; 9000995990
In SQL Server 2016+ you can use string_split().
In SQL Server pre-2016, using a CSV Splitter table valued function by Jeff Moden:
create table t (id int not null identity(1,1), str varchar(8000));
insert into t values
('9000389003; 9000389000; 9000389093; 9000383895; 9000490984; 9000389000')
,('9000490980; 9000593580; 9000593599; 9000498085; 9000389003; 9000593580; 9000490990')
,('9000489405; 9000435334; 9000535090; 9000995990');
select
t.id
, s.ItemNumber
, Item = ltrim(s.Item)
from t
cross apply [dbo].[delimitedsplit8K](t.str,';') as s
rextester demo: http://rextester.com/AVQL94047
returns:
+----+------------+------------+
| id | ItemNumber | Item |
+----+------------+------------+
| 1 | 1 | 9000389003 |
| 1 | 2 | 9000389000 |
| 1 | 3 | 9000389093 |
| 1 | 4 | 9000383895 |
| 1 | 5 | 9000490984 |
| 1 | 6 | 9000389000 |
| 2 | 1 | 9000490980 |
| 2 | 2 | 9000593580 |
| 2 | 3 | 9000593599 |
| 2 | 4 | 9000498085 |
| 2 | 5 | 9000389003 |
| 2 | 6 | 9000593580 |
| 2 | 7 | 9000490990 |
| 3 | 1 | 9000489405 |
| 3 | 2 | 9000435334 |
| 3 | 3 | 9000535090 |
| 3 | 4 | 9000995990 |
+----+------------+------------+
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand
If you cannot use SQL-Server 2016 for string_split() or if you are not allowed to create a new function, or if you just want a simpe ad-hoc approach you might do it this way:
DECLARE #tbl TABLE(id int not null identity, YourString varchar(1000));
INSERT INTO #tbl VALUES
('9000389003; 9000389000; 9000389093; 9000383895; 9000490984; 9000389000')
,('9000490980; 9000593580; 9000593599; 9000498085; 9000389003; 9000593580; 9000490990')
,('9000489405; 9000435334; 9000535090; 9000995990');
WITH Casted AS
(
SELECT t.id
,CAST('<x>' + REPLACE(YourString,';','</x><x>') + '</x>' AS XML) AsXml
FROM #tbl AS t
)
SELECT id
,nodes.value('(./text())[1]','bigint') AS TheNumber
FROM Casted
CROSS APPLY Casted.AsXml.nodes('/x') AS The(nodes)
One advantage might be - other than usual string splitter approaches - that you get the values typed (as bigint in this case).
I have a table named VPX_EVENT_ARG where a column, ARG_DATA, contains XML values.
Table 1
+----------+-------------------+----------+
| EVENT_ID | ARG_TYPE | ARG_DATA |
+----------+-------------------+----------+
| 7121001 | vim.vm.ConfigSpec | XML1 |
| 7121002 | vim.vm.ConfigSpec | XML2 |
| 7121003 | vim.vm.ConfigSpec | XML3 |
+----------+-------------------+----------+
XML1, XML2 and XML3 are XML values. They are too long to type in the table. Here are the real values. Actually they appear in one line.
XML1, for example,
<obj xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:vim25" versionId="5.5" xsi:type="VirtualMachineConfigSpec"><changeVersion>2015-09-24T10:02:53.866694Z</changeVersion><files><vmPathName>ds:///vmfs/volumes/54e5d10c-c527b7f3-7eea-a0d3c1f01404/CommVault VM Test/CommVault VM Test.vmx</vmPathName></files><deviceChange><operation>remove</operation><device xsi:type="VirtualDisk"><key>2003</key><deviceInfo><label>Hard disk 4</label><summary>20,971,520 KB </summary></deviceInfo><backing xsi:type="VirtualDiskFlatVer2BackingInfo"><fileName>ds:///vmfs/volumes/54e5d10c-c527b7f3-7eea-a0d3c1f01404/CommVault VM Test/CommVault VM Test_2.vmdk</fileName><diskMode>persistent</diskMode><split>false</split><writeThrough>false</writeThrough><thinProvisioned>false</thinProvisioned><uuid>6000C29b-e652-b5fe-76fa-18f6de988807</uuid><contentId>5bd085f0f9391346751e1e7efffffffe</contentId><digestEnabled>false</digestEnabled></backing><controllerKey>1000</controllerKey><unitNumber>3</unitNumber><capacityInKB>20971520</capacityInKB><shares><shares>1000</shares><level>normal</level></shares><storageIOAllocation><limit>-1</limit><shares><shares>1000</shares><level>normal</level></shares></storageIOAllocation></device></deviceChange></obj>
I will separate XML1 into multiple lines so that it will be easier to read.
<obj xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:vim25" versionId="5.5" xsi:type="VirtualMachineConfigSpec"><changeVersion>2015-09-24T10:02:53.866694Z</changeVersion><files><vmPathName>ds:///vmfs/volumes/54e5d10c-c527b7f3-7eea-a0d3c1f01404/CommVault VM Test/CommVault VM Test.vmx</vmPathName></files>
<deviceChange>
<operation>remove</operation>
<device xsi:type="VirtualDisk">
<key>2003</key>
<deviceInfo>
<label>Hard disk 4</label>
<summary>20,971,520 KB </summary>
</deviceInfo>
<backing xsi:type="VirtualDiskFlatVer2BackingInfo">
<fileName>ds:///vmfs/volumes/54e5d10c-c527b7f3-7eea-a0d3c1f01404/CommVault VM Test/CommVault VM Test_2.vmdk
</fileName>
<diskMode>persistent</diskMode>
<split>false</split>
<writeThrough>false</writeThrough>
<thinProvisioned>false</thinProvisioned>
<uuid>6000C29b-e652-b5fe-76fa-18f6de988807</uuid>
<contentId>5bd085f0f9391346751e1e7efffffffe</contentId>
<digestEnabled>false</digestEnabled>
</backing>
<controllerKey>1000</controllerKey>
<unitNumber>3</unitNumber>
<capacityInKB>20971520</capacityInKB>
<shares><shares>1000</shares><level>normal</level></shares><storageIOAllocation><limit>-1</limit><shares><shares>1000</shares><level>normal</level></shares></storageIOAllocation>
</device>
</deviceChange>
</obj>
I would like to extract XML1 into tables using an MSSQL query.
DECLARE #xml XML
SET #xml = (SELECT ARG_DATA FROM VPX_EVENT_ARG WHERE ARG_ID = 1 AND EVENT_ID = 7121001); --EVENT_ID is fixed.
WITH XMLNAMESPACES('urn:vim25' AS NS)
SELECT
'diskUnit' = ref.value('./NS:device[1]/NS:unitNumber[1]', 'INT'),
'operation' = ref.value('./NS:operation[1]', 'NVARCHAR(100)'),
'newSizeKB' = ref.value('./NS:device[1]/NS:capacityInKB[1]', 'BIGINT')
FROM #xml.nodes('/NS:obj/NS:deviceChange') data(ref)
Well, I get this as a result.
Table 2
+------+-----------+-----------+
| unit | operation | newSizeKB |
+------+-----------+-----------+
| 1 | edit | 24117248 |
| 2 | edit | 108003328 |
| 3 | add | 20971520 |
+------+-----------+-----------+
You can see that Table 2 is just the result of the first row in Table 1. Not even the first row, it is just ARG_DATA on the first row which fixes EVENT_ID as well. I wish anyone can help.
Question 1: I would like to consolidate multiple rows of XML values into one table without fixing EVENT_IDs and put EVENT_IDs into a column too. Please assume that VPX_EVENT_ARG table contains hundreds of rows.
Table3
+----------+------+-----------+-----------+
| EVENT_ID | unit | operation | newSizeKB |
+----------+------+-----------+-----------+
| 7121001 | 1 | edit | 24117248 |
| 7121001 | 2 | edit | 108003328 |
| 7121001 | 3 | add | 20971520 |
| 7121002 | 1 | edit | 1048576 |
| 7121002 | 3 | edit | 52428800 |
| 7121003 | 3 | edit | 125829120 |
| 7121003 | 5 | remove | 83886080 |
+----------+------+-----------+-----------+
Question 2: Is there a way to use the query without setting XML? I need to set XML for the query, then I can use nodes().
SET #xml = (SELECT ARG_DATA FROM VPX_EVENT_ARG WHERE ARG_ID = 1 AND EVENT_ID = 7121001);
.
.
FROM #xml.nodes('/NS:obj/NS:deviceChange') data(ref)
I wonder if it can be done like this.
FROM (SELECT ARG_DATA FROM VPX_EVENT_ARG WHERE ARG_ID = 1 AND EVENT_ID = 7121001).nodes('/NS:obj/NS:deviceChange') data(ref)
Error: Incorrect syntax near '.'. Expecting AS, ID, or QUOTED_ID.
I really want to use such a result as Table3 to join the table I have queried before. I have hard time dealing with XML but stuck at this so long. By the way, I cannot update the table; it is restricted.
Of couse, you can. Use cross apply on your nodes:
;WITH XMLNAMESPACES('urn:vim25' AS NS)
SELECT
v.EVENT_ID,
'diskUnit' = ref.value('./NS:device[1]/NS:unitNumber[1]', 'INT'),
'operation' = ref.value('./NS:operation[1]', 'NVARCHAR(100)'),
'newSizeKB' = ref.value('./NS:device[1]/NS:capacityInKB[1]', 'BIGINT')
FROM VPX_EVENT_ARG AS v
CROSS APPLY v.ARG_DATA.nodes('/NS:obj/NS:deviceChange') data(ref)
If your column is not XML, we need a subquery to convert it:
;WITH XMLNAMESPACES('urn:vim25' AS NS)
SELECT
v.EVENT_ID,
'diskUnit' = ref.value('./NS:device[1]/NS:unitNumber[1]', 'INT'),
'operation' = ref.value('./NS:operation[1]', 'NVARCHAR(100)'),
'newSizeKB' = ref.value('./NS:device[1]/NS:capacityInKB[1]', 'BIGINT')
FROM
(
SELECT
c.EVENT_ID,
c.ARG_TYPE,
CONVERT(xml,c.ARG_DATA) AS ARG_DATA,
c.ARG_ID
FROM VPX_EVENT_ARG AS c
) AS v
CROSS APPLY v.ARG_DATA.nodes('/NS:obj/NS:deviceChange') data(ref)