How to split string from XML content and get the required value - sql-server

Hello all I am converting an xml content and inserting it to a table variable as follows
DECLARE #iDoc int
SET #XMLData = '<NewDataSet>
<Table>
<DataId>2324205.3933251.7336404</DataId>
<IsVisible>true</IsVisible>
<Notes />
<Marks>85.5</Marks>
</Table>
</NewDataSet>'
EXEC sp_xml_preparedocument #idoc OUTPUT, #XMLData
SELECT DataId FROM OPENXML(#idoc, 'NewDataSet/Table', 1)
WITH (DataId NVARCHAR(250) 'DataId')```
I would like to split the dot value and retrieve the the first value, can some one help me how to can I do that with in XML
IsVisible is a bit filed, Marks is deicmal like that I will have

Starting from SQL Server 2005 onwards, it is better to use XQuery language, based on the w3c standards, while dealing with the XML data type.
Microsoft proprietary OPENXML and its companions sp_xml_preparedocument and sp_xml_removedocument are kept just for backward compatibility with the obsolete SQL
Server 2000. Their use is diminished just to very few fringe cases.
It is strongly recommended to re-write your SQL and switch it to XQuery.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Notes NVARCHAR(MAX));
INSERT INTO #tbl (Notes) VALUES
(N'<NewDataSet>
<Table>
<DataId>2324205.3933251.7336404</DataId>
</Table>
</NewDataSet>');
-- DDL and sample data population, end
WITH rs AS
(
SELECT *
, TRY_CAST(Notes AS XML).value('(/NewDataSet/Table/DataId/text())[1]', 'VARCHAR(MAX)') AS x
FROM #tbl
)
SELECT *
, LEFT(x, CHARINDEX('.', x) - 1) AS [After]
, PARSENAME(x, 3) AS [After2]
FROM rs;
Output
+-------------------------+---------+
| Before | After |
+-------------------------+---------+
| 2324205.3933251.7336404 | 2324205 |
+-------------------------+---------+

Related

Need to mix dynamic SQL, Open Query, JSON, dynamic variables, and a few other oddities into a single query

Need to run dynamic SQL against DB2 on MS SQL through OpenQuery, get results back in JSON, then return this as an Output Parameter in a Stored Procedure
I've tried using a table variable as the sample code shows, but I get this error:
The FOR JSON clause is not allowed in a INSERT statement
I've also tried wrapping the query into a CTE, but given the JSON column name changes I can't use * or I get this error:
No column name was specified for column 1 of 'tbl'.
So I'm at a loss. I need to run this and get the JSON in the Output parameter, but given I'm having to mix a call to DB2 through OpenQuery and dynamic SQL to set the parameter I can't find a syntax that works.
create procedure uspTesting (
#inAccountNumber nvarchar(20),
#outJSON nvarchar(max) output)
as
begin declare #result table (ResultJson nvarchar(max));
declare #tsql nvarchar(4000) = '
select name, age
from openquery(db2link,''
select name,
age
from db2.account
where accountnumber = ''''' + #inAccountNumber + ''''')'') tbl for json auto';
insert into #result
EXEC (#TSQL);
select #outJSON = ResultJson from #result; End
The results I'm looking for are the JSON string in the output parameter #outJSON.
Apply the FOR JSON after you've gotten the data, load it into a temp table and then use the FOR JSON.
Without test data, etc you might have to adjust this, but try something like:
CREATE PROCEDURE [uspTesting]
(
#inAccountNumber NVARCHAR(20)
, #outJSON NVARCHAR(MAX) OUTPUT
)
AS
BEGIN
DECLARE #result TABLE
(
[name] NVARCHAR(100) --whatever data type you need here
, [age] NVARCHAR(100)
);
DECLARE #tsql NVARCHAR(4000) = '
select name, age
from openquery(db2link,''
select name,
age
from db2.account
where accountnumber = ''' + #inAccountNumber + ''')';
--Here we will just load a table variable with the data.
INSERT INTO #result
EXEC ( #tsql );
--Then we will select from that table variable applying the JSON here.
SET #outJSON = (
SELECT *
FROM #result
FOR JSON AUTO
);
END;

SQL Server read csv binary from table

I currently store my csv formatted files on disk and then query them like this:
SELECT *
FROM OPENROWSET(BULK 'C:\myfile.csv',
FORMATFILE = 'C\format.fmt',
FIRSTROW = 2) AS rs
Where format.fmt are the defined format of the columns in the csv file. This works very well.
But I'm interested in storing the file in a SQL Server table instead of storing them at disk.
So when having a VARBINARY(MAX) datatype column. How do I query them?
If I have a table like:
CREATE TABLE FileTable
(
[FileName] NVARCHAR(256)
,[File] VARBINARY(MAX)
)
With one row 'myfile.csv', '0x427574696B3B44616....'
How to read that file content into a temporary table for example?
If you really need to work with varbinary data, you can just cast it back to nvarchar:
DECLARE #bin VARBINARY(MAX)
SET #bin = 0x5468697320697320612074657374
SELECT CAST(#bin as VARCHAR(MAX))
-- gives This is a test
Once you've got it into that format, you can use a split function to turn it into a table. Don't ask me why there isn't a built-in split function in SQL Server, given that it's such a screamingly obvious oversight, but there isn't. So create your own with the code below:
CREATE FUNCTION [dbo].[fn_splitDelimitedToTable] ( #delimiter varchar(3), #StringInput VARCHAR(8000) )
RETURNS #OutputTable TABLE ([String] VARCHAR(100), [Hierarchy] int )
AS
BEGIN
DECLARE #String VARCHAR(100)
DECLARE #row int = 0
WHILE LEN(#StringInput) > 0
BEGIN
SET #row = #row + 1
SET #String = LEFT(#StringInput,
ISNULL(NULLIF(CHARINDEX(#delimiter, #StringInput) - 1, -1),
LEN(#StringInput)))
SET #StringInput = SUBSTRING(#StringInput,
ISNULL(NULLIF(CHARINDEX(#delimiter, #StringInput), 0),
LEN(#StringInput)) + 1, LEN(#StringInput))
INSERT INTO #OutputTable ( [String], [Hierarchy] )
VALUES ( #String, #row )
END
RETURN
END
Put it all together:
select CAST('one,two,three' as VARBINARY)
-- gives 0x6F6E652C74776F2C7468726565
DECLARE #bin VARBINARY(MAX)
SET #bin = 0x6F6E652C74776F2C7468726565
select * from fn_splitDelimitedToTable(',', CAST(#bin as VARCHAR(MAX)))
gives this result:
string hierarchy
================
one 1
two 2
three 3
And of course, you can get the result into a temp table to work with if you so wish:
select * into #myTempTable
from fn_splitDelimitedToTable(',', CAST(#bin as VARCHAR(MAX)))
If you've got CSV data, why not just import it into the database?
You can also use BULK INSERT to do this as in this question.
Assuming you've created a table with the correct format to import the data into (e.g. 'MyImportTable') something like the following could be used:
BULK INSERT MyImportTable
FROM 'C:\myfile.csv'
WITH (DATAFILETYPE='char',
FIRSTROW = 2,
FORMATFILE = 'C\format.fmt');
EDIT 1:
With the data imported into the database, you can now query the table directly, and avoid having the CSV altogether like so:
SELECT *
FROM MyImportTable
With the reference to the original CSV no longer required you can delete/archive the original CSV.
EDIT 2:
If you've enabled xp_cmdshell, and you have the appropriate permissions, you can delete the file from SQL with the following:
xp_cmdshell 'del c:\myfile.csv'
Lastly, if you want to enable xp_cmdshell use the following:
exec sp_configure
go
exec sp_configure 'xp_cmdshell', 1
go
reconfigure
go

INSERT INTO with exec with multiple result sets

SQL Server allows me to insert the returned result set of a stored procedure as:
DECLARE #T TABLE (
ID int,
Name varchar(255),
Amount money)
INSERT INTO #T
exec dbo.pVendorBalance
This works as long as the stored procedure only returns 1 result set.
Is there a way to make this work if the stored procedure returns several result sets?
E.g.
DECLARE #T1 (...)
DECLARE #T2 (...)
INSERT INTO #T1 THEN INTO #T2
exec dbo.pVendorBalance
One workaround to this problem is using OUTPUT parameters (JSON/XML) instead of resultsets.
CREATE TABLE tab1(ID INT, Name NVARCHAR(10), Amount MONEY);
INSERT INTO tab1(ID, Name, Amount)
VALUES (1, 'Alexander', 10),(2, 'Jimmy', 100), (6, 'Billy', 20);
CREATE PROCEDURE dbo.pVendorBalance
AS
BEGIN
-- first resultset
SELECT * FROM tab1 WHERE ID <=2;
-- second resultset
SELECT * FROM tab1 WHERE ID > 5;
END;
Version with OUT params:
CREATE PROCEDURE dbo.pVendorBalance2
#resultSet1 NVARCHAR(MAX) OUT,
#resultSet2 NVARCHAR(MAX) OUT
AS
BEGIN
SELECT #resultSet1 = (SELECT * FROM tab1 WHERE ID <=2 FOR JSON AUTO),
#resultSet2 = (SELECT * FROM tab1 WHERE ID > 5 FOR JSON AUTO);
END;
And final call:
DECLARE #r1 NVARCHAR(MAX), #r2 NVARCHAR(MAX);
EXEC dbo.pVendorBalance2 #r1 OUT, #r2 OUT;
-- first resultset as table
SELECT *
INTO #t1
FROM OpenJson(#r1)
WITH (ID int '$.ID', [Name] NVARCHAR(50) '$.Name',Amount money '$.Amount');
-- second resultset as table
SELECT *
INTO #t2
FROM OpenJson(#r2)
WITH (ID int '$.ID', [Name] NVARCHAR(50) '$.Name',Amount money '$.Amount');
SELECT * FROM #t1;
SELECT * FROM #t2;
DBFiddle Demo
EDIT:
Second approach is to use tSQLt.ResultSetFilter CLR function (part of tSQLt testing framework):
The ResultSetFilter procedure provides the ability to retrieve a single result set from a statement which produces multiple result sets.
CREATE TABLE #DatabaseSize (
database_name nvarchar(128),
database_size varchar(18),
unallocated_space varchar(18)
);
CREATE TABLE #ReservedSpaceUsed (
reserved VARCHAR(18),
data VARCHAR(18),
index_size VARCHAR(18),
unused VARCHAR(18)
);
INSERT INTO #DatabaseSize
EXEC tSQLt.ResultSetFilter 1, 'EXEC sp_spaceused';
INSERT INTO #ReservedSpaceUsed
EXEC tSQLt.ResultSetFilter 2, 'EXEC sp_spaceused';
SELECT * FROM #DatabaseSize;
SELECT * FROM #ReservedSpaceUsed;
No. But there is more of a work around since you cannot do an insert into with a procedure that returns multiple results with a different number of columns.
If you are allowed to modify the stored procedure, then you can declare temp tables outside of the procedure and populate them within the stored procedure. Then you can do whatever you need with them outside of the stored procedure.
CREATE TABLE #result1(Each column followed by data type of first result.);
----Example: CREATE TABLE #result1(Column1 int, Column2 varchar(10))
CREATE TABLE #result2(Each column followed by data type of second result.);
EXEC pVendorBalance;
SELECT * FROM #result1;
SELECT * FROM #result2;
I had a similar requirement, and ended up using the a CLR function which you can read about here (it's the answer with the InsertResultSetsToTables method, by user Dan Guzman):
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/da5328a7-5dab-44b3-b2b1-4a8d6d7798b2/insert-into-table-one-or-multiple-result-sets-from-stored-procedure?forum=transactsql
You need to create a SQL Server CLR project in Visual Studio to get going. I had a project already written by a co-worker that I could just expand, but if you're starting from scratch, try reading this guide:
http://www.emoreau.com/Entries/Articles/2015/04/SQL-CLR-Integration-in-2015-year-not-product-version.aspx
If you've succeeded in writing and publishing the CLR project to the database, here is an example of using it I wrote:
-- declare a string with the SQL you want to execute (typically an SP call that returns multiple result sets)
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'exec usp_SomeProcedure #variable1 = ' + #variable1 + '...' -- piece together a long SQL string from various parameters
-- create temp tables (one per result set) to hold the output; could also be actual tables (non-temp) if you want
CREATE TABLE #results_1(
[CustomerId] INT, [Name] varchar(500), [Address] varchar(500)
);
CREATE TABLE #results_2(
[SomeId] UNIQUEIDENTIFIER, [SomeData] INT, [SomethingElse] DateTime
);
-- on the exemplary 'CustomerDatabase' database, there is an SP (created automatically by the SQL CLR project deployment process in Visual Studio) which performs the actual call to the .NET assembly, and executes the .NET code
-- the CLR stored procedure CLR_InsertResultSetsToTables executes the SQL defined in the parameter #sourceQuery, and outputs multiple result sets into the specified list of tables (#targetTableList)
EXEC CustomerDatabase.dbo.CLR_InsertResultSetsToTables #sourceQuery = #sql, #targetTableList = N'#results_1,#results_2';
-- The output of the SP called in #sql is now dumped in the two temp tables and can be used for whatever in regular SQL
SELECT * FROM #results_1;
SELECT * FROM #results_2;
We can do it in the following way
Consider the input SP (which returns 2 tables as output) as usp_SourceData
Alter the usp_SourceData to accept a parameter as 1 and 2
Adjust the SP in a way that when
usp_SourceData '1' is executed it will return first table
and when
usp_SourceData '2' is executed it will return second table.
Actually stored procedures can return multiple result sets, or no result sets, it's pretty arbitrary. Because of this, I don't know of any way to navigate those results from other SQL code calling a stored procedure.
However, you CAN use the returned result set from a table-valued user defined function. It's just like a regular UDF, but instead of returning a scalar value you return a query result. Then you can use that UDF like any other table.
INSERT INTO #T SELECT * FROM dbp.pVendorBalanceUDF()
http://technet.microsoft.com/en-us/library/ms191165(v=sql.105).aspx
DROP TABLE ##Temp
DECLARE #dtmFrom VARCHAR(60) = '2020-12-01 00:00:00', #dtmTo VARCHAR(60) = '2020-12-02 23:59:59.997',#numAdmDscTransID VARCHAR(60) =247054
declare #procname nvarchar(255) = 'spGetCashUnpaidBills',
#procWithParam nvarchar(255) = '[dbo].[spGetCashUnpaidBills] #dtmFromDate= ''' +#dtmFrom+ ''' ,#dtmToDate= ''' +#dtmTo+''',#numCompanyID=1,#numAdmDscTransID='+ #numAdmDscTransID +',#tnyShowIPCashSchemeBills=1',
#sql nvarchar(max),
#tableName Varchar(60) = 'Temp'
set #sql = 'create table ##' + #tableName + ' ('
begin
select #sql = #sql + '[' + r.name + '] ' + r.system_type_name + ','
from sys.procedures AS p
cross apply sys.dm_exec_describe_first_result_set_for_object(p.object_id, 0) AS r
where p.name = #procname
set #sql = substring(#sql,1,len(#sql)-1) + ')'
execute (#sql)
execute('insert ##' + #tableName + ' exec ' + #procWithParam)
end
SELECT *FROM ##Temp
If the both result sets have same number of columns then
insert into #T1 exec dbo.pVendorBalance
will insert the union of both data set into #T1.
If not
Then edit dbo.pVendorBalance and insert results into temporary tables and in outer stored proc, select from those temporary tables.
Another way(If you need it), you can try
SELECT * into #temp
from OPENROWSET('SQLNCLI', 'Server=(local)\\(instance);Trusted_Connection=yes;',
'EXEC dbo.pVendorBalance')
it will take first dataset.

SQL variable to hold list of integers

I'm trying to debug someone else's SQL reports and have placed the underlying reports query into a query windows of SQL 2012.
One of the parameters the report asks for is a list of integers. This is achieved on the report through a multi-select drop down box. The report's underlying query uses this integer list in the where clause e.g.
select *
from TabA
where TabA.ID in (#listOfIDs)
I don't want to modify the query I'm debugging but I can't figure out how to create a variable on the SQL Server that can hold this type of data to test it.
e.g.
declare #listOfIDs int
set listOfIDs = 1,2,3,4
There is no datatype that can hold a list of integers, so how can I run the report query on my SQL Server with the same values as the report?
Table variable
declare #listOfIDs table (id int);
insert #listOfIDs(id) values(1),(2),(3);
select *
from TabA
where TabA.ID in (select id from #listOfIDs)
or
declare #listOfIDs varchar(1000);
SET #listOfIDs = ',1,2,3,'; --in this solution need put coma on begin and end
select *
from TabA
where charindex(',' + CAST(TabA.ID as nvarchar(20)) + ',', #listOfIDs) > 0
Assuming the variable is something akin to:
CREATE TYPE [dbo].[IntList] AS TABLE(
[Value] [int] NOT NULL
)
And the Stored Procedure is using it in this form:
ALTER Procedure [dbo].[GetFooByIds]
#Ids [IntList] ReadOnly
As
You can create the IntList and call the procedure like so:
Declare #IDs IntList;
Insert Into #IDs Select Id From dbo.{TableThatHasIds}
Where Id In (111, 222, 333, 444)
Exec [dbo].[GetFooByIds] #IDs
Or if you are providing the IntList yourself
DECLARE #listOfIDs dbo.IntList
INSERT INTO #listofIDs VALUES (1),(35),(118);
You are right, there is no datatype in SQL-Server which can hold a list of integers. But what you can do is store a list of integers as a string.
DECLARE #listOfIDs varchar(8000);
SET #listOfIDs = '1,2,3,4';
You can then split the string into separate integer values and put them into a table. Your procedure might already do this.
You can also use a dynamic query to achieve the same outcome:
DECLARE #SQL nvarchar(8000);
SET #SQL = 'SELECT * FROM TabA WHERE TabA.ID IN (' + #listOfIDs + ')';
EXECUTE (#SQL);
Note: I haven't done any sanitation on this query, please be aware that it's vulnerable to SQL injection. Clean as required.
For SQL Server 2016+ and Azure SQL Database, the STRING_SPLIT function was added that would be a perfect solution for this problem. Here is the documentation:
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql
Here is an example:
/*List of ids in a comma delimited string
Note: the ') WAITFOR DELAY ''00:00:02''' is a way to verify that your script
doesn't allow for SQL injection*/
DECLARE #listOfIds VARCHAR(MAX) = '1,3,a,10.1,) WAITFOR DELAY ''00:00:02''';
--Make sure the temp table was dropped before trying to create it
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable;
--Create example reference table
CREATE TABLE #MyTable
([Id] INT NOT NULL);
--Populate the reference table
DECLARE #i INT = 1;
WHILE(#i <= 10)
BEGIN
INSERT INTO #MyTable
SELECT #i;
SET #i = #i + 1;
END
/*Find all the values
Note: I silently ignore the values that are not integers*/
SELECT t.[Id]
FROM #MyTable as t
INNER JOIN
(SELECT value as [Id]
FROM STRING_SPLIT(#listOfIds, ',')
WHERE ISNUMERIC(value) = 1 /*Make sure it is numeric*/
AND ROUND(value,0) = value /*Make sure it is an integer*/) as ids
ON t.[Id] = ids.[Id];
--Clean-up
DROP TABLE #MyTable;
The result of the query is 1,3
In the end i came to the conclusion that without modifying how the query works i could not store the values in variables. I used SQL profiler to catch the values and then hard coded them into the query to see how it worked. There were 18 of these integer arrays and some had over 30 elements in them.
I think that there is a need for MS/SQL to introduce some aditional datatypes into the language. Arrays are quite common and i don't see why you couldn't use them in a stored proc.
There is a new function in SQL called string_split if you are using list of string.
Ref Link STRING_SPLIT (Transact-SQL)
DECLARE #tags NVARCHAR(400) = 'clothing,road,,touring,bike'
SELECT value
FROM STRING_SPLIT(#tags, ',')
WHERE RTRIM(value) <> '';
you can pass this query with in as follows:
SELECT *
FROM [dbo].[yourTable]
WHERE (strval IN (SELECT value FROM STRING_SPLIT(#tags, ',') WHERE RTRIM(value) <> ''))
I use this :
1-Declare a temp table variable in the script your building:
DECLARE #ShiftPeriodList TABLE(id INT NOT NULL);
2-Allocate to temp table:
IF (SOME CONDITION)
BEGIN
INSERT INTO #ShiftPeriodList SELECT ShiftId FROM [hr].[tbl_WorkShift]
END
IF (SOME CONDITION2)
BEGIN
INSERT INTO #ShiftPeriodList
SELECT ws.ShiftId
FROM [hr].[tbl_WorkShift] ws
WHERE ws.WorkShift = 'Weekend(VSD)' OR ws.WorkShift = 'Weekend(SDL)'
END
3-Reference the table when you need it in a WHERE statement :
INSERT INTO SomeTable WHERE ShiftPeriod IN (SELECT * FROM #ShiftPeriodList)
You can't do it like this, but you can execute the entire query storing it in a variable.
For example:
DECLARE #listOfIDs NVARCHAR(MAX) =
'1,2,3'
DECLARE #query NVARCHAR(MAX) =
'Select *
From TabA
Where TabA.ID in (' + #listOfIDs + ')'
Exec (#query)

SELECT node text values from xml document in TSQL OPENXML

I have a xml document I want to use to update values in a stored procedure. I can process the XML using OPENXML, but I'm confused about extracting the values I want. Each row in the xml is a product record and I want to create a variable for each property. Cell0 is the ID, Cell2 description etc
DECLARE #idoc int
DECLARE #doc varchar(1000)
SET #doc ='
<products>
<rows>
<row>
<cell>1</cell>
<cell>BALSAMO DERMOSCENT</cell>
<cell>1.00</cell>
<cell>0.00</cell>
<cell>18.00</cell>
<cell>18.00</cell>
<cell>8.00</cell>
<cell>427</cell>
<cell>No</cell>
</row>
<row>
<cell>2</cell>
<cell>BAYTRIL 150 MG 1 CPDO</cell>
<cell>1.00</cell>
<cell>0.00</cell>
<cell>3.50</cell>
<cell>3.50</cell>
<cell>8.00</cell>
<cell>57</cell>
<cell>No</cell>
</row>
</rows>
</products>'
--Create an internal representation of the XML document.
EXEC sp_xml_preparedocument #idoc OUTPUT, #doc
-- Execute a SELECT statement that uses the OPENXML rowset provider.
SELECT *
FROM OPENXML (#idoc, '/products/rows/row/cell',1)
with (Col1 varchar(29) 'text()')
Running the above query returns 1 record for each CELL in the xml. I want to be able to return 1 record per row with different columns for each cell, something like:-
Prod Description Qty
---------- -------------------- --------
1 BALSAMO DERMOSCENT 1.00
2 BAYTRIL 150 MG 1 CPDO 1.00
I'm using MSSQL 2008
I've come up with the following which does the job for me
DECLARE #idoc int
DECLARE #doc varchar(1000)
SET #doc ='
<products>
<rows>
<row>
<cell>1</cell>
<cell>BALSAMO DERMOSCENT</cell>
<cell>1.00</cell>
<cell>0.00</cell>
<cell>18.00</cell>
<cell>18.00</cell>
<cell>8.00</cell>
<cell>427</cell>
<cell>No</cell>
</row>
<row>
<cell>2</cell>
<cell>BAYTRIL 150 MG 1 CPDO</cell>
<cell>1.00</cell>
<cell>0.00</cell>
<cell>3.50</cell>
<cell>3.50</cell>
<cell>8.00</cell>
<cell>57</cell>
<cell>No</cell>
</row>
</rows>
</products>'
--Create an internal representation of the XML document.
EXEC sp_xml_preparedocument #idoc OUTPUT, #doc
-- Execute a SELECT statement that uses the OPENXML rowset provider.
SELECT *
FROM OPENXML (#idoc, '/products/rows/row',1)
with (pLineNo int 'cell[1]/text()',
pDesc varchar(50) 'cell[2]/text()',
pQty float 'cell[3]/text()',
pCost float 'cell[4]/text()',
pPvp float 'cell[5]/text()',
pTotal float 'cell[6]/text()',
pIva float 'cell[7]/text()',
pId int 'cell[8]/text()',
pnoFact varchar(5) 'cell[9]/text()')
Why use openxml on sql server 2008?
This is a better option (I used varchar(max) as the datatype, but enter whatever is applicable). Note you have to declare the variable as xml, not varchar.
SELECT
Row.Item.value('data(cell[1])', 'varchar(max)') As Prod,
Row.Item.value('data(cell[2])', 'varchar(max)') As Description,
Row.Item.value('data(cell[3])', 'varchar(max)') As Qty
FROM
#doc.nodes('//row') AS Row(Item)
Note: If you're doing this is a stored procedure you may have to include the following before the select statement:
SET ARITHABORT ON -- required for .nodes
If you must use openxml, at least clean it up when you're done:
exec sp_xml_removedocument #idoc

Resources