How do I return all values from a stored procedure? - sql

Forgive my naivety, but I am new to using Delphi with databases (which may seem odd to some).
I have setup a connection to my database (MSSQL) using a TADOConnection. I am using TADOStoredProc to access my stored procedure.
My stored procedure returns 2 columns, a column full of server names, and a 2nd column full of users on the server. It typically returns about 70 records...not a lot of data.
How do I enumerate this stored procedure programmatically? I am able to drop a DBGrid on my form and attach it to a TDataSource (which is then attached to my ADOStoredProc) and I can verify that the data is correctly being retrieved.
Ideally, I'd like to enumerate the returned data and move it into a TStringList.
Currently, I am using the following code to enumerate the ADOStoredProc, but it only returns '#RETURN_VALUE':
ADOStoredProc1.Open;
ADOStoredProc1.ExecProc;
ADOStoredProc1.Parameters.Refresh;
for i := 0 to AdoStoredProc1.Parameters.Count - 1 do
begin
Memo1.Lines.Add(AdoStoredProc1.Parameters.Items[i].Name);
Memo1.Lines.Add(AdoStoredProc1.Parameters.Items[i].Value);
end;

Call Open to get a dataset returned
StoredProc.Open;
while not StoredProc.EOF do
begin
Memo1.Lines.Add(StoredProc.FieldByName('xyz').Value);
StoredProc.Next;
end;

Use Open to get the records from the StoredProc
Use either design-time Fields, ad-hoc Fields grabbed with FieldByName before the loop or Fields[nn] to get the values.
procedure GetADOResults(AStoredProc: TADOStoredProc; AStrings: TStrings);
var
fldServer, fldUser: TField;
begin
AStoredProc.Open;
fldServer := AStoredProc.FieldByName('ServerName');
fldUser := AStoredProc.FieldByName('User');
while not AStoredProc.EOF do
begin
AStrings.Add(Format('Server: %s - / User: %s',[fldServer.AsString, fldUser.AsString]));
// or with FFields and Index (asumming ServerName is the 1st and User the 2nd) and no local vars
AStrings.Add(Format('Server: %s - / User: %s',[AStoredProc.Fields[0].AsString, AStoredProc.Fields[1].AsString]));
AStoredProc.Next;
end;
end;
//use like
GetADOResults(ADOStoredProc1, Memo1.Lines);
Note: Fields[nn] allows to write less code but beware if the StoredProc changes the order of the returned columns.

If your stored procedure returns a result set (rows of data), don't use ExecProc. It's designed to execute procedures with no result set. Use Open or Active instead, and then you can process them just as you are using Parameters:
ADOStoredProc.Open;
for i := 0 to ADOStoredProc1.Parameters.Count - 1 do
begin
Memo1.Lines.Add(ADOStoredProc1.Parameters.Items[i].Name);
Memo1.Lines.Add(ADOStoredProc1.Parameters.Items[i].Value);
end;
BTW, calling Open and then ExecProc causes problems; Open returns a result set, ExecProc then clears it because you're running the procedure a second time with no result set expected. I also don't think you need the Parameters.Refresh, but I'm not 100% sure of that.

Take a look at this (just Googled it):
[http://www.scip.be/index.php?Page=ArticlesDelphi12&Lang=EN#Procedure][1]
Basically, a SQL Server stored procedure always returns one return value, but it can also create a result set, which you need to process like the data set returned from a regular select statement.

Related

Nested stored procedure, setting variable to output/result

In DB2 I'm currently running a stored procedure, inside of which I'm calling another stored procedure with a parameter.
P1 : BEGIN ATOMIC
DECLARE V_ID INTEGER ;
CALL PROGRAMS. GET_ID_BY_NAME(P_NAME) ;
-- need output of above procedure to be set to V_ID for another call here
END P1
How can I set the result of that call to the variable V_ID so that I can use it down the line?
If the relationship between ID and NAME is 1:1 , or if you simply don't care which matching name gets returned, then it is easier to modify the stored procedure (or create a renamed clone of the stored-procedure and modify the cloned copy) so that the stored procedure has an OUTPUT parameter, which the stored procedure can simply SET to the matching name, and on return the caller stored-procedure simply uses that variable name to access its value.
You may want to defensively code for a no match by arranging that the output parameter is set to NULL for that case, and test for that in the calling code. There are plenty of examples of using stored-procedure output parameters if you search, either the official Db2 documentation or elsewhere.
Using an output-parameter from the stored procedure (instead of a result-set) is the simplest approach, fewer lines of code.
Otherwise, if the relationship between ID and NAME is 1:n, and the caller wants to choose which match, then the called-stored procedure should return a result set (0 or more rows ) via an open WITH RETURN cursor. Your caller code would need to handle both the 0 rows returned case, and choose which row to use (if more than one row is returned) if you want to choose between them (usually on the basis of another column in the result-set).
To access the result-set of a called stored-procedure from inside the caller procedure, your caller procedure needs to use statements like these [ASSOCIATE RESULT SET LOCATOR][1] (v1) WITH PROCEDURE PROGRAMS.GET_ID_BY_NAME, after the CALL PROGRAMS.GET_ID_BY_NAME(), then [ALLOCATE c1 CURSOR FOR RESULT SET][1] v1.
You now have a cursor which you can iterate over to FETCH each row into variables, and at end you must CLOSE the cursor. If you know there is only ever be a single row in the result set then you don't need to iterate.
If you search, there are plenty of examples of these statements, including in the IBM documentation for your Db2 version and platform. I have shown links for Db2-LUW.

How to suppress record sets returned by SELECT statements in a Stored Procedure

I'm writing a stored procedure which checks for the existence of various tables in various databases, as well as the permissions that the user executing the stored procedure has on those tables. The stored procedure itself resides within a user database (i.e. it's not in the Master db).
To perform my checks, my stored procedure contains lots of SELECT statements. Each of those obviously returns a record set. What I would like is to somehow suppress these record sets so that they are not returned by the stored procedure, and instead return my own, single record set which is just a collection of messages relating to each check the stored procedure performs.
I think the obvious answer is to use a table-valued function instead, but I've not been able to recreate my tests successfully in a Function as they appear in the stored procedure. For starters, I'm having to use temporary tables (not possible in a function) and dynamic SQL (not very compatible with table parameters).
I think I've basically got two choices:
Rewrite my stored procedure as a function and figure out how to do the checks a different way.
Continue using my stored procedure and use an OUTPUT parameter to return my result messages, probably as a delimited string, and in the associated ASP.NET application just ignore all the record sets the stored procedure returns .
Neither of these solutions is very satisfactory. Before I spend any more time pursuing either one, is there a way to discard the record sets produced by the SELECT statements in a stored procedure and explicitly define what record I want it to return?
Hmm, I only can speculate here...
Are you using something like
SELECT ...;
IF ##rowcount > 0
BEGIN
...
END;
?
Then you can rewrite it using something like
IF EXISTS (SELECT ...)
BEGIN
...
END;
or
DECLARE #variable integer;
SELECT #variable = count(*) ...;
IF #variable > 0
BEGIN
...
END;
In general point the results of your queries to a target (variable, table, expression, ...), then they don't get outputted.
And then just execute the query for your desired result in the end.
In my opinion, here is almost no reason to have stored procedures produce record sets. That is what stored functions are for. On occasion, it is needed, because of the use of dynamic SQL or other stored procedures, but not as a general practice. Much, much too often, I see stored procedures being used where stored functions or views are more appropriate.
What should you do? Even SELECT statement in the stored procedure should be one of the following:
Setting (local) variables.
Saving the results in a temporary table or table variable.
The logic for the stored procedure should be working on the local variables. The results should be returned using OUTPUT parameters.
If you need to return rows in a tabular format, you can do that using tables explicitly (such as a global temporary table or real table). Or, you can have one SELECT at the end that does return a single result set. However, if you need this and can phrase the stored procedure as a function, that is better in my opinion.

How should I select or display the out variable from a stored procedure call?

I'm writing a pretty basic stored procedure that just takes values from the sample DB2 database and computes the standard deviation. I wrote the procedure itself just fine, and I can call it without error. But I can't figure out how to actually display my result or select it in a statement. Everything I try results in a syntax error and I haven't been able to find anyone doing this specific task in my google searches.
This is the gist of my code (snipped for brevity):
CREATE PROCEDURE SAL_STD_DEV
(OUT std_dev real)
LANGUAGE SQL
BEGIN
--do stuff
SET std_dev = 10; --changed for simplicity
END#
CALL SAL_STD_DEV(?)#
All this runs, but just CALL doesn't create any output. What's the syntax to SELECT the out variable? I can't put a DECLARE before the CALL because it's not in a stored procedure, and PRINT doesn't work either.
(# is my terminal character because I'm using ; in the stored procedure)
Edit: Both the create procedure and call statements are made in the same SQL file, the database is connect to through localhost and I'm using DB2 11.1.0.1527 and developing in IBM Data Studio 4.1.2.
From wherever the CALL is being made, that feature might present a Result Set, despite apparently not presenting the result of an OUT parameter. If so, then the stored procedure perhaps could be revised to return the OUT value [instead, or additionally] as a result set, so that the interface that accepts the CALL statement as input, might present that result-set. Regardless:
In a statement processor [e.g. that is not a GUI, but] for which SELECT query output is presented, the following scripted requests should likely suffice:
create variable my_real real
;
call SAL_STD_DEV(my_real)
;
select my_real from sysibm.sysdummy1
;

Run an oracle SQL script twice with different parameters

I have an SQL statement in Oracle SQL developer that has some variables:
DEFINE custom_date = "'22-JUL-2016'" --run1
DEFINE custom_date = "'25-JUL-2016'" --run2
SELECT * FROM TABLE WHERE date=&custom_date
The real query is much more complicated and has many more variables and new tables are created from the results of the query. How can I create a script so that the query is executed twice, the first time with the custom date set as in the first line and the second time as in the second line.
In Oracle, the &variable is a "substitution variable" and is not part of SQL; it is part of the SQL*Plus scripting language (understood by SQL Developer, Toad etc.)
The better option, and what you are asking about, is BIND VARIABLES. The notation is :variable (with a colon : instead of &), for example :custom_date.
The difference is that a substitution variable is replaced by its value in the front-end application (SQL Developer in your case) before the query is ever sent to the Oracle engine proper. A bind variable is substituted at runtime. This has several benefits; discussing them is outside the scope of your question.
When you execute a query with bind variables in SQL Developer, the program will open a window where you enter the desired values for the bind variables. You will have to experiment with that a little bit till you can make it work (for example I never remember if a date must be entered with the single quotes or without). Good luck!
Define is used in TRANSACT SQL. To do this Oracle way, You can create anonymus PL/SQL block, similar to this:
DECLARE
p_param1 DATE;
p_param2 NUMBER;
CURSOR c_cur1(cp_param1 DATE,cp_param2 NUMBER)
IS
SELECT * FROM table WHERE date = cp_param1
;
BEGIN
-- Execute it first time
p_param1 := TO_DATE('2016-09-01','YYYY-MM-DD');
FOR r IN c_cur1(p_param1)
LOOP
NULL;
END LOOP;
-- Execute it second time
p_param1 := TO_DATE('2015-10-11','YYYY-MM-DD');
FOR r IN c_cur1(p_param1)
LOOP
NULL;
END LOOP;
END;
And in it, You create cursor with parameters and execute it twice with different parameter.
I do not know why You want to execute this query twice, so the script abowe does nothing with results, but it certainly should execute Your query twice, with different params.

Any SIMPLE way to fetch ALL results from a PL/SQL cursor?

The second part of the question: How to do the same (get ALL results, without any loops) with SQL*Plus.
I'm writing some PL/SQL scripts to test the data integrity using Jenkins.
I'm having a script like this:
declare
temp_data SOME_PACKAGE.someRefCurFunction; // type: CURSOR
DATA1 NUMBER;
DATA2 NUMBER;
DATA3 SOMETHING.SOMETHING_ELSE%TYPE;
begin
cursor := SOME_PACKAGE.someFunction('some',parameters,here);
LOOP
FETCH cursor INTO DATA1,DATA2,DATA3;
EXIT WHEN temp_data%NOTFOUND;
dbms_output.put_line(DATA1||','||DATA2||','||DATA3);
END LOOP;
end;
Relsults look like this:
Something1,,Something2
Something3,Something4,Something5
Something6,Something7,Something8
Sometimes the results are null, as in the 1st line. It doesnt matter, they should be.
The purpose of this script is simple - to fetch EVERYTHING from the cursor, comma separate the data, and print lines with results.
The example here is simple as hell, but It's just and example. The "Real life" Packages contain sometimes hundreds of variables, processing enormous database tables.
I need it to be as simple as possible.
Is there any method to fetch EVERYTHING from the cursor, comma separate single results if possible, and send it to output? The final output in the Jenkins test should be a text file, to be able to diff it with other results.
Thanks in advance :)
If you're truly open to a SQL*Plus script, rather than a PL/SQL block
SQL> set colsep ','
SQL> variable rc refcursor;
SQL> exec :rc := SOME_PACKAGE.someFunction('some',parameters,here);
SQL> print rc;
should execute the procedure and fetch all the data from your cursor. You could spool the resulting CSV output to a file using the spool command. Of course, you then may encounter issues where SQL*Plus isn't displaying the data in a clean format because of the linesize or other similar issues-- that may force you to add some additional SQL*Plus formatting commands (i.e. set linesize, column <<column name>> format <<format>>, etc.)
It's not obvious to me that a SQL*Plus script buys you much over writing some dynamic SQL that generates the PL/SQL script that you posted initially or (if you're on 12c) writing some code that uses dbms_sql to fetch data from the cursor that is returned.
The answer seems obvious. You currently have a function which returns a cursor itself returning a data set of hundreds (tho you show only three) fields. You want instead a single string with the comma-separated values. So change the function or write another one based on the same query.
package body SOME_PACKAGE
...
-- The current function
function someFunction ...
returns ref_cursor ...
-- create cursor based on:
select f1, f2, f3 --> Three (or n) fields
from ...
where ...;
return generated_cursor;
end function;
-- The new function
function someOtherFunction ...
returns ref_cursor ...
-- create cursor based on:
select f1 || ',' || f2 || ',' || f3 as StringField --> one string field
from ...
where ...;
return generated_cursor;
end function;
end package;
This isn't quite all that you asked for. It does save declaring variables (one instead of hundreds) to read the data in one row at a time, but you still read it in one row at a time instead of, as I read your question, reading in every row in one operation. But if such a super-fetch were possible, it would require massive amounts of memory. Sometimes we do things that just require massive amounts of memory and we just work with that the best we can. But your "requirement" seems to be only a matter of convenience for the developers. That, imnsho, lies way down in the list of priorities for consuming resources.
Developing a cursor that returns the data in the final form you want would seem to me to the best of all alternatives.