Why doesn't this SQL run? Firebird stored procedure - sql

I execute the following simple query in IBExpert firebird2.5 and it works fine:
SELECT
pd.NOME_PRODUTO,
es.QTDE_MINIMA
FROM
TBL_ESTOQUE es,
TBL_PRODUTO pd
WHERE
es.qtde_estoque = 0
AND es.produto = pd.id
ORDER BY
pd.NOME_PRODUTO
But, if I create a stored procedure with two output parameters (see below)
begin
SELECT
pd.NOME_PRODUTO,
es.QTDE_MINIMA
FROM
TBL_ESTOQUE es,
TBL_PRODUTO pd
WHERE
es.qtde_estoque = 0
AND es.produto = pd.id
ORDER BY
pd.NOME_PRODUTO
into :nome_produto, :qtde_minima;
suspend;
end
I get a message like this:
multiple rows in singleton select. multiple rows in singleton
select. At proceddure 'SPD_SALDO_PROD_ZERADO_ESTOQUE' line: 7, col:3"
What is this? I don't understand what is happening...

FOR SELECT ...
INTO ...
DO SUSPEND;

rstrelba's answer shows how to fix the problem. If you want to understand it, here's what's going on.
suspend inside a Firebird stored procedure returns a row to the caller. It can be used in various different ways, and can be thought of as similar to Python's yield statement: suspend operation and send back a single value (or row, in this case), then continue when the caller asks for more data. (It has to be done this way for technical reasons, because database drivers don't always pull the entire result set all at once.)
You've got a select query that can return an arbitrary number of values, followed by a single suspend. Firebird is erroring out, telling you that this is probably wrong. What you want to do is put the select in a for loop, which will loop over the result set and suspend once for each row, as rstrelba's answer demonstrates. This makes sure that the caller gets all the results back.

Related

Execute PSQL procedure for multiple records matching criteria

I need to run a procedure manually for a large set of records that meet a certain criteria. It works fine if I manually include an ID but for some reason it does not work if I just tell it to get a list of ids from a table. No errors; but the missing data doesn't get added to a table from the procedure.
As far as I understand it, this should work fine but it doesn't:
select fa_sptl_cross(id, geom) from focus_area where generation_id = 3;
But this works fine
select fa_sptl_cross(id, geom) from focus_area where id = 312231;
Even this should work, should it not?
select fa_sptl_cross(id, geom) from focus_area
Ideas?
AFAIK, it cannot be the procedure that is the issue because trying to do this fails with any procedure I try
Please explain to me what does this function (fa_sptl_cross) does? If you can write the source code of this function that I could analyze this.
In general, I understand that when you are using where id = 312231 your query returns one record, that's why your function works fine. Because your function has an input parameter id. In other ways query return, many records, many id's, so and your function doesn't work. So I must view the source code of this function. I think so.
I got it to work using the information provided in PL/pgSQL perform vs execute
My solution is as follows:
DO $$
BEGIN
PERFORM habits4.fav_sptl_cross(focus_area_id, geom) from habits4.focus_area_version where generation_id = 3;
END;
$$;

Why is a query under a IF statement that is false running?

I have a application that uses a lot of string interpolation for SQL queries. I know it is a SQL injection threat, this is something that the customer and us know about and is hopefully something we can focus on next big refactor. I say that to make sense of the {Root Container.property} things that come from a GUI.
I have this query
IF ({Root Container.UserSelectedProduct}=1)
begin
DECLARE #TestNumbers {Root Container.SQLProductType};
INSERT INTO #TestNumbers SELECT * FROM {Root Container.DBTable};
SELECT *
FROM {Root Container.SQLProductFunction} (#TestNumbers)
WHERE [ID] = {Root Container.Level};
end
else
Select 0
Before a user selects a product it looks like this
IF (0=1)     
BEGIN
DECLARE #TestNumbers myDataType;
INSERT INTO #TestNumbers SELECT * FROM [MySchema].[TheWrongTable];     
SELECT * FROM [dbo].[myfunction] (#TestNumbers)
WHERE [ID] = 1;
END
ELSE
SELECT 0
Which is giving me the error:
Column name or number of supplied values does not match table definition.
I am aware why this error shows up, the table I am selecting from is not made for that data type.
However, why is it even attempting to run the first IF clause when I have IF (0=1) - how come this part is not just skipped and the SELECT 0 is only run? I would have thought that is how it was supposed to work, but I keep getting the error regarding column name/number not matching the table definition. When the user does select a Product and I get IF (1=1) and I have the appropriate table/function/datatype, it all works smoothly. I just don't know why it throws me an error prior when IF(1=0). Why does this happen/how can I get my intended behavior that everything inside my BEGIN\END under my first IF statement does not run unless the expression is true.
T-SQL is not interpreted. It must make sense regardless of what the runtime conditions are. It doesn't even do short-circuiting, in fact. Your code is invalid, and it doesn't matter that it's unreachable - T-SQL isn't going to ignore a piece of invalid code just because it could be eliminated, that's a thing that is a common source of bugs (e.g. in C++ where it's pretty common with templates).
Just make sure you still get valid SQL for the case where no product is selected; use the wrong table (or a helper table) if you have to.
The answer is simple: SQL code is fully compiled by the server before being executed, so this is basically a compile error. It's a bit like trying to compile the following in C#
if(someBoolWhichIsFalse)
intValue = "hello";
It's simply not valid.
The runtime code has not even been executed, it's still in the parsing and lexing stage. Nothing is being skipped, it just needs to be fully valid code, irrespective of runtime conditions.
This happens in every scope, i.e. on every call to a procedure or ad-hoc batch, that code must be compilable.

Stored Procedure delete statement after return

So I'm tracking down a potential bug in a sync process I'm in charge of (written by someone else). When viewing one of the stored procedures that is being called, I noticed something peculiar. Based on my understanding of returns, anything after the return will not be returned. However, I am not positive if this is the case in SQL. Based on the chunk of SQL below, will the delete statement ever run? Or does the SP return information to signify whether rows were deleted (such as how many rows, whether it was successful, etc.)? I am assuming this is a bug in the SP, but want to confirm before taking action. Thanks in advance.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[DeleteSalesforce_Contacts]
AS
Return
Delete From dbo.Contacts Where IsDeleted = 1
GO
The documentation is pretty clear on this:
"Exits unconditionally from a query or procedure. RETURN is immediate
and complete and can be used at any point to exit from a procedure,
batch, or statement block. Statements that follow RETURN are not
executed."
The delete statement won't be executed.
The return statement takes an optional parameter, but to use a query as value you would need to use a select in parentheses. Example:
return (select top 1 id from SomeTable)
The delete would never happen when the proc is executed.
The only time a statement after the return is ever executed when a proc is run is if it was related to a goto process and the code was sent there and bypassed the return. This kind of code sometimes used to be written before Try Catch blocks were allowed in SQL Server to do something with errors.

Suppress result sets from trigger

I have a sql statement that first updates, then selects:
UPDATE myTable
SET field1=#someValue
WHERE field2=#someValue2
SELECT 1 returnValue
The process that consumes the reults of this statement is expecting a single result set, simple enough.
The problem arises because an update trigger was added to the table that produces a result set, i.e. it selects like so:
SELECT t_field1, t_field2, t_field3 FROM t_table
The obvious solution is to split up the statments. Unfortunatley, the real world implementation of this is complex and to be avoided if possible. The trigger is also nessecary and cannot be disabled.
Is there a way to supress the results from the update, returning only the value from the select statement?
The ability to return result sets from triggers is deprecated in SQL Server 2012 and will be removed in a future version (maybe even in SQL Server 2016, but probably in the next version). Change your trigger to return the data in some other way. If it is needed just for debugging, use PRINT instead of SELECT. If it is needed for some other reasons, insert the data into a temporary table and perform the SELECT from the calling procedure (only when needed).

Why doesn't this PL/SQL procedure work?

I have a cursor which returns two values: one which I will use (and therefore will assign to an out variable) and another which I've only had returned to make the ROWNUM thing work.
If I run the cursor as a query, it works as expected. But if I execute the procedure the out variable comes empty. Is my approach somehow not supported? I mean, returning two values but only using one of them?
Here is my procedure code: (Don't delve too much on the query itself. It works, I know it's a bit ugly but it works. It was the only way I found to return the second-last row)
procedure retorna_infos_tabela_164(i_nip in varchar,
o_CODSDPANTERIOR out number) is
cursor c_tabela_164 is
select *
from(
select CODSDP,ROWNUM rn
from
(
select NRONIP,CODTIPOMOV,CODSDP
from TB164_HISTORICOMOVIMENTACOES
where NRONIP = i_nip and
CODTIPOMOV='S1'
order by DTHMOV desc
)
)
where rn=2;
v_temp_nr number;
begin
open c_tabela_164;
fetch c_tabela_164 into o_CODSDPANTERIOR,v_temp_nr;
close c_tabela_164;
end retorna_infos_tabela_164;
EDIT The way I've tried to run this procedure was by dbms_output.put_line(o_CODSDPANTERIOR) which didn't work. Then I googled a little bit and saw I should TO_CHAR() my var first and then have it output. Didn't work either.
There's no problem with passing a number to DBMS_OUTPUT.PUT_LINE. Oracle will silently convert other built-in types to VARCHAR2 using the default format. You only need to use TO_CHAR if you want to control the format used -- which is often a good idea, but not generally necessary.
One possibility, though, is that you are not seeing the output because you have not enabled it. If you are running your test in SQLPlus, make sure you SET SERVEROUTPUT ON before running code that includes DBMS_OUTPUT calls. If you are using some other client, consult its documentation for the proper way to enable DBMS_OUTPUT. (You can of course test if it's enabled by adding another call to output a string literal.)
There's nothing inherently wrong with the technique you're using to populate the out parameter. However, it's not necessary to return two columns from the cursor; your select * could simply be select CODSDP. You seem to be under the misconception that any column referenced in the predicates has to be in the select list, but that's not the case. In your innermost query, the select list does not need to include NRONIP or CODTIPOMOV, because they are not referenced in the outer blocks; the WHERE clause in that query can reference any column in the table, regardless of whether it is in the select list.
So, my first guess is that you simply don't have server output enabled. The only other possibility I can think of right now is that you're running your query and the procedure in two different sessions, and one of them has uncommitted transaction against the table, so they are actually seeing different data.
If those suggestions don't seem to be the problem, I'd suggest you run your tests of the standalone query and the procedure in a single SQLPlus session, then copy and paste the entire session here, so we can see exactly what you're doing.
I'm sorry I've had you guys take the time to answer me when the answer was something to do with the tool I'm using. I hope all you guys have learnt something.
The query does work for me at least, I've not come across any edge cases where it doesn't work, but I haven't tested it exhaustively.
The problem was that TOAD, the tool I'm using to run the procedures, sometimes populates the procedures with the parameters I tell it to but sometimes it doesn't. The issue here was that I was trying to execute the procedure with no parameters, yielding no results...
Lesson Learnt: double check the generated procedure code when you run a Procedure using Right Click > Run Procedure on TOAD version 9.