Returning a record set from a stored procedure
Posted by decipherinfosys on May 19, 2008
One of the the database developers at a client site who is responsible for writing DB code across the three RDBMS that they support wanted a simple example of a stored procedure which returns the record set to the calling application for further processing. In this post, we will show you in brief example, how can we address this in Oracle, MS SQL Server and DB2 LUW.
Oracle:
First let us create a table and populate it with small set of data. If you already have table with the same name, change all the occurrences of the table name with some other name.
CREATE TABLE Invoice
(
INVOICE_NUMBER NUMBER(9) NOT NULL,
INVOICE_DATE DATE NOT NULL,
CLIENT_ID NUMBER(9) NOT NULL,
INVOICE_AMT NUMBER(9,2) DEFAULT 0 NOT NULL,
PAID_FLAG NUMBER(1) DEFAULT 0 NOT NULL, — 0 Not paid/ 1 paid
CONSTRAINT PK_INVOICE PRIMARY KEY(INVOICE_NUMBER)
)
– TABLESPACE Clause
/
CREATE SEQUENCE INVOICE_SEQ
START WITH 1
CACHE 100
/
INSERT INTO INVOICE(Invoice_Number, Invoice_date, client_ID, Invoice_Amt)
VALUES(INVOICE_SEQ.NEXTVAL, sysdate,101,1100.00);
INSERT INTO INVOICE(Invoice_Number, Invoice_date, client_ID, Invoice_Amt)
VALUES(INVOICE_SEQ.NEXTVAL, sysdate,102,1100.00);
INSERT INTO INVOICE(Invoice_Number, Invoice_date, client_ID, Invoice_Amt)
VALUES(INVOICE_SEQ.NEXTVAL, sysdate,103,1100.00);
INSERT INTO INVOICE(Invoice_Number, Invoice_date, client_ID, Invoice_Amt)
VALUES(INVOICE_SEQ.NEXTVAL, sysdate,104,1100.00);
Oracle uses cursor variables to pass query result sets between PL/SQL sub programs and to the client application. A cursor variable has data type REF CURSOR and that is what is normally known as a ref cursor (we will cover more on the ref cursors in one of our future whitepapers). Following is the code snippet to return result set from the stored procedure.
To create a cursor variable first we need to define a REF CURSOR type and then declare the cursor variable of that type. To avoid declaring the same REF CURSOR type in each program, we will create one global type in a package specification and declare cursor of that type in our procedure.
Run following scripts to create package and procedure from SQL*Plus prompt. Testing script is to test the execution of the procedure.
/* Create package */
CREATE OR REPLACE PACKAGE types
AS
type cursorType is ref cursor;
END;
/
– Here we have declared cursor variable of type cursorType as an output variable.
CREATE OR REPLACE PROCEDURE DEC_RTN_RECORDSET
(
p_InvoiceDate IN DATE,
p_ResultSet OUT TYPES.cursorType
)
AS
BEGIN
OPEN p_ResultSet FOR
SELECT Invoice_Number, Invoice_Date, Client_ID, Invoice_Amt
FROM Invoice
WHERE Invoice_date <= p_InvoiceDate
ORDER BY Invoice_number;
END DEC_RTN_RECORDSET;
/
– Testing
VARIABLE resultSet REFCURSOR
EXEC DEC_RTN_RECORDSET(sysdate, :resultSet);
PRINT :resultSet
Following is the output. It is formatted for more readability.
Invoice_Number Invoice_Date Client_ID Invoice_Amt
————– ———— ——— ———–
1 25-JAN-07 101 1100
2 25-JAN-07 102 1100
3 25-JAN-07 103 1100
4 25-JAN-07 104 1100
MS SQL Server does not require any declaration of variable to return record set back to calling program from the stored procedure. By simply adding a SELECT statement at the end of the procedure for qualified columns, procedure will return data back to calling program since SQL Server handles all this internally which is indeed very convenient for programming purposes. Following is the code snippet of MS SQL Server stored procedure. It does not require specific open cursor command.
CREATE TABLE dbo.Invoice
(
INVOICE_NUMBER INT IDENTITY(1,1) NOT NULL,
INVOICE_DATE DATETIME NOT NULL,
CLIENT_ID INT NOT NULL,
INVOICE_AMT NUMERIC(9,2) DEFAULT 0 NOT NULL,
PAID_FLAG TINYINT DEFAULT 0 NOT NULL, — 0 Not paid/ 1 paid
CONSTRAINT PK_INVOICE PRIMARY KEY(INVOICE_NUMBER)
)
– FILEGROUP Clause
GO
INSERT INTO INVOICE(Invoice_date, client_ID, Invoice_Amt) VALUES(getDate(),101,1100.00);
INSERT INTO INVOICE(Invoice_date, client_ID, Invoice_Amt) VALUES(getDate(),102,1100.00);
INSERT INTO INVOICE(Invoice_date, client_ID, Invoice_Amt) VALUES(getDate(),103,1100.00);
INSERT INTO INVOICE(Invoice_date, client_ID, Invoice_Amt) VALUES(getDate(),104,1100.00);
Create following stored procedure either from Query analyzer or management studio.
CREATE PROC DEC_RTN_RECORDSET
(
@InvoiceDate DATETIME
)
AS
BEGIN
SET NOCOUNT ON
–Get the data from the Invoice table to return it to client application
SELECT Invoice_Number, Invoice_Date, Client_ID, Invoice_Amt
FROM Invoice
WHERE Invoice_date <= @InvoiceDate
ORDER BY Invoice_number;
SET NOCOUNT OFF
END
GO
– Run followig command to test the execution of the procedure.
DECLARE @CurrentDate datetime
set @CurrentDate = GETDATE()
exec DEC_RTN_RECORDSET @CurrentDate
Following is the output. It is formatted for more readability.
Invoice_Number Invoice_Date Client_ID Invoice_Amt
————– ———— ——— ———–
1 2007-01-25 15:21:22.300 101 1100
2 2007-01-25 15:21:22.300 102 1100
3 2007-01-25 15:21:22.300 103 1100
4 2007-01-25 15:21:22.300 104 1100
DB2 LUW also does not require any variable declaration to pass back to the calling program. But we need to DECLARE the cursor first and then OPEN the cursor. When we declare a cursor we have to use WITH RETURN TO clause in order to return record set from the procedure. If result set is to be returned to invoker of the procedure then we need to use WITH RETURN TO CALLER in declaration. If result set is to be returned to originating application then we need to use WITH RETURN TO CLIENT in declaration. Here we are assuming that we are returning result set to originating application and hence we are using WITH RETURN TO CLIENT.
Let us create table first and populate it with data. You can create it either via command editor or using command window. Please make sure that statement terminator is “@” or else you may receive an error (or replace with whatever statement terminator you have set in your programming tool).
CREATE TABLE Invoice
(
INVOICE_NUMBER INTEGER NOT NULL GENERATED BY DEFAULT AS
IDENTITY (START WITH +1, INCREMENT BY +1, CACHE 1000),
INVOICE_DATE TIMESTAMP NOT NULL,
CLIENT_ID INTEGER NOT NULL,
INVOICE_AMT DECIMAL(9,2) DEFAULT 0 NOT NULL,
PAID_FLAG SMALLINT DEFAULT 0 NOT NULL,
CONSTRAINT PK_INVOICE PRIMARY KEY(INVOICE_NUMBER)
)
@
INSERT INTO INVOICE(Invoice_date, client_ID, Invoice_Amt) VALUES(current timestamp,101,1100.00)@
INSERT INTO INVOICE(Invoice_date, client_ID, Invoice_Amt) VALUES(current timestamp,102,1100.00)@
INSERT INTO INVOICE(Invoice_date, client_ID, Invoice_Amt) VALUES(current timestamp,103,1100.00)@
INSERT INTO INVOICE(Invoice_date, client_ID, Invoice_Amt) VALUES(current timestamp,104,1100.00)@
Following is the procedure to return the result set. You can run it from Command Editor or save it in a file and run it from command window using following command.
db2 �td@ -f <filename> (filename in which following code is stored).
CREATE PROCEDURE DEC_RTN_RECORDSET
(
IN P_InvoiceDate TIMESTAMP
)
DYNAMIC RESULT SETS 1
LANGUAGE SQL
BEGIN
–Get the data from the table to display it back to client application
BEGIN
DECLARE c2 CURSOR WITH RETURN TO CLIENT FOR
SELECT Invoice_Number, Invoice_Date, Client_ID, Invoice_Amt
FROM Invoice
WHERE Invoice_date <= p_InvoiceDate
ORDER BY Invoice_number;
OPEN c2;
END;
END
@
– To test the procedure execute following command.
call DEC_RTN_RECORDSET(current timestamp)@
Following is the output. It is formatted for more readability.
Invoice_Number Invoice_Date Client_ID Invoice_Amt
————– ———— ——— ———–
1 2007-01-25-15.45.33.991000 101 1100
2 2007-01-25-15.45.34.054000 102 1100
3 2007-01-25-15.45.34.132000 103 1100
4 2007-01-25-15.45.34.210000 104 1100

