How to parse XML ordred list into table view - sql

How to parse XML ordered list like this
<ol>
<li>value1</li>
<li>value2</li>
<li>value3</li>
</ol>
into table like this (like it's visible in html):
Nr Value
----------- ------
1 value1
2 value2
3 value3
Here is the code for XML string:
declare #ol XML= '<ol><li>'+REPLACE('value1,value2,value3', ',', '</li><li>')+'</li></ol>'
select #ol
NB! Is it possible to parse "numbering" from XML without creating something like identity column?
Small update:
Following solutions provide right answer for simple example above:
Yitzhak Khabinsky, Salman A
akhilesh singh
But is it possible to get solution for this more tricky example:
DECLARE #ol XML
SET #ol=
'<ol type="i" start="3">
<li>value1</li>
<li>value2</li>
<li>value3</li>
</ol>';
Estimated result:
Nr Value
---- -------
iii value1
iv value2
v value3
?

Finding the nodes is easy, finding their relative position is tricky. Here is one solution by using what is called Node Comparison operation via << operator and count function:
DECLARE #ol XML = '<ol>
<li>value1</li>
<li>value2</li>
<li>value3</li>
</ol>';
SELECT li.value('.', 'NVARCHAR(100)') AS value
, li.value('let $n := . return count(../*[. << $n]) + 1', 'int') AS pos
FROM #ol.nodes('/ol/li') AS x(li)

DECLARE #ol XML
SET #ol= '<ol>
<li>value1</li>
<li>value2</li>
<li>value3</li>
</ol>';
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS Nr,
li.value('.', 'NVARCHAR(100)') AS Value
FROM #ol.nodes('/ol/li') AS x(li)
Try this Simple Query...here...Getting Row Number ()...so we get Nr as Number...1,2,3..so on...and xml value into table....
You will get this type of Output...
Output :
Nr Value
----------- ------
1 value1
2 value2
3 value3
After Edit Your Question...the Solution Should be Like this.....
DECLARE #ol XML
SET #ol=
'<ol type="i" start="3">
<li>value1</li>
<li>value2</li>
<li>value3</li>
</ol>';
SELECT 2 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS Nr,
li.value('.', 'NVARCHAR(100)') AS Value
FROM #ol.nodes('/ol/li') AS x(li)
Output :
Nr Value
----------- ------
3 value1
4 value2
5 value3
here adding +2 value to RowNumber() so....we can get the value 2,3,4...and so on..

Related

XQuery SQL XML values

We have to find a query parsing XML to have a result like this one:
COL_ACTION N COL_NAME_I COL_VALUE_AFTER
---------- ----------- ---------------- -------------------
INS 1 N 1
INS 1 TST_ID 28
INS 1 TST_DATA data2
INS 2 N 2
INS 2 TST_ID 27
INS 2 TST_DATA data1
The value of column N depend on the value of the first "row" (COL_NAME_I = N) of the XML dataset
The XML contains:
DECLARE #XML XML =
N'
<DATASET>
<XML_INS>
<IROW xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<N>1</N>
<TST_ID>28</TST_ID>
<TST_DATA>data2</TST_DATA>
</IROW>
<IROW xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<N>2</N>
<TST_ID>27</TST_ID>
<TST_DATA>data1</TST_DATA>
</IROW>
</XML_INS>
</DATASET>
';
The query I was able to do is:
SELECT RIGHT(ca.value('local-name(.)','CHAR(7)'), 3) AS COL_ACTION,
x.value('(//N)[1]', 'int') AS N,
-- cn.value('./text()[1]','int') AS NI,
ci.value('local-name(.)','sysname') AS COL_NAME_I,
ci.value('./text()[1]','nvarchar(max)') AS COL_VALUE_AFTER
FROM #XML.nodes('.') AS T(x)
OUTER APPLY #XML.nodes('DATASET/*') AS TA(ca)
OUTER APPLY #XML.nodes('DATASET/XML_INS/IROW/N/*') AS TN(cn)
OUTER APPLY #XML.nodes('DATASET/XML_INS/IROW/*') AS TI(ci);
The problem remains on the "N" column that does no have the expected value:
COL_ACTION N COL_NAME_I COL_VALUE_AFTER
---------- ----------- ---------------- -------------------
INS 1 N 1
INS 1 TST_ID 28
INS 1 TST_DATA data2
INS 1 N 2
INS 1 TST_ID 27
INS 1 TST_DATA data1
I tryed different approach, as seen in SQL comment, but does not produce the good result...
You need each .nodes to refer to the previous, in order to only bring back its children. Otherwise you get every descendant node for the whole document.
SELECT RIGHT(action.value('local-name(.)','CHAR(7)'), 3) AS COL_ACTION,
irow.value('(N/text())[1]', 'int') AS N,
ci.value('local-name(.)','sysname') AS COL_NAME_I,
ci.value('text()[1]','nvarchar(max)') AS COL_VALUE_AFTER
FROM #XML.nodes('DATASET/*') AS x1(action)
OUTER APPLY x1.action.nodes('IROW') AS x2(irow)
OUTER APPLY x2.irow.nodes('*') AS TI(ci);
db<>fiddle
This should do it:
SELECT
irow.value('local-name(..)', 'NVARCHAR(100)') AS COL_ACTION,
irow.value('(./N)[1]', 'NVARCHAR(100)') AS N,
child.value('local-name(.)', 'NVARCHAR(100)') AS COL_NAME_I,
child.value('.', 'NVARCHAR(100)') AS COL_VALUE_AFTER
FROM #XML.nodes('/DATASET/*/IROW') AS n1(irow)
CROSS APPLY irow.nodes('./*') AS n2(child)
i am a new contributer so sorry if i got something wrong.
I used this approach on my work : Link
My sample working code :
CREATE TABLE #FieldPermissionsTable
(
TE_ID VARCHAR(MAX),
Value VARCHAR(MAX),
Value2 VARCHAR(MAX),
NAVI_USER VARCHAR(MAX)
)
--SET #FieldPermissionAccessXML = '<Row_ID><Elements ID="1" Value="1" Value2="NULLnROLLA" Navi_User="testuser" /><Elements ID="3" Value="tespit" Value2="NULLnROLLA" Navi_User="testuser" /><Elements ID="6" Value="aciklama" Value2="NULLnROLLA" Navi_User="testuser" /></Row_ID>'
-- SELECT #FieldPermissionAccessXML = [dbo].[to_xml_replace] (#FieldPermissionAccessXML)
EXEC sp_xml_preparedocument #XmlHandle output,#FieldPermissionAccessXML
--'<FieldPermissions>
--<FieldPermission FieldName="" RoleID="1" Access="1" />
--<FieldPermission FieldName="" RoleID="2" Access="1" />'
--select * from JMP_FieldPermissions
INSERT INTO #FieldPermissionsTable
SELECT *--ID,Value, Value2, Navi_User
FROM OPENXML (#XmlHandle, './Row_ID/Elements')
WITH (TE_ID VARCHAR(MAX) '#ID',
Value VARCHAR(MAX) '#Value',
Value2 VARCHAR(MAX) '#Value2',
NAVI_USER VARCHAR(MAX) '#Navi_User'
)

Get column names from a table matching a specific value

I have a table named TableA which has many rows. Sample structure given below
CID int, Col1 int, Col2 int, Col3 int, Col4 int
when I run a query (say when CID=5) i will get only one row with Col1,Col2 etc having different values.
I want to get the digit of column name where the row value is -1.
For more clarity
CID, Col1, Col2 , Col3 , Col4
5 0 -1 0 -1
in this example i should get result as
MyRes
2
4
Is there a any way to achieve it
You may use unpivot then filter your record on basis of result value which is -1 in your case.
; with cte as (
select CID, result, col from
(
select * from table
) as t
unpivot
(
result for col in ( Col1, col2, col3, col4 )
) as p
)
select CId, col from cte where result = -1
Just a bit of homework for you to get the number part from the column name.
In case you find any problem in that part please comment I'll do that also but lets give a try first.
I got the answer
declare #ID int
set #ID = 1
select substring( T1.ColName, 2, LEN(T1.ColName)) as MyRes from (
select
Col.value('local-name(.)', 'varchar(10)') as ColName
from (select *
from TableA
for xml path(''), type) as T(XMLCol)
cross apply
T.XMLCol.nodes('*') as n(Col)
where Col.value('.', 'varchar(10)') = '-1'
)T1

Replacing unwanted portions of a string value in SQL column

I have a table in the following format where COL1 contains a unique identifier and COL2 contains a collection of phone numbers followed by a tag (<abc> or <def>) and delimited by pipe (|). Number of phone records in each row is unknown - it may contain just one phone number followed by tag or upto 10.
Table
----------
COL1 : COL2
----------
ID1 : 1234567890<abc>|4312314124<abc>|1232345133<def>|4131234131<abc>|41234134132<def>
I need to copy this data into a new table with the result in following format i.e. remove all portion of the string with the tag <def>.
Table
----------
COL1 : COL2
----------
ID1 : 1234567890<abc>,4312314124<abc>,4131234131<abc>
What are the best ways to do this to get optimum performance? I need the program to transform data in a table that contains about a million records.
If performance is important then I would suggest delimitedSplit8k_Lead. You can just use the pipe as a delimiter to split the string then exclude items (tokens) that don't end with .
DECLARE #table TABLE (COL1 VARCHAR(10), COL2 VARCHAR(1000));
INSERT #table
VALUES
('ID1','1234567890<abc>|4312314124<abc>|1232345133<def>|4131234131<abc>|41234134132<def>'),
('ID2','2662314129<abc>|7868845133<abc>|6831234131<abc>|41234139999<xxx>|1234567999<abc>')
SELECT t.COL1, ds.item
FROM #table t
CROSS APPLY dbo.DelimitedSplit8K_LEAD(t.COL2,'|') ds
WHERE ds.Item LIKE '%<abc>';
Returns
COL1 item
---------- -----------------
ID1 1234567890<abc>
ID1 4312314124<abc>
ID1 4131234131<abc>
ID2 2662314129<abc>
ID2 7868845133<abc>
ID2 6831234131<abc>
ID2 1234567999<abc>
Then you use XML PATH for concatenation like this:
DECLARE #table TABLE (COL1 VARCHAR(10), COL2 VARCHAR(1000));
INSERT #table
VALUES
('ID1','1234567890<abc>|4312314124<abc>|1232345133<def>|4131234131<abc>|41234134132<def>'),
('ID2','2662314129<abc>|7868845133<abc>|6831234131<abc>|41234139999<xxx>|1234567999<abc>')
SELECT t.COL1, stripBadNumbers.newString
FROM #table t
CROSS APPLY
(VALUES((
SELECT ds.item
FROM dbo.DelimitedSplit8K_LEAD(t.COL2,'|') ds
WHERE ds.Item LIKE '%<abc>'
FOR XML PATH(''), TYPE
).value('.', 'varchar(1000)'))) stripBadNumbers(newString);
Returns:
COL1 newString
---------- -------------------------------------------------------------------
ID1 1234567890<abc>4312314124<abc>4131234131<abc>
ID2 2662314129<abc>7868845133<abc>6831234131<abc>1234567999<abc>
That string of yours can easily be transformed into some XML basically using replace(). The phone numbers with the right tag can then be selected using XQuery. As a bonus this might work with an arbitrary number of phone numbers.
(I don't get your schema, so I use my own. Translate it into yours yourself.)
CREATE TABLE elbat
(nmuloc nvarchar(MAX));
INSERT INTO elbat
(nmuloc)
VALUES ('1234567890<abc>|4312314124<abc>|1232345133<def>|4131234131<abc>|41234134132<def>');
WITH
cte AS
(
SELECT convert(xml,
concat('<phonenumbers><phonenumber number="',
replace(replace(substring(nmuloc,
1,
len(nmuloc) - 1),
'<',
'" tag="'),
'>|',
'"/><phonenumber number="'),
'"/></phonenumbers>')) phonenumbers
FROM elbat
)
SELECT stuff((SELECT ',' + nodes.node.value('concat(./#number, "<", ./#tag, ">")',
'nvarchar(max)')
FROM cte
CROSS APPLY phonenumbers.nodes('/phonenumbers/phonenumber[#tag="abc"]') nodes(node)
FOR XML PATH(''),
TYPE).value('(.)[1]',
'nvarchar(max)'),
1,
1,
'');
But while you're at it you should really consider to normalize your schema and don't use delimiter separated lists and the also non atomic number and tag combination in a string anymore!
SQL Fiddle
I didn't understand your question at first.but for answer you can you following code if you sql server is 2016 or upper.I think it has a good performance
Insert into table2 (ID1)
SELECT
STUFF((SELECT [value] +N',' AS 'data()' FROM STRING_SPLIT(ID1,'|') WHERE [value] LIKE'%<abc>' FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'') AS ID1
FROM
table1

How can I repeat a value from one column according to a value from another column?

I have data like below - one column with a value, the second with a count:
Name Count
----- -----
John 2
Smith 3
I want an output like below - each row consisting of the value in the first column repeated n times, where n is the value of the second column:
John,John
Smith,Smith,Smith
Is there a built-in Oracle function or SQL query that could be used to achieve this? If PL/SQL is required, that would also be helpful.
Looks to me that RPAD can do it
This is NOT TESTED as I don't have an Oracle db to hand
RPAD("", (LENGTH(name)+1)*count -1, name||',')
(rpad a blank string with count copies of the string, by requiring the result to be (length(name)+1)*count -1 long. The -1 is to remove the trailing comma )
Hat tip to OracleUser for the appending-the comma bit.
I'm not familiar with oracle, but if you know how to use recursion in oracle then you might be able to convert my T-SQL code to what works for you...
Step 1. Create test table containing your sample data:
CREATE TABLE #t ([Name] VARCHAR(20), [Count] INT);
INSERT INTO #t VALUES('John',2)
INSERT INTO #t VALUES('Smith',3)
SELECT * FROM #t
This gives the following output:
Name Count
-------------------- -----------
John 2
Smith 3
Step 2. Recursive Query to get desired output:
DECLARE #Separator VARCHAR(3); SET #Separator = ','
;WITH myCTE AS (
SELECT 1 AS [Count], [Name], [Count] AS RequiredCount, [Name] AS Result FROM #t
UNION ALL
SELECT [Count] + 1, [Name], RequiredCount, CAST(Result + #Separator + [Name] AS VARCHAR(20)) FROM myCTE WHERE RequiredCount > [Count]
)
SELECT [Count], [Name], Result FROM myCTE WHERE [Count] = RequiredCount
This gives the following output:
Count Name Result
----------- -------------------- --------------------
3 Smith Smith,Smith,Smith
2 John John,John
If this doesn't make sense to you, post a comment... I'll try and explain. I'm sure someone familiar with PL/SQL can help you convert this to meet your needs.
All the best!

SQL group records with same value, and place that into a table

I want to group same record with SQL, the following is my result
Name Code Qty
data1 AG 12
data1 AS 15
data2 MS 10
data2 IS 11
I want it to be like this instead.
Name Code Qty Code Qty
data1 AG 12 AS 15
data2 MS 10 IS 11
Can this be done in SQL only?
Can this be done in SQL only?
With a variable number of columns you have to build a the query dynamically.
I would hardly call this "SQL only" but it can be done in T-SQL and here is one way.
-- Sample table
declare #T table
(
Name varchar(5),
Code varchar(2),
Qty int
)
-- Sample data
insert into #T values
('data1', 'AG', 12),
('data1', 'AS', 15),
('data1', 'AQ', 17),
('data2', 'MS', 10),
('data2', 'IS', 11)
declare #XML xml
declare #SQL nvarchar(max)
declare #Max int
-- Max number of codes per name
select #Max = max(C)
from (select count(*) as C
from #T
group by Name) as T
-- Convert table to XML
set #XML = (select Name,
(select Code,
Qty
from #T as T2
where T1.Name = T2.Name
for xml path('c'), type)
from #T as T1
group by Name
for xml path('r'))
-- Build a dynamic query
;with Numbers(Number) as
(
select 1
union all
select Number + 1
from Numbers
where Number < #Max
)
select #SQL = 'select T.N.value(''Name[1]'', ''varchar(5)'') as Name ' +
(select ',T.N.value(''c['+cast(Number as nvarchar(10))+']/Code[1]'', ''char(2)'') as Code
,T.N.value(''c['+cast(Number as nvarchar(10))+']/Qty[1]'', ''int'') as Qty'
from Numbers
for xml path(''), type).value('.', 'nvarchar(max)') +
' from #xml.nodes(''/r'') as T(N)'
-- Execute query
exec sp_executesql #SQL, N'#xml xml', #XML
Result:
Name Code Qty Code Qty Code Qty
----- ---- ----------- ---- ----------- ---- -----------
data1 AG 12 AS 15 AQ 17
data2 MS 10 IS 11 NULL NULL
Try here: https://data.stackexchange.com/stackoverflow/q/122860/
Assuming you are using SQLServer, you could use rank to assign a sequential number to each code with the name group, like so:
select Name, Code, Qty, Rank() OVER (PARTITION BY Name ORDER BY Code) AS CodeRank
from MyTable
Then you can use the pivot functionality within either SQLServer or SSRS to format this as required.
Not exactly. You can hack around with the idea of concatenating lots of Code,Qty pairs into a single value for each record, though. I'm not sure what you're planning to do with multiple columns with the same name, even if such a thing were possible.