Get multiple values from XML field stored as Varchar(max) - sql-server

I have this value in a varchar(max) column in SQL Server:
<VersionSeries><SeriesTypeIdList><int>3</int><int>4</int><int>2</int><int>29</int><int>31</int><int>32</int><int>39</int></SeriesTypeIdList></VersionSeries
I want to get the int values out into a table.
This is the code I have but it is only returning the first record it finds.
Declare #Version varchar(100)
Select #Version = '2016A Demo'
DECLARE #DataTable TABLE
(
Xml XML NOT NULL,
Code NVARCHAR(50) NULL
)
INSERT
INTO #DataTable(Xml)
SELECT
CONVERT(XML,CONVERT(NVARCHAR(max), Series))
FROM Version
where VersionName = #Version
Create table #SeriesCodes
(Code integer)
Insert Into #SeriesCodes
(Code)
SELECT
T.c.value('int[1]', 'nvarchar(50)') as Code
FROM #DataTable d
OUTER APPLY d.Xml.nodes('/VersionSeries/SeriesTypeIdList') T(c);
Select * from #SeriesCodes

This is a single statement to get what you want:
DECLARE #x2 XML = N'<VersionSeries><SeriesTypeIdList><int>3</int><int>4</int><int>2</int><int>29</int><int>31</int><int>32</int><int>39</int></SeriesTypeIdList></VersionSeries>';
SELECT t.c.query(N'.').value(N'(/*)[1]', N'int') AS [int_value]
FROM #X2.nodes(N'/VersionSeries/SeriesTypeIdList/*') AS [t]([c]);

Like this:
Declare #Version varchar(100)
Select #Version = '2016A Demo'
DECLARE #DataTable TABLE
(
Xml XML NOT NULL,
Code NVARCHAR(50) NULL
)
INSERT
INTO #DataTable(Xml)
values (N'<VersionSeries><SeriesTypeIdList><int>3</int><int>4</int><int>2</int><int>29</int><int>31</int><int>32</int><int>39</int></SeriesTypeIdList></VersionSeries>')
SELECT
T.c.value('.', 'nvarchar(50)') as Code
FROM #DataTable d
OUTER APPLY d.Xml.nodes('/VersionSeries/SeriesTypeIdList/int') T(c);

Related

Stored procedure which writes xml into a DB, T-SQL

I am trying to write a stored procedure which writes xml into a DB, T-SQL.
Here is my sample xml (which will have a significant number of <RECORD>s in prod environment):
<?xml version="1.0" encoding="windows-1251"?>
<DATA FORMAT_VERSION="1.0">
<RECORD>
<NAME>МІЖНАРОДНА ГРОМАДСЬКА ОРГАНІЗАЦІЯ МІЖНАРОДНА АКАДЕМІЯ БІОЕНЕРГОТЕХНОЛОГІЙ</NAME>
<SHORT_NAME>МАБЕТ</SHORT_NAME>
<EDRPOU>00011601</EDRPOU>
<ADDRESS>01001, м.Київ, Шевченківський район, ВУЛИЦЯ ПРОРІЗНА, будинок 8, офіс 426</ADDRESS>
<STAN>зареєстровано</STAN>
</RECORD>
</DATA>
I pass the path to the xml file in the #pathToXml parameter.
Here is my stored procedure:
CREATE PROCEDURE [dbo].[LegalContractorsDataSynchronize]
(
#pathToXml varchar
)
AS
BEGIN
BEGIN TRANSACTION
DELETE FROM [dbo].[LegalContractors];
INSERT INTO [dbo].[LegalContractors]([Code], [ShortName], [Name], [LegalAddress], [Status])
SELECT CONVERT([Code], [ShortName], [Name], [LegalAddress], [Status])
FROM OPENROWSET(BULK, #pathToXml, SINGLE_CLOB) as x
COMMIT TRANSACTION
END
I am using Entity Framework to call the stored procedure. The call just happens (with no errors), but the DB is not updated. I am very sure that I mistyped something in the INSERT statement. I followed this example.
Could someone point out how could I fill the three columns in my DB using the data from the respective elements in xml? The columns are Code, ShortName, Name, LegalAddress and Status.
UPDATE
After the answer being posted I tried the suggested solution. I am getting the error:
Msg 102, Level 15, State 1, Procedure LegalContractorsDataSynchronize, Line 15 [Batch Start Line 0]
Incorrect syntax near '#pathToXml'.
Here is my code:
CREATE PROCEDURE [dbo].[LegalContractorsDataSynchronize]
(
#pathToXml varchar
)
AS
BEGIN
BEGIN TRANSACTION
DELETE FROM [dbo].[LegalContractors];
;WITH XmlFile (xmlData) AS
(
SELECT TRY_CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK #pathToXml, SINGLE_BLOB) AS x
)
INSERT INTO [dbo].[LegalContractors] ([Code], [ShortName], [Name], [LegalAddress], [Status])
SELECT c.value('(EDRPOU/text())[1]','NVARCHAR(100)') AS [Code]
, c.value('(SHORT_NAME/text())[1]','NVARCHAR(512)') AS [ShortName]
, c.value('(NAME/text())[1]','NVARCHAR(2048)') AS [Name]
, c.value('(ADDRESS/text())[1]','NVARCHAR(2048)') AS [LegalAddress]
, c.value('(STAN/text())[1]','NVARCHAR(100)') AS [Status]
FROM XmlFile CROSS APPLY xmlData.nodes('/DATA/RECORD') AS t(c);
COMMIT TRANSACTION
END
Please try the following. To the best of my knowledge, OPENROWSET() doesn't accept file name parameter as a variable.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (
ID INT IDENTITY PRIMARY KEY,
Code NVARCHAR(50) NOT NULL,
ShortName NVARCHAR(100) NOT NULL,
[Name] NVARCHAR(100) NOT NULL,
LegalAddress NVARCHAR(100) NOT NULL,
[Status] NVARCHAR(50) NOT NULL
);
-- DDL and sample data population, end
-- Method #1
-- XML file is hardcoded
;WITH XmlFile (xmlData) AS
(
SELECT TRY_CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK 'c:\...\Ukraine.xml', /*CODEPAGE = '65001',*/ SINGLE_BLOB) AS x
)
INSERT INTO #tbl (Code, ShortName, [Name], LegalAddress, [Status])
SELECT c.value('(EDRPOU/text())[1]','NVARCHAR(50)') AS [Code]
, c.value('(SHORT_NAME/text())[1]','NVARCHAR(100)') AS [ShortName]
, c.value('(NAME/text())[1]','NVARCHAR(100)') AS [Name]
, c.value('(ADDRESS/text())[1]','NVARCHAR(100)') AS [LegalAddress]
, c.value('(STAN/text())[1]','NVARCHAR(50)') AS [Status]
FROM XmlFile CROSS APPLY xmlData.nodes('/DATA/RECORD') AS t(c);
-- test
SELECT * FROM #tbl;
-- Method #2
-- dynamic XML file name as a parameter
DECLARE #xml XML
, #sql NVARCHAR(MAX)
, #fileName VARCHAR(256) = 'c:\...\Ukraine.xml';
SET #sql = N'SELECT #xmlOut = XmlDoc FROM OPENROWSET (BULK ' + QUOTENAME(#fileName,NCHAR(39)) + ', SINGLE_BLOB) AS Tab(XmlDoc)';
EXEC master.sys.sp_executesql #sql, N'#xmlOut XML OUTPUT', #xmlOut = #xml OUTPUT;
INSERT INTO #tbl (Code, ShortName, [Name], LegalAddress, [Status])
SELECT c.value('(EDRPOU/text())[1]','NVARCHAR(50)') AS [Code]
, c.value('(SHORT_NAME/text())[1]','NVARCHAR(100)') AS [ShortName]
, c.value('(NAME/text())[1]','NVARCHAR(100)') AS [Name]
, c.value('(ADDRESS/text())[1]','NVARCHAR(100)') AS [LegalAddress]
, c.value('(STAN/text())[1]','NVARCHAR(50)') AS [Status]
FROM #xml.nodes('/DATA/RECORD') AS t(c);
-- test
SELECT * FROM #tbl;

SQL Server : convert XML data onto table

I need help in fine-tuning this code that I write. I am new to SQL Server and I believe there are better ways to do this or perhaps some of the following codes can be simplified or fine-tuned for performances or saving memory resources.
Basically, I have this XML data :
<table_result id="001" subj_cd="cdaaa" grade="b" name="Phua Chu Kang"/>
and I want to create a table which looks like this from that XML data
Please note on the following points :
SplitThis is not a built in function (check the code below).
The data can have space, but delimited by ". Do note as well that the XML data can have varying number of fields-data pairs for that particular given table - referred as #dummy in the following codes. i.e. example XML data above have 4 fields (id, subj_cd, grade, name) and the next XML data could have 5 fields (i.e. id, name, occupation, phone_no, address). In the following code, #table_result is created to match the example XML data for easier demonstration. In other words, the table structures are known..so I can ignore the field names from the XML data and focus on extracting the data itself.
The code ran well on SQL Server 2012 (you can copy and paste run code directly) and I am able to get as above. I just need to fine tune this, if possible. I have include line like this : - - test blabla. You can uncomment that and try. I could use enhancements such as in term of avoiding the number of temp tables used or any ways to replace the use of row_number() in the code.
/* remove all temp tables */
declare #sql varchar(5000)
SELECT #sql = isnull(#sql+';', '') + 'drop table ' + SUBSTRING(t.name, 1, CHARINDEX('___', t.name)-1)
FROM tempdb..sysobjects AS t
WHERE t.name LIKE '#%[_][_][_]%'
AND t.id =OBJECT_ID('tempdb..' + SUBSTRING(t.name, 1, CHARINDEX('___', t.name)-1));
exec (#sql)
/* end */
/* function */
drop function splitthis
go
create function splitthis(#separator char(1), #list varchar(max))
returns #returntable table(item nvarchar(max))
as
begin
declare #index int
declare #newtext varchar(max)
if #list = null
return
set #index = charindex(#separator, #list)
while not(#index = 0)
begin
set #newtext = rtrim(ltrim(left(#list, #index - 1)))
set #list = right(#list, len(#list) - #index)
insert into #returntable(item) values(#newtext)
set #index = charindex(#separator, #list)
end
insert into #returntable(item) values(rtrim(ltrim(#list)))
update #returntable set item='' where item is null
return
end
go
/* end of function */
/* create dummy tables */
create table #table_result
(id nvarchar(max), subj_cd nvarchar(max), grade nvarchar(max), name nvarchar(max))
create table #dummy (name nvarchar(max), data nvarchar(max))
insert into #dummy
values ('a', '<table_result id="001" subj_cd="cdaaa" grade="b" name="phua chu kang"/>');
--test : select * from #dummy
/* remove the fist non-data opening tag */
declare #record nvarchar(max)
select #record = data from #dummy where name = 'a'
select *, null as temp into #tempb from splitthis(' ',#record)
select *, row_number() over (order by temp) count into #tempc from #tempb
select item into #tempd from #tempc where #tempc.count>1
-- test : select * from #tempd
/* get the actual field & data into a single column table */
declare #temp varchar(max)
set #temp=''select #temp=#temp+' ' + item from #tempd
select *, null as temp into #tempe from splitthis('"',#temp)
select *, row_number() over (order by temp) count into #tempf from #tempe
select item, count into #tempg from #tempf
--test : select * from #tempg
/* prepare the data table */
select
case when #tempg.count % 2 = 0
then item
else null
end as data
into #temph
from #tempg
select data, null as temp into #tempi from #temph
select data, row_number() over (order by temp) count into #data from #tempi
where data is not null
--test : select * from #data
/* prepare the field table. */
select name, null as temp into #tempj
from tempdb.sys.columns where object_id=object_id('tempdb..#table_result');
select *, row_number() over (order by temp) count into #field from #tempj
--test : select * from #field
/* get the final table */
select a.name as field, b.data from #field a
left join #data b on a.count=b.count
This is - using XML methods - much easier!
Try this:
DECLARE #xml XML='<table_result id="001" subj_cd="cdaaa" grade="b" name="Phua Chu Kang"/>';
SELECT One.Attr.value('fn:local-name(.)','varchar(max)') AS field
,One.Attr.value('.','varchar(max)') AS data
FROM #xml.nodes('table_result/#*') AS One(Attr)
The result
field data
id 001
subj_cd cdaaa
grade b
name Phua Chu Kang
Now I try to imitate your table structure (I'd recommend to store the data as XML from the beginning! In this case you could omit the first CROSS APPLY with the CAST ... AS XML):
DECLARE #tbl TABLE(name VARCHAR(10),data VARCHAR(MAX));
INSERT INTO #tbl VALUES
('a','<table_result id="001" subj_cd="cdaaa" grade="b" name="Phua Chu Kang"/>')
,('b','<Another test="test data" test2="test2 data"/>')
,('c','<OneMore x="x data" y="y data" z="z data"/>');
SELECT tbl.name
,One.Attr.value('fn:local-name(..)','varchar(max)') AS element
,One.Attr.value('fn:local-name(.)','varchar(max)') AS field
,One.Attr.value('.','varchar(max)') AS data
FROM #tbl AS tbl
CROSS APPLY(SELECT CAST(tbl.data AS XML)) AS MyData(AsXml)
CROSS APPLY MyData.AsXml.nodes('*/#*') AS One(Attr)
The result
name element field data
a table_result id 001
a table_result subj_cd cdaaa
a table_result grade b
a table_result name Phua Chu Kang
b Another test test data
b Another test2 test2 data
c OneMore x x data
c OneMore y y data
c OneMore z z data
Now, I'm not at all very good with T-SQL XML, but can't you just do it like this:
create table #dummy (name nvarchar(max), data xml);
insert into #dummy
values ('a', '<table_result id="001" subj_cd="cdaaa" grade="b" name="phua chu kang"/>');
select 'id' "field",
elem.value('#id', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem)
union all
select 'subj_cd' "field",
elem.value('#subj_cd', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem)
union all
select 'grade' "field",
elem.value('#grade', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem)
union all
select 'name' "field",
elem.value('#name', 'nvarchar(50)') "data"
from #dummy
cross apply data.nodes('/table_result') tbl(elem);
Notice that I changed the data type for #dummy.data to be xml. That's required to be able to use the XML functions.

Based on a variable: SQL Query to fetch data for multiple values from one column OR get all values from that column

Problem Statement :
when #a has a single word(Ex. 'name1') OR comma separated string (Example 'name1,name2,name3') then the query should return the manager names of employees with name1 and name2 and name3
when #a has an empty string then return the manager names of all the employees in the emp_master table
I have defined a stored procedure where I pass a variable.
This variable can be a comma separated string, a single word or an empty string.
If the string is comma separated then I split that string and get values based on the return table of split statement
else
I get the related value of the non comma separated data using normal subquery
I have tried to achieve this in the following way
Declare #a varchar(50)= ''
select emp.Name from
emp_master emp
where
(LEN(#a)=0 AND emp.Name in
(
SELECT DISTINCT [Name] FROM
[dbo].[Emp_Master] WHERE [EmpId] IN
(
SELECT
DISTINCT [MGR_ID]
FROM [dbo].[Emp_Master]
)
)
)
OR
emp.Name in (Select * from [dbo].[SplitString](#a, ','))
Details for the above sample:
[dbo].[SplitString] - custom written function : returns a table of split values. So
Select * from [dbo].SplitString
will return
SplitTable
----------
name1
name2
name3
and
Select * from [dbo].[SplitString](',','name1')
will return
SplitTable
----------
name1
[dbo].[Emp_Master] contains data for all the employees
[MGR_ID] is the column which has the employeeID of the employee manager
#a is the input variable
The Database is MS SQL 2008
My current solution(the above insane query) solves my purpose but it is very slow, it would be helpful to get an optimized and faster working solution for the problem
Emp_master Table has 400 000 rows, 30 columns
There are 18 000 managers in that table
CREATE NONCLUSTERED INDEX ix ON dbo.Emp_Master ([MGR_ID])
GO
DECLARE #a VARCHAR(50) = ''
DECLARE #t TABLE (val VARCHAR(50) PRIMARY KEY WITH(IGNORE_DUP_KEY=ON))
INSERT INTO #t
SELECT item = t.c.value('.', 'INT')
FROM (
SELECT txml = CAST('<r>' + REPLACE(#a, ',', '</r><r>') + '</r>' AS XML)
) r
CROSS APPLY txml.nodes('/r') t(c)
SELECT /*DISTINCT*/ [Name]
FROM dbo.Emp_Master e1
WHERE (
#a = ''
AND
e1.[EmpId] IN (SELECT DISTINCT MGR_ID FROM dbo.Emp_Master)
)
OR (
#a != ''
AND
e.Name IN (SELECT * FROM #t)
)
OPTION(RECOMPILE)
TRY THIS
CREATE NONCLUSTERED INDEX IX_MGR_ID_Emp_Master ON dbo.Emp_Master ([MGR_ID])
GO
Create Procedure searchname (#a varchar(255))
as
IF (#a = '')
BEGIN
EXEC Searchname1 #a
END
ELSE
BEGIN
EXEC Searchname2 #a
END
GO
Create Procedure Searchname1 (#a varchar(255))
AS
SELECT DISTINCT [Name] FROM
[dbo].[Emp_Master] m1 WHERE
exists
(
SELECT
*
FROM [dbo].[Emp_Master] m2
WHERE
m1.[EmpId]= m2.[MGR_ID]
)
GO
Create Procedure Searchname2 (#a varchar(max))
AS
Select #a = ' SELECT '''+replace( #a,',',''' Union ALL SELECT ''')+' '''
Create table #names (name varchar(255))
insert into #names
EXEC ( #a )
select emp.Name from
emp_master emp
WHERE
emp.Name in( Select name FRom #names)
option (recompile)
IF YOU ARE ALREADY DEALING WITH SQL INJECTION AT APPLICATION LEVEL
THEN
ALTER procedure [dbo].[Searchname2] (#a varchar(max))
AS
select #a = ''''+replace ( #a,',',''',''')+''''
DECLARE #sql NVARCHAR(MAX) = N'
select distinct emp.Name from
emp_master emp
WHERE
emp.Name in( '+#a+')'
EXEC (#sql)

SQL Server query with list parameter

I am using SQL Server and in a stored procedure I want to execute a query with a list parameter something like this:
select * from table where type in #list_types
Is it possible to make this? Or must I use temporary tables?
You could use table-valued parameters. For example:
-- A table valued parameter must have a type.
-- This command creates the type.
create type YourType as table (type varchar(50))
go
create procedure dbo.YourStoredProcedure(
#types YourType readonly)
as
select *
from YourTable
where type in (select type from #types)
go
You can invoke the stored procedure like this:
declare #types YourType
insert #types (type) values ('Type1'), ('Type2')
exec dbo.YourStoredProcedure #types
ADO.NET supports passing a DataTable as a table-valued parameter.
Try this one -
DECLARE #temp TABLE
(
[type] INT
, name NVARCHAR(50)
)
INSERT INTO #temp ([type], name)
VALUES
(1, '1'),
(2, '2')
DECLARE #list_types VARCHAR(30)
SELECT #list_types = '1,3,4,5'
;WITH cte AS
(
SELECT [type] = p.value('(./s)[1]', 'INT')
FROM (
SELECT field = CAST('<r><s>' + REPLACE(#list_types, ',', '</s></r><r><s>') + '</s></r>' AS XML)
) d
CROSS APPLY field.nodes('/r') t(p)
)
SELECT *
FROM #temp
WHERE [type] IN (SELECT [type] FROM cte)

Pivot with a table variable

I'm unable to use a pivot the data of a table variable.
Its giving following error on run-time:
"Must declare the scalar variable #reportData"
I have tried as mentioned below
DECLARE #reportData TABLE
(
PERSONID NUMERIC(6,0),
personname VARCHAR(100),
bu VARCHAR(50),
timeperiod VARCHAR(100),
wfstatus VARCHAR(100)
)
I'm using the below dynamic pivot query
declare #query nvarchar(max)
set #query=N'SELECT PERSONID,PERSONNAME,BU,wfstatus,'+#datelist+'
from(
SELECT PERSONID,PERSONNAME,BU,wfstatus,timeperiod
FROM
'+#reportData+') AS SOURCETABLE
PIVOT
(group by wfstatus
FOR timeperiod
IN('+#datelist+')
) as pivorttable
select personid,personname,bu,timeperiod,status from pivorttable'
execute(#query);
can some one help me in this?
I need to use only table variable to maintain concurrency issue.!
FROM'+#reportData attempts to add a table variable to a string, which wont work as a table variable is not a string.
Given that you presumably need to populate reportData first you could switch to an explicitly created temp table
create table #reportData
(
PERSONID NUMERIC(6,0)
...
)
Or use a Table Type;
--run once
CREATE TYPE ReportDataType AS TABLE (
PERSONID NUMERIC(6,0),
personname VARCHAR(100)
)
declare #reportData ReportDataType
insert #reportData values
(111, 'bob'),
(222, 'alice')
declare #query nvarchar(max) = N'select * from #T'
exec sp_executesql #query, N'#T ReportDataType readonly', #reportData

Resources