I've been at this Create Trigger for a while...
I'm using IBM Data Studio 4.1.3 while making this Trigger. At first I had problems with ending statements with ';' but on the IBM website it says to use 'x' and it works.
My main problem, however, wondering why I get this message:
"N.ITEMNAME" is not valid in the context where it is used.. SQLCODE=-206, SQLSTATE=42703, DRIVER=3.69.56
This also applies to all the others: o.itemid, o.quantity, and n.quantity. I found this out when switching/swapping the names around each other.
The editor is telling me that it has no errors in the statement but when executing, problems arise.
-- <ScriptOptions statementTerminator="x" />
CREATE TRIGGER DB2ADMIN.SUPPLIES_I
AFTER UPDATE OF QUANTITY ON DB2ADMIN.SUPPLIES
REFERENCING NEW TABLE AS n
OLD TABLE AS o
FOR EACH ROW MODE DB2SQL NOT SECURED
BEGIN ATOMIC
INSERT INTO db2admin.tran_log VALUES (USER, CURRENT TIMESTAMP || ' ' || n.itemname || ' ( ' || o.itemid || ' ) from ' || CHAR(o.quantity) || ' to ' || CHAR(n.quantity));
END
Remove the TABLE word from the CREATE TRIGGER statement:
CREATE TRIGGER DB2ADMIN.SUPPLIES_I
AFTER UPDATE OF QUANTITY ON DB2ADMIN.SUPPLIES
REFERENCING NEW AS n
OLD AS o
FOR EACH ROW MODE DB2SQL NOT SECURED
BEGIN ATOMIC
INSERT INTO db2admin.tran_log VALUES (USER, CURRENT TIMESTAMP || ' ' || n.itemname || ' ( ' || o.itemid || ' ) from ' || CHAR(o.quantity) || ' to ' || CHAR(n.quantity));
END
You can't reference a table transition variable in the way you try. Imagine, that we build the new table as below.
It's possible:
with n(i) as (values 1, 2, 3)
select i
from n;
It's not possible, and you get the same error message:
with n(i) as (values 1, 2, 3)
values (n.i);
Alternative solution with a FOR EACH STATEMENT trigger
If you your table have a key (one or more columns), and it doesn't include the updated column QUANTITY:
CREATE TRIGGER DB2ADMIN.SUPPLIES_I2
AFTER UPDATE OF QUANTITY ON DB2ADMIN.SUPPLIES
REFERENCING NEW TABLE AS n
OLD TABLE AS o
FOR EACH STATEMENT
INSERT INTO db2admin.tran_log
SELECT USER, CURRENT TIMESTAMP || ' ' || n.itemname || ' ( ' || o.itemid || ' ) from ' || CHAR(o.quantity) || ' to ' || CHAR(n.quantity)
FROM n, o
WHERE n.<key>=o.<key>
Related
I have a function that creates a set of INSERT INTO ... VALUES scripts. If I uncomment the dvp.content line, the function fails with an "ERROR: could not open relation with OID ###", which refers to the temp table. The content column is a jsonb type. Not sure where to begin?
CREATE OR REPLACE FUNCTION export_docs_as_sql(doc_list uuid[], to_org_id uuid)
RETURNS table(id integer, sql text)
AS $$
BEGIN
...
-- use a temp table to gather all INSERT statements
CREATE TEMP TABLE IF NOT EXISTS doc_data_export(
id serial PRIMARY KEY,
sql text
);
...
-- get doc_version_pages
INSERT INTO doc_data_export(sql)
SELECT 'INSERT INTO doc_version_pages(id, doc_version_id, persona_id, care_category_id, patient_group_id, title, content, created_at, updated_at, is_guide, is_root) VALUES (' ||
quote_literal(dvp.id::TEXT) || ', ' ||
quote_literal(dvp.doc_version_id::TEXT) || ', ' ||
CASE WHEN p.name IS NOT NULL THEN '(SELECT px.id FROM personas px WHERE px.org_id = ' || quote_literal(dv.id::TEXT) || ' AND px.name = ' || quote_literal(p.name) || '), ' ELSE 'NULL, ' END ||
CASE WHEN c.name IS NOT NULL THEN '(SELECT cx.id FROM care_categories cx WHERE cx.org_id = ' || quote_literal(to_org_id) || ' AND cx.name = ' || quote_literal(c.name) || '), ' ELSE 'NULL, ' END ||
CASE WHEN g.name IS NOT NULL THEN '(SELECT gx.id FROM patient_groups gx WHERE gx.org_id = ' || quote_literal(to_org_id) || ' AND gx.name = ' || quote_literal(g.name) || '), ' ELSE 'NULL, ' END ||
quote_literal(dvp.title::TEXT) || ', ' ||
--dvp.content || ', ' ||
quote_literal(dvp.created_at::TEXT) || ', ' ||
quote_literal(now()::timestamp) || ', ' ||
quote_literal(dvp.is_guide::TEXT) || ', ' ||
quote_literal(dvp.is_root::TEXT) || ');'
FROM unnest(doc_list) l
INNER JOIN doc_versions dv ON l = dv.doc_id
INNER JOIN doc_version_pages dvp ON dv.id = dvp.doc_version_id
LEFT JOIN personas p ON dvp.persona_id = p.id
LEFT JOIN care_categories c ON dvp.care_category_id = c.id
LEFT JOIN patient_groups g ON dvp.patient_group_id = g.id;
...
-- output all inserts
RETURN QUERY SELECT * FROM doc_data_export;
-- drop temp table
DROP TABLE doc_data_export;
END;
$$ LANGUAGE plpgsql;
The "Could Not Open Relation" problem is occurring due to the bug described here, which remains an issue as of Postgres 14.0:
What seems to be happening is that if the strings are large enough to be
toasted, then the data returned out of the function with RETURN QUERY
contains toast pointers referencing the temp table's toast table.
If you drop the temp table then those pointers will fail upon use.
To explain further, when a column value is greater than the TOAST_TUPLE_THRESHOLD configuration parameter (usually 2KB) and cannot be compressed or when the column is configured with a storage parameter of EXTERNAL, the value will be broken down into chunks and stored in a special secondary table called a TOAST table. This table will be stored in the pg_toast schema and will be named like pg_toast.pg_toast_<table OID>.
So when you add dvp.content to the sql statement you insert that into doc_data_export, some of these values are larger than the aforementioned constraints and are thus TOASTed. Your RETURN QUERY is only sending the pointers to the values in the toast table. After the return is done, the temporary table and its corresponding TOAST table is dropped. Thus when the outer query attempts to materialize the results, it can't find the TOAST table that these pointers reference - hence the cryptic error message you see.
You can avoid sending TOAST pointers for the temporary table -and thus safely DROP it after the RETURN QUERY -by performing an operation on the sql column that returns the same value:
RETURN QUERY SELECT id, sql || '' FROM doc_data_export;
The simple function below will reproduce a minimal example of the TOAST bug when you set fail to true and demonstrate the successful workaround when you set fail to false.
DROP FUNCTION IF EXISTS buttered_toast(boolean);
CREATE OR REPLACE FUNCTION buttered_toast(fail boolean)
RETURNS table(id integer, enormous_data text)
AS $$
BEGIN
CREATE TEMPORARY TABLE tbl_with_toasts (
id integer PRIMARY KEY,
enormous_data text
) ON COMMIT DROP;
--generate a giant string that is sure to generate a TOAST table.
INSERT INTO tbl_with_toasts(id,enormous_data) SELECT 1, string_agg(gen_random_uuid()::text,'-') FROM generate_series(1,10000) as ints(int);
IF buttered_toast.fail THEN
-- will return pointers to tbl_with_toast's TOAST table for the "enormous_data" column.
RETURN QUERY SELECT tbl_with_toasts.id, tbl_with_toasts.enormous_data FROM tbl_with_toasts ;
ELSE
-- will generate and return new values for the "enormous_data" column
RETURN QUERY SELECT tbl_with_toasts.id, tbl_with_toasts.enormous_data || '' FROM tbl_with_toasts ;
END IF;
DROP TABLE tbl_with_toasts;
END;
$$ LANGUAGE plpgsql;
-- fails with "Could Not Open Relation"
select * from buttered_toast(true)
--succeeds
select * from buttered_toast(false);
Soo, here is the thing, I am using in my DB methods 2 approaches:
1.) Is compose and SQL query from various string, depending on what I need to filter out:
sql_cmd := 'SELECT count(*) FROM art_short_term_finished WHERE (entry_time <= ''' || timestamp_up || ''' AND exit_time >= ''' || timestamp_down || ''') AND ' || time_filter || ' AND entry_zone = ' || zone_parameter || ' AND park_uuid = ' || park_id_p || '';
EXECUTE sql_cmd INTO shortterm_counter;
2.) Copy part of the big table, into smaller temp table and work with it:
-- Get the data from FPL into smaller table for processing
DROP TABLE IF EXISTS temp_fpl_filtered;
CREATE TEMP TABLE temp_fpl_filtered AS SELECT car_id FROM flexcore_passing_log fpl WHERE fpl.zone_leaved = '0' AND fpl.status IN (SELECT status_id FROM fpl_ok_statuses) AND fpl.park_uuid = park_id_p AND (fpl.datetime BETWEEN row_i.start_d AND row_i.end_d);
But what If I want to mix those two?
I want to have the SELECT after CREATE TEMP TABLE temp_fpl_filtered AS to have different WHERE clauses depending on input parameters of stored procedure, without having to write the same statement xy times in one stored procedure.
But my approach:
-- art class is shortterm, check shortterm history
IF art_class_p = 1 OR article_p = 0 THEN
-- create temporary table derivated from shortterm history
IF article_p = 0 THEN
article_p_filter := '';
ELSE
article_p_filter := ' AND article_id = ' || article_p;
END IF;
short_cmd := 'SELECT car_id, article_id, entry_time, exit_time FROM art_short_term_finished WHERE zone_leaved = ''0'' AND status IN (SELECT status_id FROM fpl_ok_statuses) ''' || article_p_filter || ''' AND park_uuid = ''' || park_id_p || ''' AND (entry_time <= ''' || timestamp_up || ''' AND exit_time >= ''' || timestamp_down || ''')';
DROP TABLE IF EXISTS temp_short_full;
CREATE TEMP TABLE temp_short_full AS short_cmd;
--EXECUTE sql_cmd INTO shortterm_counter;
END IF;
throws me an exception when I try to inser stored procedure:
psql:report_parking_average.sql:107: ERROR: syntax error at or near "short_cmd"
LINE 50: CREATE TEMP TABLE temp_fpl_filtered AS short_cmd;
^
Also, the another try:
EXECUTE short_cmd INTO TEMP TABLE temp_short_full;
is not working..
You need to include the CREATE TABLE part into the SQL you generate:
short_cmd := 'CREATE TEMP TABLE temp_short_full AS SELECT car_id, article_id, entry_time, exit_time FROM art_short_term_finished WHERE zone_leaved = ''0'' AND status IN (SELECT status_id FROM fpl_ok_statuses) ''' || article_p_filter || ''' AND park_uuid = ''' || park_id_p || ''' AND (entry_time <= ''' || timestamp_up || ''' AND exit_time >= ''' || timestamp_down || ''')';
DROP TABLE IF EXISTS temp_short_full;
execute short_cmd;
I've got a plpgsql function that needs to prepare data from 3 tables based on user input, and export the data using COPY TO. The data are road accidents, so the 3 tables are accident, casualty and vehicle, each accident links to zero or more records in the vehicle and casualty tables via an accidentid column that exists in all three tables. severity and local_authorities are input parameters (both text []).
sql_query = 'SELECT COUNT(*) FROM accident WHERE severity = ANY(' || quote_literal(severity)
|| ') AND local_auth = ANY (' || quote_literal(local_authorities) || ')';
EXECUTE sql_query INTO result_count;
IF result_count > 0 THEN
-- replace Select Count(*) With Select *
sql_query = Overlay(sql_query placing '*' from 8 for 8);
-- copy the accident data first
EXECUTE 'COPY (' || sql_query || ') TO ' || quote_literal(file_path || file_name_a) ||
' CSV';
This first bit will get the relevant accidents, so I'm now looking for the most efficient way to use the accidentid's from the first query to download the related vehicle and casualty data.
I thought I'd be able to use a WITH block like this:
-- replace * with accidentid
sql_query = Overlay(sql_query placing 'accidentid' from 8 for 1);
WITH acc_ids AS (sql_query)
EXECUTE 'COPY (SELECT * FROM vehicle WHERE accidentid IN (SELECT accidentid FROM
acc_ids)) TO ' || out_path_and_vfilename || ' CSV';
EXECUTE 'COPY (SELECT * FROM casualty WHERE accidentid IN (SELECT accidentid FROM
acc_ids)) TO ' || out_path_and_cfilename || ' CSV';
but get an error:
ERROR: syntax error at or near "$1"
LINE 1: WITH acc_ids AS ( $1 ) EXECUTE 'COPY (SELECT * FROM accident....
I have tried the above in a non-dynamic test case e.g.
WITH acc_ids AS (
SELECT accidentid FROM accident
WHERE severity = ANY ('{3,2}')
AND local_auth = ANY ('{E09000001,E09000002}')
)
SELECT * FROM vehicle
WHERE accidentid IN (
SELECT accidentid FROM acc_ids);
which works. Unfortunately the server is still running Postgres 8.4 so I can't use format() for the time being.
Perhaps this isn't possible with a WITH block, but I hope it at least illustrates what I'm trying to achieve.
Edit/Update
The main goal is to get the relevant data from the 3 tables in 3 separate csv files, ideally without having to run the selection on the accident table 3 times
If you want to run a query (part) that is stored in a string variable, you need a dynamic query like
EXECUTE 'WITH acc_ids AS (' || sql_query || ')'
'SELECT ... ';
Either the whole query is a string executed by EXECUTE, or the whole query is static SQL. You cannot mix them.
Do you need a CTE? If you can express the query as a join, the optimizer has more options.
This does what I need to do without CTE but I can't see this being the most efficient way of solving this since I have to perform the same query on the accident table 3 times:
sql_query = sql_query || which_tab || ' WHERE severity = ANY ('||
quote_literal(severity) ||') AND ' || date_start || ' AND ' ||
date_end || ' AND local_auth = ANY (' ||
quote_literal(local_authorities) || ')';
-- replace * with COUNT(*)
sql_query = Overlay(sql_query placing 'COUNT(*)' from 8 for 1);
EXECUTE sql_query INTO result_count;
IF result_count > 0 THEN
-- replace COUNT(*) with *
sql_query = Overlay(sql_query placing '*' from 8 for 8);
-- copy the accident data first
EXECUTE 'COPY (' || sql_query || ') TO ' || quote_literal(file_path ||
file_name_a) || ' CSV';
sql_query = Overlay(sql_query placing 'accidentid' from 8 for 1);
-- vehicles
EXECUTE 'COPY (SELECT * FROM vehicle WHERE accidentid IN (
SELECT accidentid FROM accident
WHERE severity = ANY (' || quote_literal(severity) || ')
AND local_auth = ANY (' || quote_literal(local_authorities) ||')))
TO ' || quote_literal(file_path || file_name_v) || ' CSV';
-- casualties
EXECUTE 'COPY (SELECT * FROM casualty WHERE accidentid IN (
SELECT accidentid FROM accident
WHERE severity = ANY (' || quote_literal(severity) || ')
AND local_auth = ANY (' || quote_literal(local_authorities) ||')))
TO ' || quote_literal(file_path || file_name_c) || ' CSV';
END IF;
I have a table with a bunch of cols
I have created a full text index on a table like this:
CREATE INDEX phrasetable_exp_idx ON msc.mytable
USING gin(to_tsvector('norwegian', coalesce(msc.mytable.col1,'') || ' ' ||
coalesce(msc.mytable.col2,'') || ' ' ||
coalesce(msc.mytable.col3,'') || ' ' ||
coalesce(msc.mytable.col4,'') || ' ' ||
coalesce(msc.mytable.col5,'') || ' ' ||
coalesce(msc.mytable.col6,'') || ' ' ||
coalesce(msc.mytable.col7,'')));
I try some searches and they are lightning fast, however, for one particular search I don't get the expected results.
I have a row in my table where both col1 and col2 have the exact value "Importkompetanse Oslo AS"
in col3 it has the value "9999".
Only the query to_tsquery('9999') returns the row, which shows me that it does have the value "Importkompetanse Oslo AS" in the both col1 and col2, but the first two queries return no matches.
SELECT *
FROM msc.mytable
WHERE to_tsvector('norwegian', coalesce(msc.col1,'') || ' ' ||
coalesce(msc.mytable.col2,'') || ' ' ||
coalesce(msc.mytable.col3,'') || ' ' ||
coalesce(msc.mytable.col4,'') || ' ' ||
coalesce(msc.mytable.col5,'') || ' ' ||
coalesce(msc.mytable.col6,'') || ' ' ||
coalesce(msc.mytable.col7,'')));
## --to_tsquery('Importkompetanse&Oslo&AS') -- nada
plainto_tsquery('Importkompetanse') -- nada
--to_tsquery('9999') -- OK!
Does anyone have an idea why my searches yields no results?
EDIT:
For some reason, to_tsquery returns something like this:
"'9999':9 'importkompetans':1,6"
The word importkompetanse seems to be cut off?
However, if I set it to simple instead of norwegian, I get the expected results and everything looks good. Why is that?
You used cross configuration between your tsvector and tsquery values. You should use consistent configuration, like:
select to_tsvector('norwegian', 'Importkompetanse Oslo AS')
## to_tsquery('norwegian', 'Importkompetanse&Oslo&AS');
SQLFiddle
This is why it worked with the 'simple' configuration (that is your default).
Note: you can always debug text search with ts_debug(): f.ex. 'Importkompetanse' has not been cut off, 'importkompetans' is just the appropriate lexeme for this word (in the 'norwegian' configuration).
Off: you use a really long, expression-based index, which will only be used, if you use the exact expression in your queries too. You used it right in your example, but this makes your queries really long, and if you change your index expression some time later, you need to make sure all "uses" updated as well.
You could use a simple (sql) function, to simplify your queries:
create or replace function col_tsvector(mytable)
returns tsvector
immutable
language sql
as $function$
return to_tsvector('norwegian',
coalesce($1.col1, '') || ' ' ||
coalesce($1.col2, '') || ' ' ||
coalesce($1.col3, '') || ' ' ||
coalesce($1.col4, '') || ' ' ||
coalesce($1.col5, '') || ' ' ||
coalesce($1.col6, '') || ' ' ||
coalesce($1.col7, ''))
$function$;
With this, you can greatly simplify your index definition & your queries too. (You can even use the attribute notation.)
I need a store procedure that loop in a table that returns a name of a table in the db2 and depending from that name i need to do a select statement from the named table. i have tried doing it with an 'execute immediate' in so many ways that a lost the count here is an example of the execute immediate:
set insertstring = 'INSERT INTO pribpm.TEMP_T_TOQUE_CICLO (idSemana,tiempo_ciclo,tiempo_toque)
SELECT to_number(to_char( '''|| ' time_stamp ' ||''' ,' || ' IW ' || ')) ,SUM(KPITOTALTIMECLOCK),SUM(s.KPIEXECUTIONTIMECLOCK) FROM ' || TABLA || ' where to_number(to_char( '''|| ' time_stamp ' ||''' ,' || ' IW ' || ')) between ' || (to_number(to_char(FECHA,'IW'))-3) || ' and ' || to_number(to_char(FECHA,'IW')) || ' GROUP BY to_number(to_char('''|| ' time_stamp ' ||''' ,' || ' IW ' || '))';
PREPARE stmt FROM insertstring;
EXECUTE IMMEDIATE insertstring;
where tabla is a string that contains the name of the table and fecha is a date in timestamp type
besides i've tried it with cursors like this
set select_ = 'SELECT time_stamp, KPITOTALTIMECLOCK, KPIEXECUTIONTIMECLOCK FROM ' || tabla;
PREPARE stmt FROM select_;
FOR v2 AS
c2 cursor for
execute select_
do
if to_number(to_char(time_stamp,'IW')) between
(to_number(to_char(fecha,'IW'))-3) and to_number(to_char(fecha,'IW')) then
--something here
end if;
END FOR;
but with no success.
may you or may someone please help me clear my error or giving some other idea about this im trying to do?
all this in db2 environment
Write a procedure and loop tables from SYSCAT.TABLES to get the table name and again loop to fire a select query for each and every table.
I am not 100% sure as it has been a long time I worked on db2