Resolving a singleton error in a stored procedure - sql

I have the following stored procedure in Firebird SQL:
ALTER PROCEDURE SP_REPORT_USD
(
PER SMALLINT
)
RETURNS
(
ACCOUNT_NUMBER CHAR(21),
AMOUNT NUMERIC(15, 4)
)
AS
BEGIN
SELECT
L.ACCOUNT_NUMBER, SUM(CURRROUND(L.DEBIT,2)-CURRROUND(L.CREDIT,2))
FROM
LEDGER L
WHERE
L.LEDGER_ACCOUNT = '31621' AND L.PERIOD = :PER
GROUP BY
L.ACCOUNT_NUMBER
INTO
ACCOUNT_NUMBER, AMOUNT;
SUSPEND;
END
When I run the following query:
SELECT * FROM SP_REPORT_USD('17')
I get the following error:
MULTIPLE ROWS IN SINGLETON SELECT
AT PROCEDURE 'SP_REPORT_USD' LINE: 15, COL: 1
Line 15 Col 1 is where my select statement starts when doing the stored procedure.
I did test the following query:
SELECT
L.ACCOUNT_NUMBER, INV.DESCRIPTION, SUM(-(CURRROUND(L.DEBIT,2) - CURRROUND(L.CREDIT,2)))
FROM
LEDGER L join INVENTORY INV ON L.ACCOUNT_NUMBER = INV.STOCK_CODE
WHERE
L.LEDGER_ACCOUNT = '31621' AND L.PERIOD = 17
GROUP BY
L.ACCOUNT_NUMBER, INV.DESCRIPTION
And the results where as expected. So I know my query logic is correct, I am just doing something wrong with the stored procedure.
Any assistance will be appreciated.

The problem is that inside a stored procedure, a SELECT statement is for selecting values from a single row only (a so-called singleton select). Your query is producing multiple rows, hence the error "multiple rows in singleton select".
If you want to produce multiple rows, you need to use the FOR SELECT statement, and the SUSPEND statement must be in the body of this FOR SELECT statement:
ALTER PROCEDURE SP_REPORT_USD(PER SMALLINT)
RETURNS (ACCOUNT_NUMBER CHAR(21), AMOUNT NUMERIC(15, 4))
AS
BEGIN
FOR
SELECT L.ACCOUNT_NUMBER, SUM(CURRROUND(L.DEBIT,2)-CURRROUND(L.CREDIT,2))
FROM LEDGER L
WHERE L.LEDGER_ACCOUNT = '31621' AND L.PERIOD = :PER
GROUP BY L.ACCOUNT_NUMBER
INTO :ACCOUNT_NUMBER, :AMOUNT
DO
BEGIN
SUSPEND;
END
END
The BEGIN...END around the SUSPEND; is optional in this case (as it is a single statement), but I prefer to include them always.

Related

Firebird query taking too long when using IN clause with Stored Procedure

For a report I had to write a recursive Stored Procedure GET_RECIPE_STEPS_ID(recipe_id). It returns ids of steps which are of type recipe. I.E.
SELECT GET_RECIPE_STEPS_ID.ID FROM GET_RECIPE_STEPS_ID(3189)
It Returns
3189
3190
3191
3192
When I run it on it's own It's quick (like 0.031sec execution time). But if it is to be used with IN clause in a query it takes ages. Like the following query took almost 12 minutes.
SELECT rs.RECIPEID
FROM RECIPESTEPS rs
WHERE rs.RECIPEID IN (select GET_RECIPE_STEPS_ID.ID from GET_RECIPE_STEPS_ID(3189))
Which is equivalent to the following query, and almost as quick as the stored procedure itself (0.038sec)
Select rs.RECIPEID
FROM RECIPESTEPS rs
WHERE rs.RECIPEID IN (3189, 3190, 3191, 3192)
Stored Procedure
CREATE OR ALTER PROCEDURE GET_RECIPE_STEPS_ID
(recipe_id integer)
RETURNS
(id integer)
AS
declare variable coType integer;
BEGIN
/* Recursive Procedure
* For Passed Recipe 'Recipe_id', it Returns the step's which are of type Recipe again.
*
* If any step is of type Recipe(i.e COTYPE = 1)
* Then it calls itself again for that step(Recipe)
*/
id =: recipe_id;
SUSPEND;
FOR SELECT rs.COMMODITYID, c.COTYPE
FROM RECIPESTEPS rs
LEFT JOIN COMMODITIES c ON c.COMMODITYID = rs.COMMODITYID
WHERE rs.RECIPEID =: recipe_id INTO :id, :coType
Do
BEGIN
IF(coType = 1)
THEN
FOR SELECT r.RECIPEID FROM RECIPES r WHERE r.LATEST = 1 AND r.COMMODITYID =:id into :id
DO
BEGIN
FOR SELECT GET_RECIPE_STEPS_ID.ID
FROM GET_RECIPE_STEPS_ID(:id) INTO :id
DO
BEGIN
SUSPEND;
END
END
END
END^
The problem is two-fold:
IN does not have very good performance to begin with and
In this situation the stored procedure gets executed for each row and not once like you would expect; I guess the Firebird optimizer doesn't infer that this stored procedure invocation is not correlated with the query.
It will probably perform better if you transform your query to use an INNER JOIN instead of IN:
select rs.RECIPEID
from GET_RECIPE_STEPS_ID(3189) grs
inner join RECIPESTEPS rs
on rs.RECIPEID = grs.ID
I assume that your real query might be more complex, because otherwise just select ID from GET_RECIPE_STEPS_ID(3189) would suffice.
The above query will behave slightly different than IN, for example if an ID occurs multiple times in the stored procedure output, it will also produce multiple rows now. You may need to adjust accordingly.

How to get row count of select statement inside stored procedure in SQL Server

I have requirement where I want get row count of select statement inside stored procedure and if row count is equal to 0 then only it will execute the next statement
select A, B, C
from AT
where B = 1
If the above statement returns any rows, then it will not execute further but if this statement do not have any row then it will execute next statement. I have tried it using in two ways
##rowcount - it's not working properly
Using temp table by inserting select statement into table getting row count of table but using temp table is not optimize way
Is there any solution?
You could use IF NOT EXISTS:
IF NOT EXISTS (select A,B,C from AT where B=1)
BEGIN
-- sth
END
is there any solution like getting into variable without hitting to database again and again
DECLARE #my_rowcount INT;
select A,B,C from AT where B=1;
SET #my_rowcount = ##ROWCOUNT; -- immediately after select get ##ROWCOUNT
...
IF #my_rowcount = 0
BEGIN
-- sth
END
EDIT:
##ROWCOUNT Globle variable for database so it may return wrong Value if any other select statement processed in other sp in same databe
Nope. ##ROWCOUNT:
Returns the number of rows affected by the last statement.
You could easily check it with your SSMS(open 2 tabs, select 1 and 2 rows on each of them and then get ##ROWCOUNT respectively).

Execute a stored procedure from within another stored procedure's SELECT statement?

I would like to execute a stored procedure X from within the SELECT statement of stored procedure Y, so that X's value can be returned as part of Y's data.
I am trying the following syntax, but it's apparently not valid.
SELECT name, type, (EXEC X #type=type)
FROM table
As I hope you can see above, I need to pass the current row's type value to procedure X to get the proper return value.
Disclaimer: I probably just don't know what I'm doing.
The approach what you have tried is invalid. Instead of the X as the stored procedure convert it as user-defined function. like the below
Create function dbo.fnGetTypeDetail
(
#type varchar(50)
)
returns varchar(100)
As
Begin
return --do your operation;
End
And replace your query as:
SELECT name, type, dbo.fnGetTypeDetail(type) AS TypeDetail
FROM table
For sample, I created a scalar function. Based on your requirement you can create inline table valued function as per the example
You can't EXEC a stored proc inside a SELECT statement.
What you can do is INSERT..EXEC a stored proc into a temp table, and then run a SELECT statement that queries that temp table, while joining to other tables if desired.
Psuedo-example:
INSERT INTO #Tmp (Column1) EXEC X;
SELECT Name, Type, (SELECT Column1 FROM #tmp)
FROM MyTable

SQL Looping through results

I do a select and it brings back a list of IDs. I then want to call another procedure for each ID that calculates a result, so I end up with a list of results.
How can I do this? I need some kind of loop but I am not very good at SQL.
Edit: Microsoft SQL 2008, and purely in SQL
Write a user defined function that takes in the ID and returns the calculated result you can then get that result for each ID with a query like this:
SELECT id, DatabaseYouUsed.dbo.functionYouWrote(id)
FROM DatabaseYouUsed.dbo.TableWithIDs
You can have a stored procedure that calls the select to get the IDs, use a cursor on the result list and call the other procedure for each ID. All inside a stored procedure.
If one row generates one result:
CREATE FUNCTION f(x int) RETURNS int AS BEGIN
RETURN x * 2
END
GO
SELECT id, x, dbo.f(x) FROM my_table
If one row might generate more than one result:
CREATE FUNCTION f(x int) RETURNS #r TABLE(result int) AS BEGIN
INSERT INTO #r VALUES(x)
INSERT INTO #r VALUES(x * 2)
RETURN
END
GO
SELECT t.id, t.x, r.result FROM my_table t CROSS APPLY dbo.f(t.x) r

Datasource returned from Informix Stored Proc

I have an Informix stored procedure that returns two columns and multiple rows. I can use "EXECUTE FUNCTION curr_sess(2009,'SP')" fine, but how do I get the results into a temp table.
EDIT: We are on version 10.00.HC5
Testing Jonathan Leffler's idea didn't work.
EXECUTE FUNCTION curr_sess(2009,'SP')
works fine. Then I did
CREATE TEMP TABLE t12(yr smallint, sess char(4));
But when I try
INSERT INTO t12 EXECUTE FUNCTION curr_sess(2009,'SP');
It doesn't work, I get a " Illegal SQL statement in SPL routine." error.
The source for curr_sess
begin procedure
DEFINE _yr smallint;
DEFINE _sess char(4);
SELECT
DISTINCT
sess_vw.yr,
sess_vw.sess,
sess_vw.sess_sort
FROM
sess_vw
ORDER BY
sess_vw.sess_sort DESC
INTO temp tmp_sess WITH NO LOG;
SELECT
FIRST 1
tmp_sess.yr,
tmp_sess.sess
FROM
tmp_sess
WHERE
tmp_sess.sess_sort = sess_sort(iYear,sSess)
INTO temp tmp_final WITH NO LOG;
FOREACH cursor1 FOR
SELECT
tmp_final.yr,
tmp_final.sess
INTO
_yr,
_sess
FROM
tmp_final
RETURN _yr, _sess WITH RESUME;
END FOREACH;
DROP TABLE tmp_sess;
DROP TABLE tmp_final;
end procedure
EDIT: sess_sort() does a lookup.
I have tried to rewrite the function as one query. Here is next_sess:
SELECT
FIRST 1
sess_vw.sess_sort
FROM
sess_vw
WHERE
sess_vw.sess_sort > sess_sort(2009,'SP')
ORDER BY
sess_vw.sess_sort ASC
Someone from IBM emailed me and suggested using something like this:
SELECT
*
FROM
TABLE(next_sess(2009,'SP'))
But that still didn't work.
One possibility is a stored procedure. Another (tested on IDS 11.50.FC1), which I wasn't sure would work, is:
CREATE PROCEDURE r12() RETURNING INT, INT;
RETURN 1, 2 WITH RESUME;
RETURN 2, 3 WITH RESUME;
END PROCEDURE;
CREATE TEMP TABLE t12(c1 INT, c2 INT);
INSERT INTO t12 EXECUTE PROCEDURE r12();
The last line is the important one.
Given the observation that the stored procedure cannot be executed as shown just above (because it contains some non-permitted SQL statement), then you need to use stored procedures another way - illustrated by this test code (which works: worked first time, which pleasantly surprised me):
CREATE TEMP TABLE t12(yr smallint, sess char(4));
CREATE PROCEDURE curr_sess(yearnum SMALLINT, sesscode CHAR(2))
RETURNING SMALLINT AS yr, CHAR(4) AS sess;
RETURN yearnum, (sesscode || 'AD') WITH RESUME;
RETURN yearnum, (sesscode || 'BC') WITH RESUME;
END PROCEDURE;
CREATE PROCEDURE r12(yearnum SMALLINT, sesscode CHAR(2))
DEFINE yr SMALLINT;
DEFINE sess CHAR(4);
FOREACH EXECUTE PROCEDURE curr_sess(yearnum, sesscode) INTO yr, sess
INSERT INTO t12 VALUES(yr, sess);
END FOREACH;
END PROCEDURE;
EXECUTE PROCEDURE r12(2009,'SP');
SELECT * from t12;
You could incorporate the creation of the temp table into the stored procedure; you could even arrange to drop a pre-existing table with the same name as the temp table (use exception handling). Given that you're using IDS 10.00, you are stuck with a fixed name for the temp table. It would be possible, though not recommended (by me) to use the dynamic SQL facility in 11.50 to name the temp table at runtime.
Be aware that stored procedures that access temporary tables get reoptimized when reused - the table that is used is not the same as the last time (because it is a temporary) so the query plan isn't all that much help.
That fails possibly because the 'drop table' is not valid statment in a procedure that is used in this context?
http://publib.boulder.ibm.com/infocenter/idshelp/v115/topic/com.ibm.sqls.doc/ids_sqs_1755.htm#ids_sqs_1755