Update column value from array element inside before insert trigger - postgresql-11

i am trying to do a before insert trigger that updates one of the columns that will be inserted with a value taken from another column on the same record, however the catch is that that value is the first value of the said column after converting it to an array. when i include the function or the array i get a syntax error but the examples i have found for the proper syntax do not have the detail needed to make my code work. Here is my code:
Schema is as follows
create table events (
id int primary key,
performers varchar,
performer_id varchar
);
Function is:
create function events_set_performer() returns trigger
language plpgsql
as $$
declare
performer_ varchar := '0';
BEGIN
set NEW.performer_id = (string_to_array(NEW.performers,':'))[1];
return new;
END;
$$;

Related

How to use page item value inside a trigger query in oracle?

I want the following trigger to be run correctly but it rise an error which is: bad bind variable 'P23_ID'.
The trigger query is:
Create or replace trigger "newTRG"
Before
Insert on "my_table"
For each row
Begin
If :new."ID" is null then
Insert into my_table (ID) values (:P23_ID);
end if;
End;
Use the v() syntax:
create or replace trigger "newTRG" before
insert on "my_table"
for each row
begin
if :new."ID" is null then
insert into my_table ( id ) values (v('P23_ID'));
end if;
end;
On a side note, if this is a primary key value it is a lot easier to use identity columns (the new way) or a sequence (the old way) to populate your column. Doing this from a page item is error prone.

Redshift Pivot Function

I've got a similar table which I'm trying to pivot in Redshift:
UUID
Key
Value
a123
Key1
Val1
b123
Key2
Val2
c123
Key3
Val3
Currently I'm using following code to pivot it and it works fine. However, when I replace the IN part with subquery it throws an error.
select *
from (select UUID ,"Key", value from tbl) PIVOT (max(value) for "key" in (
'Key1',
'Key2',
'Key3
))
Question: What's the best way to replace the IN part with sub query which takes distinct values from Key column?
What I am trying to achieve;
select *
from (select UUID ,"Key", value from tbl) PIVOT (max(value) for "key" in (
select distinct "keys" from tbl
))
From the Redshift documentation - "The PIVOT IN list values cannot be column references or sub-queries. Each value must be type compatible with the FOR column reference." See: https://docs.aws.amazon.com/redshift/latest/dg/r_FROM_clause-pivot-unpivot-examples.html
So I think this will need to be done as a sequence of 2 queries. You likely can do this in a stored procedure if you need it as a single command.
Updated with requested stored procedure with results to a cursor example:
In order to make this supportable by you I'll add some background info and description of how this works. First off a stored procedure cannot produce results strait to your bench. It can either store the results in a (temp) table or to a named cursor. A cursor is just storing the results of a query on the leader node where they wait to be fetched. The lifespan of the cursor is the current transaction so a commit or rollback will delete the cursor.
Here's what you want to happen as individual SQL statements but first lets set up the test data:
create table test (UUID varchar(16), Key varchar(16), Value varchar(16));
insert into test values
('a123', 'Key1', 'Val1'),
('b123', 'Key2', 'Val2'),
('c123', 'Key3', 'Val3');
The actions you want to perform are first to create a string for the PIVOT clause IN list like so:
select '\'' || listagg(distinct "key",'\',\'') || '\'' from test;
Then you want to take this string and insert it into your PIVOT query which should look like this:
select *
from (select UUID, "Key", value from test)
PIVOT (max(value) for "key" in ( 'Key1', 'Key2', 'Key3')
);
But doing this in the bench will mean taking the result of one query and copy/paste-ing into a second query and you want this to happen automatically. Unfortunately Redshift does allow sub-queries in PIVOT statement for the reason given above.
We can take the result of one query and use it to construct and run another query in a stored procedure. Here's such a store procedure:
CREATE OR REPLACE procedure pivot_on_all_keys(curs1 INOUT refcursor)
AS
$$
DECLARE
row record;
BEGIN
select into row '\'' || listagg(distinct "key",'\',\'') || '\'' as keys from test;
OPEN curs1 for EXECUTE 'select *
from (select UUID, "Key", value from test)
PIVOT (max(value) for "key" in ( ' || row.keys || ' )
);';
END;
$$ LANGUAGE plpgsql;
What this procedure does is define and populate a "record" (1 row of data) called "row" with the result of the query that produces the IN list. Next it opens a cursor, whose name is provided by the calling command, with the contents of the PIVOT query which uses the IN list from the record "row". Done.
When executed (by running call) this function will produce a cursor on the leader node that contains the result of the PIVOT query. In this stored procedure the name of the cursor to create is passed to the function as a string.
call pivot_on_all_keys('mycursor');
All that needs to be done at this point is to "fetch" the data from the named cursor. This is done with the FETCH command.
fetch all from mycursor;
I prototyped this on a single node Redshift cluster and "FETCH ALL" is not supported at this configuration so I had to use "FETCH 1000". So if you are also on a single node cluster you will need to use:
fetch 1000 from mycursor;
The last point to note is that the cursor "mycursor" now exists and if you tried to rerun the stored procedure it will fail. You could pass a different name to the procedure (making another cursor) or you could end the transaction (END, COMMIT, or ROLLBACK) or you could close the cursor using CLOSE. Once the cursor is destroyed you can use the same name for a new cursor. If you wanted this to be repeatable you could run this batch of commands:
call pivot_on_all_keys('mycursor'); fetch all from mycursor; close mycursor;
Remember that the cursor has a lifespan of the current transaction so any action that ends the transaction will destroy the cursor. If you have AUTOCOMMIT enable in your bench this will insert COMMITs destroying the cursor (you can run the CALL and FETCH in a batch to prevent this in many benches). Also some commands perform an implicit COMMIT and will also destroy the cursor (like TRUNCATE).
For these reasons, and depending on what else you need to do around the PIVOT query, you may want to have the stored procedure write to a temp table instead of a cursor. Then the temp table can be queried for the results. A temp table has a lifespan of the session so is a little stickier but is a little less efficient as a table needs to be created, the result of the PIVOT query needs to be written to the compute nodes, and then the results have to be sent to the leader node to produce the desired output. Just need to pick the right tool for the job.
===================================
To populate a table within a stored procedure you can just execute the commands. The whole thing will look like:
CREATE OR REPLACE procedure pivot_on_all_keys()
AS
$$
DECLARE
row record;
BEGIN
select into row '\'' || listagg(distinct "key",'\',\'') || '\'' as keys from test;
EXECUTE 'drop table if exists test_stage;';
EXECUTE 'create table test_stage AS select *
from (select UUID, "Key", value from test)
PIVOT (max(value) for "key" in ( ' || row.keys || ' )
);';
END;
$$ LANGUAGE plpgsql;
call pivot_on_all_keys();
select * from test_stage;
If you want this new table to have keys for optimizing downstream queries you will want to create the table in one statement then insert into it but this is quickie path.
A little off-topic, but I wonder why Amazon couldn't introduce a simpler syntax for pivot. IMO, if GROUP BY is replaced by PIVOT BY, it can give enough hint to the interpreter to transform rows into columns. For example:
SELECT partname, avg(price) as avg_price FROM Part GROUP BY partname;
can be written as:
SELECT partname, avg(price) as avg_price FROM Part PIVOT BY partname;
Even multi-level pivoting can also be handled in the same syntax.
SELECT year, partname, avg(price) as avg_price FROM Part PIVOT BY year, partname;

Parse data from table Greenplum

I have a table scheme2.central_id__new_numbers in Greenplum.
I need select data from scheme2.central_id__new_numbers in the form of a many-to-many relationship.
Also I write the code but must have made a wrong turn somewhere (the code doesn't work):
CREATE FUNCTION my_scheme.parse_new_numbers (varchar) RETURNS SETOF varchar as
$BODY$
declare
i int;
BEGIN
FOR i IN 1..10 LOOP
select
central_id,
(select regexp_split_to_table((select new_numbers
from scheme2.central_id__new_numbers limit 1 offset i), '\s+'))
from scheme2.central_id__new_numbers limit 1 offset i
END LOOP;
END;
$BODY$
LANGUAGE plpgsql;
I'd recommend using the UNNEST() function instead, i.e. assuming new_numbers column is of int[] data type,
SELECT central_id
, UNNEST(new_numbers) AS new_numbers
FROM central_id__new_numbers;
If new_columns column is not an array data type then you need to use i.e. string_to_array() or similar before using UNNEST().

How to show other vaues in a selectlist in APEX

I am using Oracle APEX to build a interactive report. There is a field in my database called method which should contain either A or B. In the edit page, I want to show a list containing A and B so that users can choose from those two.
I set the type of the item to SelectList and since I need to add the other value to the list, in the List of Values area, I set the type to PL/SQL Function Body returning SQL Query and the code is as follows:
Begin
select TEST_METHOD into method from table_test
where ROWID = :P2_ROWID;
IF ('Live' = method) THEN
return select 'Screenshots' from dual;
END IF;
return select 'Live' from dual;
End;
However, I got the following error:
ORA-06550: line 5, column 10: PLS-00103: Encountered the symbol "SELECT" when expecting one of the following: ( - + ; case mod new not null continue avg count current exists max min prior sql stddev sum variance execute forall merge time timestamp interval date pipe
I am new to plsql and APEX, I know the code looks wired but I don't know what's wrong. I am also wondering if there is any other way to achieve my goal? Thanks!

SOCI Cannot prepare statement

I have a function like this:
CREATE OR REPLACE FUNCTION get_path_set_1(IN pathset_id_in character varying, OUT id character varying, OUT pathset_id character varying, OUT utility double precision)
RETURNS SETOF record AS
$BODY$
begin
if exists(SELECT 1 FROM "PathSet_Scaled_HITS_distinctODs" WHERE "ID" = $1) then
return query SELECT "ID", "PATHSET_ID", "UTILITY"
FROM "SinglePath_Scaled_HITS_distinctODs"
where "PATHSET_ID" = $1;
end if;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION get_path_set_1(character varying)
OWNER TO postgres;
when I call it in my program using this:
std::string testStr("43046,75502");// or std::string testStr("'43046,75502'");
soci::rowset<sim_mob::SinglePath> rs = (sql.prepare << "get_path_set_1(:pathset_id_in)",soci::use(testStr));
I get the following exception:
terminate called after throwing an instance of 'soci::postgresql_soci_error'
what(): Cannot prepare statement. ERROR: syntax error at or near "get_path_set_1"
LINE 1: get_path_set_1($1)
I will appreciate if you help me detect missing part
thank you
This does not solve the error you report. But simplify your function:
CREATE OR REPLACE FUNCTION get_path_set_1(pathset_id_in varchar)
RETURNS TABLE(id varchar, pathset_id varchar, utility double precision) AS
$func$
BEGIN
RETURN QUERY
SELECT "ID", "PATHSET_ID", "UTILITY"
FROM "SinglePath_Scaled_HITS_distinctODs"
WHERE "PATHSET_ID" = $1;
END
$func$ LANGUAGE plpgsql;
RETURNS TABLE is the modern, more elegant, equivalent form of the combination RETURNS SETOF record and OUT parameters.
IF exists ... is buying you nothing here. Run the query; if nothing is found, nothing is returned. Same result for half the cost.
From this piece of code:
soci::rowset<sim_mob::SinglePath> rs =
(sql.prepare << "get_path_set_1(:pathset_id_in)",soci::use(testStr));
it appears you're trying to prepare a query that just contains the function call without even a SELECT.
That's not valid in SQL. You want to prepare this query instead:
SELECT * FROM get_path_set_1(:pathset_id_in)
This form (select * from function(...)) is also necessary because the function returns a resultset with multiple columns, as opposed to just a scalar value.
Also as Erwin mentions, the OUT and SETOF RECORD are weird in this case, I'll second his advice on using RETURNS TABLE.