Inserting Order Details

Readers help Rick, a database developer, write a stored procedure for a Web application.

Umachandar Jayachandran

January 12, 2004

7 Min Read
ITPro Today logo in a gray background | ITPro Today

Congratulations to Ihor Bobak, an MCP and chief software architect for UKEESS Software House in Lviv, Ukraine. Ihor won first prize of $100 for the best solution to the January Reader Challenge, "Inserting Order Details." Here's a recap of the problem and the solution to the January Reader Challenge.

Problem:


Rick is a database developer for a company that sells products online. SQL Server 2000 hosts the online transaction processing (OLTP) database, and customers place their orders through a Web services application that the company created. The OLTP database schema is similar to schema for the Northwind sample database, and order information is similar to data in Northwind's Orders and OrderDetails tables. Help Rick write a stored procedure that the Web services application can call to insert an order row, with details, into the database. The stored procedure needs to:

  • Take order details in XML format

  • Return the order ID that the processing system generates

  • Validate the XML that the Web services application submits

  • Enable systems that produce the XML (in two formats: one with shipping information and one without, as the Web version of this problem shows) to use the stored procedure

The following XML shows two valid fragments, one with shipping information and one without shipping information.

                                    

Solution:


Rick can use T-SQL's OPENXML rowset in SQL Server 2000 to process the XML information in rowset form, letting him obtain the order provider or order detail information from the XML document in the format he desires. To process the XML information using OPENXML, Rick must prepare the XML document by using the sp_xml_preparedocument system stored procedure, which creates an internal representation of the XML for processing. After obtaining the handle, a numeric value he can use to access the prepared XML information, Rick can use the handle with OPENXML in the SELECT statement's FROM clause to read data in relational or table format.

The code to read the order information from the sample XML code (without shipping information) is:

DECLARE @h int-- Prepare XML input for use:EXEC sp_xml_preparedocument @h out, '                  '-- Get order element from the XML that's mapped to the Orders table schema by using WITH:SELECT *  FROM OPENXML(@h, '/root/Order')  WITH "Orders"-- Remove prepared XML from memory:EXEC sp_xml_removedocument @hGO

In the preceding sample code, the OPENXML rowset provider produces a table that looks like the Orders table and fills the columns with values from the XML document's Order element. OPENXML's WITH clause also supports schema declaration for each column. Rick will use both mechanisms to solve his problem. For the Orders table, he uses the table approach becase the column values are provided in the XML document or can be nullable. The schema declaration helps Rick specify column information for XML attributes, then access them in SQL. (You can read the OPENXML section in SQL Server Books Online (BOL) for complete syntax rules and options.)

Rick can also use OPENXML to read the order details from the same XML document by using the following code.

DECLARE @h int-- Prepare XML input for use:EXEC sp_xml_preparedocument @h out, '                  '-- Get order details from the XML that's mapped to desired columns only:SELECT *  FROM OPENXML(@h, '/root/Order/OrderDetail' )  WITH (ProductID int, UnitPrice money, Quantity smallint, Discount real)-- Remove prepared XML from memory:EXEC sp_xml_removedocument @hGO

Schema declarations map the various XML OrderDetail element attributes to the table columns. You can't use the Order Details table to map the schema because the table's OrderID column isn't nullable because the value isn't known and the attribute representing the column isn't in the XML document.

Now, Rick can use the OPENXML features inside a stored procedure to get the order details in table format and process the details using regular SQL statements. The following stored procedure inserts the order and order detail information into the database from the XML document:

IF object_id('InsertOrderFromWeb') IS NOT NULL    DROP PROCEDURE InsertOrderFromWebGOCREATE PROCEDURE InsertOrderFromWeb(@Order_Xml text, @OrderID int = NULL output)ASBEGIN    DECLARE @h int, @retcode int    -- Prepare Order XML for OPENXML use:    EXEC @retcode = sp_xml_preparedocument @h OUTPUT, @Order_Xml    IF @@error | @retcode  0    BEGIN        RAISERROR('Invalid order xml!', 16, 2)        RETURN 1    END        BEGIN TRANSACTION WebOrderEntry    -- Extract order information from XML:    INSERT INTO "Orders"    (CustomerID, EmployeeID, OrderDate, RequiredDate, ShippedDate, ShipVia, Freight,     ShipName, ShipAddress, ShipCity, ShipRegion,  ShipPostalCode, ShipCountry)    SELECT o.CustomerID, o.EmployeeID, o.OrderDate, o.RequiredDate,           o.ShippedDate, o.ShipVia, o.Freight, o.ShipName,           o.ShipAddress, o.ShipCity, o.ShipRegion,    o.ShipPostalCode, o.ShipCountry      FROM OPENXML(@h, '/root/Order' )      WITH "Orders" AS o    IF @@error  0 GOTO undo    -- Get new order id value:    SET @OrderID = SCOPE_IDENTITY()    -- Extract order details from XML:    INSERT INTO "Order Details"    (OrderID, ProductID, UnitPrice, Quantity, Discount)    SELECT @OrderID, od.ProductID, od.UnitPrice, od.Quantity, od.Discount      FROM OPENXML(@h, '/root/Order/OrderDetail' )      WITH (ProductID int, UnitPrice money, Quantity smallint, Discount real) AS od    IF @@error  0 GOTO undo    COMMIT TRANSACTION WebOrderEntry        -- Remove Order XML from memory:    EXEC sp_xml_removedocument @h    RETURN 0    undo:    IF @@trancount > 0 ROLLBACK TRANSACTION WebOrderEntry    -- Remove Order XML from memory:    EXEC sp_xml_removedocument @h    RETURN 2            ENDGO

The stored procedure starts a user-defined transaction, performs the inserts into the Orders and Order Details tables, and returns the newly generated order ID value as the output parameter. The OrderID column in the Orders table is an IDENTITY column. The stored procedure retrieves the newly generated OrderID value by using the SCOPE_IDENTITY() system function. The SCOPE_IDENTITY() system function returns the most recently generated IDENTITY value in the session for a column in a table.

FEBRUARY READER CHALLENGE:


Now, test your SQL Server savvy in the February Reader Challenge, "Importing from Excel" (below). Submit your solution in an email message to [email protected] by January 22. Umachandar Jayachandran, a SQL Server Magazine technical editor, will evaluate the responses. We'll announce the winner in an upcoming SQL Server Magazine UPDATE. The first-place winner will receive $100, and the second-place winner will receive $50.

Phil generates reports for a sales team in a company that sells books. The company stores publication data for its books in a SQL Server 2000 database. Phil receives sales data updates in a Microsoft Excel file that has the following header labels for columns: stor_id, yr, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, and Dec. The spreadsheet contains quantity of books sold in each store by year and month. You can generate sample data for the Excel spreadsheet from the Sales table in the Pubs database by using the following code:

SELECT s.stor_id, year(s.ord_date) AS yr,       SUM(CASE month(s.ord_date) WHEN 1 THEN s.qty ELSE 0 END) AS Jan,       SUM(CASE month(s.ord_date) WHEN 2 THEN s.qty ELSE 0 END) AS Feb,       SUM(CASE month(s.ord_date) WHEN 3 THEN s.qty ELSE 0 END) AS Mar,       SUM(CASE month(s.ord_date) WHEN 4 THEN s.qty ELSE 0 END) AS Apr,       SUM(CASE month(s.ord_date) WHEN 5 THEN s.qty ELSE 0 END) AS May,       SUM(CASE month(s.ord_date) WHEN 6 THEN s.qty ELSE 0 END) AS Jun,       SUM(CASE month(s.ord_date) WHEN 7 THEN s.qty ELSE 0 END) AS Jul,       SUM(CASE month(s.ord_date) WHEN 8 THEN s.qty ELSE 0 END) AS Aug,       SUM(CASE month(s.ord_date) WHEN 9 THEN s.qty ELSE 0 END) AS Sep,       SUM(CASE month(s.ord_date) WHEN 10 THEN s.qty ELSE 0 END) AS Oct,       SUM(CASE month(s.ord_date) WHEN 11 THEN s.qty ELSE 0 END) AS Nov,       SUM(CASE month(s.ord_date) WHEN 12 THEN s.qty ELSE 0 END) AS Dec  FROM Sales AS s GROUP BY s.stor_id, year(s.ord_date)

Phil needs to import the data from the Excel file into a SQL Server table called StoreSalesSummary by unpivoting the month columns from the spreadsheet. You can create the StoreSalesSummary table by using the following code:

CREATE TABLE StoreSalesSummary (    stor_id int NOT NULL,    qty int NOT NULL,    yr smallint NOT NULL,    mn tinyint NOT NULL,    PRIMARY KEY(stor_id, yr, mn))

Help Phil import only the Excel spreadsheet data into the StoreSalesSummary table, then insert and update each store's sales from the spreadsheet. Import only the stores with a nonzero quantity value for any month.

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like