Exception when trying to execute a query from pascal - firebird

The following function takes the cuits (the cuit is like social security number) from a grid and inserts them into a temporary table. I'm using Delphi XE7 and Firebird 2.5.
function TfImportFileARBARetenPercep.fxListClientFromGrid(
pboClient: Boolean): WideString;
var
wsAux : WideString;
stTable, stCuit: String;
qCliProv, qCuitsExcl, qCuit : TFXQuery;
niRow : Integer;
begin
wsAux := '';
qCuitsExcl.SQL.Text := 'Create global temporary table TempExcl (cuitExcl varchar(13)) on commit delete rows';
qCuitsExcl.ExecSQL;
if pboClient then
begin
stTable := 'Client'
end
else
begin
stTable := 'Prov';
end;
for niRow := 1 to gDetails.RowCount - 1 do
if Trim(gDetails.Cells[3,niRow]) <> '' then
Begin
stCuit := QuotedStr(Trim(gDetails.Cells[3,niRow]));
qCuit.SQL.Text := 'Insert into TempExcl(:cuitExcl)';
qCuit.ParamByName('cuitExcl').AsString := stCuit;
qCuit.ExecSQL;//←←←←←←←←←←←←←←←←this line throws the exception
End;
qCuitsExcl.SQL.Text :='';
qCuitsExcl.SQL.Text := 'commit;';//to commit previous inserts
qCuitsExcl.SQL.Add('drop table TempExcl;');//in case the table is not deleted when the connection is closed
qCuitsExcl.SQL.Add('commit;');//commit previous drop
qCuitsExcl.ExecSQL;
//the rest of the code only returns the list of cuits that were loaded
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
try
qCliProv.SQL.Text :=
' Select Code' +
' From ' + stTable +
' Where Active = 1 ';
if gDetails.RowCount > 0 then
begin
qCliProv.SQL.Add('And Cuit Not In (Select cuitExcl from TempExcl)');
end;
qCliProv.Open;
wsAux := '';
while not qCliProv.Eof do
begin
wsAux := wsAux + iif((wsAux = ''), '', ',') + qCliProv.FieldByName('Code').AsString;
qCliProv.Next;
end;
Result := wsAux;
finally
FreeAndNil(qCliProv);
FreeAndNil(qCuit);
FreeAndNil(qCuitsExcluidos);
end;
end;
When trying to execute the line qCuit.ExecSQL; the following exception is thrown:
Project ERP.exe raised exception class EIBNativeException with message
'[FireDAC][Phys][FB] Dynamic SQL Error SQL error code = -104 Token
unknown - line 1, column 27 ?'.
I don't know why it throws this exception

The problem is that this is wrong:
qCuit.SQL.Text := 'Insert into TempExcl(:cuitExcl)';
This will result in a generated query that is:
Insert into TempExcl(?)
Which is a syntax error (the "Token unknown"), because Firebird doesn't expect a question mark (parameter placeholder) in this position, it expects an identifier (column name).
A valid query would be either:
Insert into TempExcl (cuitExcl) values (?)
or:
Insert into TempExcl values (?)
The first form is preferred, as you explicitly specify the columns, though in this case not really necessary as the table has one column anyway.
In other words, you need to use:
qCuit.SQL.Text := 'Insert into TempExcl (cuitExcl) values (:cuitExcl)';
As an aside, I'm not familiar with Delphi or how and when queries are prepared, but it might be better to move this statement out of the loop, so it is prepared only once (in case Delphi prepares the statement again any time the qCuit.SQL.Text is assigned a value).
The comment "// in case the table is not deleted when the connection is closed" seems to indicate a lack of understanding of Global Temporary Tables. They are intended to be permanent objects, available for re-use by your application. That is on commit delete rows makes the content available only to your current transaction, while on commit preserve rows will make the content available for the remainder of your connection (and only your connection), deleting the data when the connection is closed. Creating and dropping a GTT in something that is executed more than once is an indication something is wrong in your design.

Related

Convert SQLERRM( SQLCODE ) in Oracle to PostgreSQL

I'm converting Oracle to PostgreSQL, I haven't alternative for SQLERRM( SQLCODE ) like Oracle in PostgreSQL.
DECLARE
name employees.last_name%TYPE;
v_code NUMBER;
v_errm VARCHAR2(64);
BEGIN
SELECT last_name INTO name FROM employees WHERE employee_id = 1000;
EXCEPTION
WHEN OTHERS THEN
v_code := SQLERRM( SQLCODE );
v_errm := SUBSTR(SQLERRM, 1 , 64);
DBMS_OUTPUT.PUT_LINE('The error code is ' || v_code || '- ' || v_errm);
END;
Look at the documentation:
Within an exception handler, the special variable SQLSTATE contains the error code that corresponds to the exception that was raised (refer to Table A.1 for a list of possible error codes). The special variable SQLERRM contains the error message associated with the exception. These variables are undefined outside exception handlers.
Within an exception handler, one may also retrieve information about the current exception by using the GET STACKED DIAGNOSTICS command, which has the form:
GET STACKED DIAGNOSTICS variable { = | := } item [ , ... ];
Each item is a key word identifying a status value to be assigned to the specified variable (which should be of the right data type to receive it). The currently available status items are shown in Table 43.2.
So that could be
v_code := SQLSTATE;
v_errm := substr(SQLERRM, 1, 64);

Using a select in the set of an update pgsql

Hello i'm wondering why I can't use an SELECT in my SET of an UPDATE like this :
UPDATE "WU_Users" SET (SELECT "colonnesName" FROM colonnes WHERE "id" = i) = '0/0' WHERE "IDWU_Users" = (SELECT "idUser" FROM query WHERE "id" = i);
I get this error :
erreur de syntaxe sur ou près de « SELECT »
LIGNE 22 : UPDATE "WU_Users" SET (SELECT "colonnesName" FRO...
Apparently you are attempting to derive the column name to be updated from a column in another table. However, parsing an SQL statement requires the structural components (table names, column name, view names, ...) to be fixed before the statement can be parsed. What you are attempting would require parsing part of the statement inside parsing the statement. Currently, that is not going to happen. To accomplish what you are attempting will require dynamic SQL in order to generate the UPDATE statement at run time. Something like:
create or replace
procedure update_wu_users_from_idwu_users(i integer) -- assumes i as parameter as it is not defined
language plpgsql
as $$
declare
k_update_stmt constant text =
$stmt$ update wu_users set %I = '0/0'
where wu_users =
(select iduser
from query -- assumes query is a table, view, or materalized view
where id = i
)
$stmt$;
l_update_column text;
l_update_stmt text;
begin
select colonnesname
into l_update_column
from columns
where id = i;
if not found then
raise exception 'No column name found on table colonnes for id=%',i;
end if;
l_update_stmt = format(k_update_stmt,k_update_stmt);
raise notice '%', l_update_stmt; -- for debigging for prod raise_log might be better
execute l_update_stmt;
end;
$$;
NOTE: In the absence of table definition and test data the above IS NOT tested. Nor will I quarantine it complies. It is offered as example.

Delphi/FireDac + PostgreSQL + stored procedure + Procedure (To call a procedure, use CALL)

We are putting postgreSQL database support on our system, we currently work with Oracle.
We are trying to call a procedure in the database, just as we call in Oracle.
var conBanco := TFDConnection.Create(Self);
try
conBanco.Params.Database := 'Database';
conBanco.Params.UserName := 'UserName';
conBanco.Params.Password := 'Password';
conBanco.Params.Values['Server'] := 'IP';
conBanco.Params.DriverID := 'PG';
conBanco.Open();
var stpBanco := TFDStoredProc.Create(Self);
try
stpBanco.Connection := conBanco;
stpBanco.StoredProcName := 'gerador_padrao';
stpBanco.Prepare;
stpBanco.ParamByName('gerador').Value := 'pessoas';
stpBanco.ExecProc();
ShowMessage(VarToStrDef(stpBanco.ParamByName('parchave').Value, ''));
finally
stpBanco.Free;
end;
finally
conBanco.Free;
end;
However we get the following error:
exception class : Exception
exception message : EPgNativeException: [FireDAC][Phys][PG][libpq]
ERROR: superus.gerador_padrao(gerador => character varying, parchave
=> numeric) is a procedure. To call a procedure, use CALL.
Stored procedure in database:
CREATE OR REPLACE PROCEDURE superus.gerador_padrao
(gerador character varying, INOUT parchave numeric)
LANGUAGE plpgsql
AS $procedure$
begin
--Code
end;
$procedure$
;
The error occurs on the following line:
stpBanco.Prepare;
The above code works perfectly in Oracle, how do I call the procedure in PostgreSQL?
Thank you.
Below we will talk about Delphi 10.2.3, maybe in later versions it's different.
Inside the file FireDAC.Phys.PGMeta.pas has a TFDPhysPgCommandGenerator.GetStoredProcCall method, inside which the procedure text is formed and there is no option to call the procedure via CALL. Moreover, there is no "CALL " text in this entire file. Maybe this is a mistake, or maybe some kind of cunning idea... But that doesn't make it any easier for us.
// function returns void
if not lHasOut then begin
if FCommandKind = skStoredProc then
FCommandKind := skStoredProcNoCrs;
// NULL to avoid problem with execution of a
// void function in binary result format
Result := 'SELECT NULL FROM ';
end
else begin
if lHasCrs and (FCommandKind = skStoredProc) then
FCommandKind := skStoredProcWithCrs
else if lFunc then
lFunc := FCommandKind in [skStoredProc, skStoredProcNoCrs];
if lFunc then
Result := 'SELECT '
else
Result := 'SELECT * FROM ';
end;
In total, here I see 2 options:
Form the procedure call string yourself. I'm sure it's not difficult, although, of course, it's more correct when the component does it.
Instead of a procedure, make a function with the same content, and make your output parameter the result. It is important to remember - the procedure call then must pass through ExecFunc in this case;

Firebird run execute statement inside trigger

I wrote a procedure STRING_SESTAVLJEN_ENAKOST_TABEL('MERILA_STRANKE') that generates part of code that i want to execute (some long if statement)
IF ((new.LOKACIJA IS DISTINCT FROM old.LOKACIJA )
OR (new.MODIFIED IS DISTINCT FROM old.MODIFIED )
OR (new.KARAKTERISTIKE IS DISTINCT FROM old.KARAKTERISTIKE )
OR (new.LETNIK IS DISTINCT FROM old.LETNIK )
OR (new.ID_PNS_CERT_POS IS DISTINCT FROM old.ID_PNS_CERT_POS )
OR (new.ID_PNS_CERT_POS IS DISTINCT FROM old.ID_PNS_CERT_POS ))
and I want to call it in trigger, then add some code and run it all together.
The code is:
SET TERM ^ ;
ALTER TRIGGER BI_MERILA_STRANKE ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS
declare variable besedilo_primerjave varchar(5000);
BEGIN
begin
if (new.ID_MERILA_STRANKE is null OR new.ID_MERILA_STRANKE = 0) then new.ID_MERILA_STRANKE = gen_id(GEN_ID_MERILA_STRANKE,1);
end
begin
execute procedure STRING_SESTAVLJEN_ENAKOST_TABEL('MERILA_STRANKE')
returning_values :besedilo_primerjave;
execute statement besedilo_primerjave || ' THEN BEGIN INSERT INTO SYNC_INFO(TABLE_NAME,ID_COLUMN_NAME,ID_VALUE,DATETIME)
VALUES (
''MERILA_STRANKE'',
''ID_MERILA_STRANKE'',
NEW.ID_MERILA_STRANKE,
CURRENT_TIMESTAMP
);
END ELSE BEGIN
exception ENAK_RECORD;
END';
end
END^
SET TERM ; ^
Now when I run the update and trigger triggers I get this error:
SQL Message : -104 Invalid token
Engine Code : 335544569 Engine Message : Dynamic SQL Error SQL
error code = -104 Token unknown - line 1, column 1 IF
On the other hand if I write it like this:
SET TERM ^ ;
ALTER TRIGGER BI_MERILA_STRANKE ACTIVE
BEFORE INSERT OR UPDATE POSITION 0
AS
BEGIN
begin
if (new.ID_MERILA_STRANKE is null OR new.ID_MERILA_STRANKE = 0) then new.ID_MERILA_STRANKE = gen_id(GEN_ID_MERILA_STRANKE,1);
end
begin
IF ((new.LOKACIJA IS DISTINCT FROM old.LOKACIJA )
OR (new.MODIFIED IS DISTINCT FROM old.MODIFIED )
OR (new.KARAKTERISTIKE IS DISTINCT FROM old.KARAKTERISTIKE )
OR (new.LETNIK IS DISTINCT FROM old.LETNIK )
OR (new.ID_PNS_CERT_POS IS DISTINCT FROM old.ID_PNS_CERT_POS )
OR (new.ID_PNS_CERT_POS IS DISTINCT FROM old.ID_PNS_CERT_POS ))
THEN BEGIN
INSERT INTO SYNC_INFO(TABLE_NAME,ID_COLUMN_NAME,ID_VALUE,DATETIME)
VALUES (
'MERILA_STRANKE',
'ID_MERILA_STRANKE',
NEW.ID_MERILA_STRANKE,
CURRENT_TIMESTAMP
);
END ELSE BEGIN
exception ENAK_RECORD;
END
end
END^
SET TERM ; ^
It works as it should. I do not understand why it doesn't run if is more or less the same code.
As I also mentioned in your previous question, execute statement cannot be used to execute snippets of PSQL (procedural SQL) like that, it can only execute normal DSQL (dynamic SQL). And as it doesn't understand PSQL, you get the "token unknown - if" error, because if is not valid in DSQL.
execute statement is equivalent to executing SQL yourself from a query tool or application (it uses the same API), you can't use if there either.
There is a loophole by using an execute block statement, but that still would not allow you to gain access to the NEW (or OLD) trigger context variables unless explicitly passed as parameters, which would negate most of the usefulness of dynamically generated code in this context.
The only real solution is to write the trigger and not do it dynamically, maybe using a code generator (I'm not sure if any exist, otherwise you need to write that yourself).

Variable containing the number of rows affected by previous DELETE? (in a function)

I have a function that is used as an INSERT trigger. This function deletes rows that would conflict with [the serial number in] the row being inserted. It works beautifully, so I'd really rather not debate the merits of the concept.
DECLARE
re1 feeds_item.shareurl%TYPE;
BEGIN
SELECT regexp_replace(NEW.shareurl, '/[^/]+(-[0-9]+\.html)$','/[^/]+\\1') INTO re1;
RAISE NOTICE 'DELETEing rows from feeds_item where shareurl ~ ''%''', re1;
DELETE FROM feeds_item where shareurl ~ re1;
RETURN NEW;
END;
I would like to add to the NOTICE an indication of how many rows are affected (aka: deleted). How can I do that (using LANGUAGE 'plpgsql')?
UPDATE:
Base on some excellent guidance from "Chicken in the kitchen", I have changed it to this:
DECLARE
re1 feeds_item.shareurl%TYPE;
num_rows int;
BEGIN
SELECT regexp_replace(NEW.shareurl, '/[^/]+(-[0-9]+\.html)$','/[^/]+\\1') INTO re1;
DELETE FROM feeds_item where shareurl ~ re1;
IF FOUND THEN
GET DIAGNOSTICS num_rows = ROW_COUNT;
RAISE NOTICE 'DELETEd % row(s) from feeds_item where shareurl ~ ''%''', num_rows, re1;
END IF;
RETURN NEW;
END;
For a very robust solution, that is part of PostgreSQL SQL and not just plpgsql you could also do the following:
with a as (DELETE FROM feeds_item WHERE shareurl ~ re1 returning 1)
select count(*) from a;
You can actually get lots more information such as:
with a as (delete from sales returning amount)
select sum(amount) from a;
to see totals, in this way you could get any aggregate and even group and filter it.
In Oracle PL/SQL, the system variable to store the number of deleted / inserted / updated rows is:
SQL%ROWCOUNT
After a DELETE / INSERT / UPDATE statement, and BEFORE COMMITTING, you can store SQL%ROWCOUNT in a variable of type NUMBER. Remember that COMMIT or ROLLBACK reset to ZERO the value of SQL%ROWCOUNT, so you have to copy the SQL%ROWCOUNT value in a variable BEFORE COMMIT or ROLLBACK.
Example:
BEGIN
DECLARE
affected_rows NUMBER DEFAULT 0;
BEGIN
DELETE FROM feeds_item
WHERE shareurl = re1;
affected_rows := SQL%ROWCOUNT;
DBMS_OUTPUT.
put_line (
'This DELETE would affect '
|| affected_rows
|| ' records in FEEDS_ITEM table.');
ROLLBACK;
END;
END;
I have found also this interesting SOLUTION (source: http://markmail.org/message/grqap2pncqd6w3sp )
On 4/7/07, Karthikeyan Sundaram wrote:
Hi,
I am using 8.1.0 postgres and trying to write a plpgsql block. In that I am inserting a row. I want to check to see if the row has been
inserted or not.
In oracle we can say like this
begin
insert into table_a values (1);
if sql%rowcount > 0
then
dbms.output.put_line('rows inserted');
else
dbms.output.put_line('rows not inserted');
end if; end;
Is there something equal to sql%rowcount in postgres? Please help.
Regards skarthi
Maybe:
http://www.postgresql.org/docs/8.2/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW
Click on the link above, you'll see this content:
37.6.6. Obtaining the Result Status There are several ways to determine the effect of a command. The first method is to use the GET
DIAGNOSTICS command, which has the form:
GET DIAGNOSTICS variable = item [ , ... ];This command allows
retrieval of system status indicators. Each item is a key word
identifying a state value to be assigned to the specified variable
(which should be of the right data type to receive it). The currently
available status items are ROW_COUNT, the number of rows processed by
the last SQL command sent down to the SQL engine, and RESULT_OID, the
OID of the last row inserted by the most recent SQL command. Note that
RESULT_OID is only useful after an INSERT command into a table
containing OIDs.
An example:
GET DIAGNOSTICS integer_var = ROW_COUNT; The second method to
determine the effects of a command is to check the special variable
named FOUND, which is of type boolean. FOUND starts out false within
each PL/pgSQL function call. It is set by each of the following types
of statements:
A SELECT INTO statement sets FOUND true if a row is assigned, false if
no row is returned.
A PERFORM statement sets FOUND true if it produces (and discards) a
row, false if no row is produced.
UPDATE, INSERT, and DELETE statements set FOUND true if at least one
row is affected, false if no row is affected.
A FETCH statement sets FOUND true if it returns a row, false if no row
is returned.
A FOR statement sets FOUND true if it iterates one or more times, else
false. This applies to all three variants of the FOR statement
(integer FOR loops, record-set FOR loops, and dynamic record-set FOR
loops). FOUND is set this way when the FOR loop exits; inside the
execution of the loop, FOUND is not modified by the FOR statement,
although it may be changed by the execution of other statements within
the loop body.
FOUND is a local variable within each PL/pgSQL function; any changes
to it affect only the current function.
I would to share my code (I had this idea from Roelof Rossouw):
CREATE OR REPLACE FUNCTION my_schema.sp_delete_mytable(_id integer)
RETURNS integer AS
$BODY$
DECLARE
AFFECTEDROWS integer;
BEGIN
WITH a AS (DELETE FROM mytable WHERE id = _id RETURNING 1)
SELECT count(*) INTO AFFECTEDROWS FROM a;
IF AFFECTEDROWS = 1 THEN
RETURN 1;
ELSE
RETURN 0;
END IF;
EXCEPTION WHEN OTHERS THEN
RETURN 0;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;