Trying to convert simple execution stored procedure to postgres function - can't get CURRVAL('table_seq') to function - postgresql

The MySQL Stored Procedure was:
BEGIN
set #sql=_sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
set _ires=LAST_INSERT_ID();
END$$
I tried to convert it to:
BEGIN
EXECUTE _sql;
SELECT INTO _ires CURRVAL('table_seq');
RETURN;
END;
I get the error:
SQL error:
ERROR: relation "table_seq" does not exist
LINE 1: SELECT CURRVAL('table_seq')
^
QUERY: SELECT CURRVAL('table_seq')
CONTEXT: PL/pgSQL function "myexecins" line 4 at SQL statement
In statement:
SELECT myexecins('SELECT * FROM tblbilldate WHERE billid = 2')
The query used is for testing purposes only. I believe this function is used to get the row id of the inserted or created row from the query. Any Suggestions?

When you create tables with serial columns, sequences are, by default, named as tablename_columnname_seq, but it seems that you're trying to access tablename_seq. So with a table called foobar and primary key column foobar_id it ends up being foobar_foobar_id_seq.
By the way, a cleaner way to get the primary key after an insert is using the RETURNING clause in INSERT. E.g.:
_sql = 'INSERT INTO sometable (foobar) VALUES (123) RETURNING sometable_id';
EXECUTE _sql INTO _ires;

PostgreSQL is saying that there is no sequence called "table_seq". Are you sure that that is the right name? The name you would use would depend on what is in _sql as each SERIAL or BIGSERIAL gets its own sequence, you can also define sequences and wire them up by hand.
In any case, lastval() is a closer match to MySQL's LAST_INSERT_ID(), lastval() returns the most recently returned value from any sequence in the current session:
lastval
Return the value most recently returned by nextval in the current session. This
function is identical to currval, except that instead of taking the sequence name
as an argument it fetches the value of the last sequence used by nextval in the
current session. It is an error to call lastval if nextval has not yet been called
in the current session.
Using lastval() also means that you don't have to worry about what's in _sql, unless of course it doesn't use a sequence at all.

Related

How to update column based on column name in postgres?

I've narrowed it down to two possibilities - DynamicSQL and using a case statement.
However, I've failed with both of these.
I simply don't understand dynamicSQL, and how I would use it in my case.
This is my attempt using case statements; one of many failed variations.
SELECT column_name,
CASE WHEN column_name = 'address' THEN (**update statement gives syntax error within here**)
END
FROM information_schema.columns
WHERE table_name = 'employees';
As an overview, I'm using Axios to talk to my Node server, which is making calls to my Heroku database using Massivejs.
Maybe this isn't the way to go - so here's my main problem:
I've ran into troubles because the values I'm planning on using as column names are sent to my server as strings. The exact call that I've been trying to use is
update employees
set $1 = $2
where employee_id = $3;
Once again, I'm passing into those using massive.
I get the error back { error: syntax error at or near "'address'"} because my incoming values are strings. My thought process was that the above statement would allow me to use variables because 'address' is encapsulated by quotes.
But alas, my thought process has failed me.
This seems to be close to answering my question, but I can't seem to figure out what to do in my case if using dynamic SQL.
How to use dynamic column names in an UPDATE or SELECT statement in a function?
Thanks in advance.
I will show you a way to do this by using a function.
First we create the employees table :
CREATE TABLE employees(
id BIGSERIAL PRIMARY KEY,
column1 TEXT,
column2 TEXT
);
Next, we create a function that requires three parameters:
columnName - the name of the column that needs to be updated
columnValue - the new value to which the column needs to be updated
employeeId - the id of the employee that will be updated
By using the format function we generate the update query as a string and use the EXECUTE command to execute the query.
Here is the code of the function.
CREATE OR REPLACE FUNCTION update_columns_on_employee(columnName TEXT, columnValue TEXT, employeeId BIGINT)
RETURNS VOID AS
$$
DECLARE update_statement TEXT := format('UPDATE EMPLOYEES SET %s = ''%s'' WHERE id = %L',columnName, columnValue, employeeId);
BEGIN
EXECUTE update_statement;
end;
$$ LANGUAGE plpgsql;
Now, lets insert some data into the employees table
INSERT INTO employees(column1, column2) VALUES ('column1_start_value','column2_start_value');
So now we currently have an employee with an id value of 1 who has 'column1_start_value' value for the column1, and 'column2_start_value' value for column2.
If we want to update the value of column2 from 'column2_start_value' to 'column2_new_value' all we have to do is execute the following call
SELECT * FROM update_columns_on_employee('column2','column2_new_value',1);

How to return values from dynamically generated "insert" command?

I have a stored procedure that performs inserts and updates in the tables. The need to create it was to try to centralize all the scan functions before inserting or updating records. Today the need arose to return the value of the field ID of the table so that my application can locate the registry and perform other stored procedures.
Stored procedure
SET TERM ^ ;
CREATE OR ALTER procedure sp_insupd (
iaction varchar(3),
iusuario varchar(20),
iip varchar(15),
imodulo varchar(30),
ifieldsvalues varchar(2000),
iwhere varchar(1000),
idesclogs varchar(200))
returns (
oid integer)
as
declare variable vdesc varchar(10000);
begin
if (iaction = 'ins') then
begin
vdesc = idesclogs;
/*** the error is on the line below ***/
execute statement 'insert into '||:imodulo||' '||:ifieldsvalues||' returning ID into '||:oid||';';
end else
if (iaction = 'upd') then
begin
execute statement 'select '||:idesclogs||' from '||:imodulo||' where '||:iwhere into :vdesc;
execute statement 'execute procedure SP_CREATE_AUDIT('''||:imodulo||''');';
execute statement 'update '||:imodulo||' set '||:ifieldsvalues||' where '||:iwhere||';';
end
insert into LOGS(USUARIO, IP, MODULO, TIPO, DESCRICAO) values (
:iusuario, :iip, :imodulo, (case :iaction when 'ins' then 1 when 'upd' then 2 end), :vdesc);
end^
SET TERM ; ^
The error in the above line is occurring due to syntax error. The procedure is compiled normally, that is, the error does not happen in the compilation, since the line in question is executed through the "execute statement". When there was no need to return the value of the ID field, the procedure worked normally with the line like this:
...
execute statement 'insert into '||:imodulo||' '||:ifieldsvalues||';';
...
What would be the correct way for the value of the ID field to be stored in the OID variable?
What is REAL VALUE in ifieldsvalues ?
you can not have BOTH
'insert into '||:imodulo||' '||:ifieldsvalues
'update '||:imodulo||' set '||:ifieldsvalues
because methods to specify column names and column values in INSERT and UPDATE statements is fundamentally different!!! You either would have broken update-stmt or broken insert-stmt!
The error in the above line is occurring due to syntax error
This is not enough. Show the real error text, all of it.
It includes the actual command you generate and it seems you had generated it really wrong way.
all the scan functions before inserting or updating records
Move those functions out of the SQL server and into your application server.
Then you would not have to make insert/update in that "strings splicing" way, which is VERY fragile and "SQL injection" friendly. You stepped into the road to hell here.
the error does not happen in the compilation
Exactly. And that is only for starters. You are removing all the safety checks that should had helped you in applications development.
http://searchsoftwarequality.techtarget.com/definition/3-tier-application
https://en.wikipedia.org/wiki/Multitier_architecture#Three-tier_architecture
http://bobby-tables.com
On modern Firebird versions EXECUTE STATEMENT command can have the same INTO clause as PSQL SELECT command.
https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-psql-coding.html#fblangref25-psql-execstmt
Use http://translate.ru to read http://www.firebirdsql.su/doku.php?id=execute_statement
Or just see SQL examples there. Notice, however, those examples all use SELECT dynamic command, not INSERT. So I am not sure it would work that way.
This works in Firebird 2.5 (but not in Firebird 2.1) PSQL blocks.
execute statement 'insert into Z(payload) values(2) returning id' into :i;
To run it from IBExpert/FlameRobin/iSQL interactive shell add that obvious boilerplate:
execute block returns (i integer) as
begin
execute statement 'insert into Z(payload) values(2) returning id' into :i;
suspend;
end

Define variable and run a query using EXECUTE BLOCK

I have a large query in Firebird (which I run using FlameRobin), using a parameter all over the place, but getting the query below to run will do:
SELECT * FROM customers WHERE customerid = 1234;
I want to define 1234 as a variable, say customerID, so that I can easily replace it with something else.
I've learned that I need to put this inside a EXECUTE BLOCK.
EXECUTE BLOCK
AS
DECLARE customerID INT = 1234;
BEGIN
SELECT * FROM customers WHERE customerid = :customerID
END
If of any importance, the error I am getting is Engine Message :
Dynamic SQL Error
SQL error code = -104
Unexpected end of command - line 3, column 26
The problem is that FlameRobin needs to know when a statement ends and the next statement starts. By default it uses the semicolon (;) for this. However an EXECUTE BLOCK is essentially a stored procedure that isn't stored in the database, so it contains PSQL code that also uses the semicolon as a statement separator.
The consequence of this is that you get syntax errors because FlameRobin is sending incomplete statements to the server (that is: it is sending a statement after each ; it encounters).
You need to instruct FlameRobin to use a different statement terminator using SET TERM. Other Firebird query tools (eg isql) require this as well, but it is not actually part of the syntax of Firebird itself!
So you need to execute your code as:
-- Instruct flamerobin to use # as the terminator
SET TERM #;
EXECUTE BLOCK
AS
DECLARE customerID INT = 1234;
BEGIN
SELECT * FROM customers WHERE customerid = :customerID;
END#
-- Restore terminator to ;
SET TERM ;#
However doing this will still result in an error, because this query is invalid for PSQL: A SELECT in a PSQL block requires an INTO clause to map the columns to variables. And to get the values out of the EXECUTE BLOCK returned to FlameRobin you also need to specify a RETURNS clause as described in the documentation of EXECUTE BLOCK:
-- Instruct flamerobin to use # as the terminator
SET TERM #;
EXECUTE BLOCK
RETURNS (col1 INTEGER, col2 VARCHAR(100))
AS
DECLARE customerID INT = 1234;
BEGIN
SELECT col1, col2 FROM customers WHERE customerid = :customerID INTO :col1, :col2;
SUSPEND;
END#
-- Restore terminator to ;
SET TERM ;#
As far as I know the SUSPEND is technically not required here, but Flamerobin will not fetch the returned row if it isn't included.
However the above will not work if the select produces multiple rows. For that you need to use FOR SELECT ... DO combined with a SUSPEND:
-- Instruct flamerobin to use # as the terminator
SET TERM #;
EXECUTE BLOCK
RETURNS (col1 INTEGER, col2 VARCHAR(100))
AS
DECLARE customerID INT = 1234;
BEGIN
FOR SELECT col1, col2 FROM customers WHERE customerid = :customerID INTO :col1, :col2
DO
SUSPEND;
END#
-- Restore terminator to ;
SET TERM ;#
The SUSPEND here returns the row and waits until the caller has fetched that row and then continues with the FOR loop. This way it will iterate over the results.
IMHO this too much effort for parameterization. You might want to consider simply not parametrizing when you use FlameRobin, or use a tool that supports asking for parameter values for the normal parameter placeholders of Firebird (but to be honest I am not sure if there are any).
Well you need a input and return value, ex:
EXECUTE BLOCK (p_customerID integer=?)
returns(col_1 type of column table.id_cv)
as
begin
for
select col_1 from table
where customerID=:p_customerID
into col_1 do
suspend;
end
Delphi as client side:
myquery1.ParamByName('p_customerID ').Value:=1234;
myquery1.Open()
if not myquery1.IsEmpty then
begin
// using returning values
if myquery1.FieldbyName('col_1').AsString='this' then DoThat();
end;
Use this way ever yout need input parameters and/or returning values;

Accessing columns inside a record using table alias in Postgresql

I'm trying to iterate over the result of a query using a record data type. Nevertheless, if I try to access one column using the table alias defined in the query, I get the following error:
ERRO: schema "inv_row" does not exist
CONTEXT: SQL command "SELECT inv_row.s.processor <> inv_row.d.processor"
PL/pgSQL function "teste" line 7 at IF
Here is the code that throws this error:
CREATE OR REPLACE FUNCTION teste() returns void as $$
DECLARE
inv_row record;
BEGIN
FOR inv_row in SELECT * FROM sa_inventory s LEFT JOIN dim_inventory d ON s.macaddr = d.macaddr LOOP
IF inv_row.s.processor <> inv_row.d.processor THEN
<do something>;
END IF;
END LOOP;
END;
$$ language plpgsql;
Is there another way to access a column of a particular table inside a record data type?
Fortunately the answer here is relatively simple. You have to use parentheses to indicate tuples:
IF (inv_row.s).processor <> (inv_row.d).processor THEN
This is because SQL specifies meaning to the depth of the namespaces and therefore without this PostgreSQL cannot safely determine what this means. So inv_row.s.processor means the processor column of the s table in the inv_row schema. However (inv_row.s).processor means take the s column of the inf_row table, treat it as a tuple, and take the processor column of that.

How do I fix this PostgreSQL 9.1 stored procedure?

We have a problem with our software and in order to correct the issue, I have to write a stored procedure that will be run as part of the upgrade process for upgrade installs. This stored procedure needs to find every row in a particular table that matches certain conditions and update that row. For internal reasons, the update has to be done through a stored procedure we wrote specifically for inserting and updating data.
Here is the stored procedure I have written to fix this issue:
CREATE OR REPLACE FUNCTION FixDataProblem() RETURNS VOID AS $$
DECLARE
FixCursor NO SCROLL CURSOR FOR
SELECT * FROM MyTable WHERE ProblemColumn IN ( '?', 'PR' );
RowToUpdate MyTable%ROWTYPE;
BEGIN
-- Open the cursor
OPEN FixCursor;
-- Start a loop
LOOP
-- Fetch the next row from thr cursor
FETCH FixCursor INTO RowToUpdate;
-- Did we get anything back?
IF RowToUpdate IS NULL THEN
-- We didn't. Exit the loop
EXIT;
END IF;
-- Call the UpsertMyTable stored procedure to set the ProblemColumn column to NULL
SELECT CarSystem.UpsertMyTable( RowToUpdate.RowId,
RowToUpdate.ForeignId,
RowToUpdate.CountryId,
NULL,
RowToUpdate.Plate,
RowToUpdate.HashedData,
RowToUpdate.PlateClassId,
RowToUpdate.AlarmClassId,
RowToUpdate.BeginDate,
RowToUpdate.EndDate,
RowToUpdate.ListPriorityId,
RowToUpdate.VehicleTypeId,
RowToUpdate.MakeId,
RowToUpdate.ModelId,
RowToUpdate.Year,
RowToUpdate.ColorId,
RowToUpdate.Notes,
RowToUpdate.OfficerNotes,
NULL,
UUID_GENERATE_V4() );
END LOOP;
-- Close the cursor
CLOSE ListDetailsCursor;
END;
$$ LANGUAGE plpgsql;
This stored procedure fine, but when I run it, I get:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function "fixdataproblem" line 22 at SQL statement
********** Error **********
ERROR: query has no destination for result data
SQL state: 42601
Hint: If you want to discard the results of a SELECT, use PERFORM instead.
Context: PL/pgSQL function "fixdataproblem" line 22 at SQL statement
How do I fix this issue? I believe I am calling the stored procedure correctly. I really don't know what the issue with this stored procedure is.
Thanks
Tony
It says right there:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function "fixdataproblem" line 22 at SQL statement
And on line 22:
-- Call the UpsertMyTable stored procedure to set the ProblemColumn column to NULL
SELECT CarSystem.UpsertMyTable( RowToUpdate.RowId,
...
Change it from SELECT to PERFORM. See PERFORM for why.