How do I parameterize table & column in a Postgres-custom-function, selecting PK if value exists, otherwise insert it and return PK anyways? - sql

Trying to do what I specified in the title, I already got the upsert-functionalities working, however when I try to parameterize it, I'm just out of my depth and can't debug it.
My query:
CREATE OR REPLACE FUNCTION custom_upsert(target_value_input text,
target_table_input text,
target_column_input text,
OUT pk_output int)
LANGUAGE plpgsql AS
$func$
BEGIN
LOOP
execute 'SELECT id '
' FROM ' || target_table_input ||
' WHERE ' || target_column_input || ' = ' || target_value_input ||
' INTO pk_output';
EXIT WHEN FOUND;
execute 'INSERT INTO ' || target_table_input || 'AS o ( ' || target_column_input || ' )'
' VALUES ( ' || target_value_input || ' ) '
' ON CONFLICT ( ' || target_column_input || ' ) DO NOTHING '
' RETURNING o.id'
' INTO pk_output';
EXIT WHEN FOUND;
END LOOP;
END
$func$;
now when I try to use the function, I get:
ERROR: syntax error at or near "INTO"
LINE 1: ...module WHERE artifact_id = artifact_id_example_1 INTO pk_ou...
^
QUERY: SELECT id FROM maven_module WHERE artifact_id = artifact_id_example_1 INTO pk_output
CONTEXT: PL/pgSQL function custom_upsert(text,text,text) line 4 at EXECUTE
What puzzles me about this is the fact that this syntax works fine in an unparameterized version:
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=765389a746d3a392bc646fbedb7ed3b3
My attempts at parameterization:
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=1bffab45d8a9587342a7c3253ea35fc8
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=de6ba235aa21dae33b922f8fddac3b63
Thank you very much in advance, first time posting so if there's anything I should do differently when asking a question, I'm happy about feedback
edit: this is my function call:
-- should return pk of existing artifact_id
SELECT custom_upsert('artifact_id_example_1', 'maven_module', 'artifact_id');
-- should return pk of new artifact_id
SELECT custom_upsert('artifact_id_example_2', 'maven_module', 'artifact_id');

Do not concatenate strings like that. The function format() makes your life much easier (safer), e.g.
EXECUTE format('INSERT INTO %1$I AS o (%2$I)
VALUES (%3$L) ON CONFLICT (%2$I) DO NOTHING RETURNING o.id',
target_table_input,
target_column_input,
target_value_input) INTO pk_output;
%I will wrap the identifiers with double quote, which is handy when tables or columns are case sensitive of contain special characters.
%L will wrap the literals with single quotes
1$, 2$ and 3$ are the variables positions provided in the format() call, which is quite handy if one variable is used more than once.
Demo: db<>fiddle

Related

PL/SQL Query to check row's length

I'm having some trouble on solving this.
I have to check on the table, if there's any row that exceed the length of 34 characters (for this, I'm using this first part of the query using Lenght command), if found, return the error with the variable 'END_CNPJ$' (that's being populated by the second part of the query 'ENDERECO') so the user can see which row has more than 34 characters. Is this code correct (probably not)? If it isn't, how can I fix it?
SELECT
LENGTH(CONCAT (CONCAT (CONCAT(CONCAT (CONCAT (CONCAT (CONCAT (
'', T.TTIPO_LOGR),
''), T.TENDERE),
''), T.NNRO_ENDER),
''),T.TCOMPL_ENDER) ),
T.TTIPO_LOGR || ' ' || T.TENDERE || ', ' || T.NNRO_ENDER || ' ' || T.TCOMPL_ENDER || ' - ' || TMUNICI || ' CNPJ: ' || T.NCGC AS ENDERECO
INTO CHARACTER_COUNT$, END_CNPJ$
FROM TBENDER T
WHERE T.CEMPRES = :ENDER_BLK.CEMPRES;
IF CHARACTER_COUNT$ > 34 THEN
MSG_ALERT_COSMO(' You exceeded 34 character for this address: ' || END_CNPJ$ );
RAISE FORM_TRIGGER_FAILURE;
END IF;
I hope I'm not violating any rule, just got here yesterday :D
TIA
That's actually Oracle Forms, is it not? raise form_trigger_failure and :ender_blk smell so.
In that case, the only drawback might be possibility of no rows in that table for block item value (which will raise no_data_found) or two or more rows for it (which will then raise too_many_rows).
Other than that, this should be OK.
Though, it is kind of unclear why you nicely concatenated values (using the double pipe || operator) for END_CNPJ$ and nested that many concat functions for CHARACTER_COUNT$.
Also, you didn't post the whole trigger code (missing declarations, begin-end keywords, perhaps something else).
But, as I said, in general - that's OK.

How can I fix these Trigger Errors? Oracle 11g Express

code:
create or replace trigger "ARTICULOSCAT_INSERT" AFTER insert on
"ARTICULOSCAT" for each row BEGIN INSERT INTO
BITACORA (USUARIO,FECHA,TABLA_AFECTADA,ACCION_EFECTUADA,COLUMNAS,VALORES_ANT,VALORES_NUEVOS)
VALUES(USER,SYSDATE,'ArticulosCat','Insert','I_ID_ARTICULO,V_DESCRIPCION_100,I_ID_UM,F_PRECIO,I_ID_IMPUESO',
CONCAT('I_ID_ARTICULO: ',:OLD.I_ID_ARTICULO,',','V_DESCRIPCION_100: ',:OLD.V_DESCRIPCION_100,',','I_ID_UM: ',:OLD.I_ID_UM,',','F_PRECIO: ',:OLD.F_PRECIO,',','I_ID_IMPUESTO: ',:OLD.I_ID_IMPUESTO),
CONCAT('I_ID_ARTICULO: ',:NEW.I_ID_ARTICULO,',','V_DESCRIPCION_100: ',:NEW.V_DESCRIPCION_100,',','I_ID_UM: ',:NEW.I_ID_UM,',','F_PRECIO: ',:NEW.F_PRECIO,',','I_ID_IMPUESTO: ',:NEW.I_ID_IMPUESTO)
END;​
Error:
Compilation failed, line 3 (02:17:30) The line numbers associated with
compilation errors are relative to the first BEGIN statement. This
only affects the compilation of database triggers. PL/SQL: ORA-00909:
invalid number of argumentsCompilation failed, line 2 (02:17:30) The
line numbers associated with compilation errors are relative to the
first BEGIN statement. This only affects the compilation of database
triggers. PL/SQL: SQL Statement ignoredCompilation failed, line 4
(02:17:30) The line numbers associated with compilation errors are
relative to the first BEGIN statement. This only affects the
compilation of database triggers. PLS-00103: Encountered the symbol
"end-of-file" when expecting one of the following: ( begin case
declare end exception exit for goto if loop mod null pragma raise
return select update while with
The issue lies in your use of the CONCAT function. As you can see from the documentation, this only accepts two parameters.
What you should use instead is the concatenation operator ||, as this can be used to string together many different parts and is, IMO, far easier to read than a series of nested CONCATs!
Therefore your trigger would look something like:
CREATE OR REPLACE TRIGGER articuloscat_insert
AFTER INSERT ON articuloscat
FOR EACH ROW
BEGIN
INSERT INTO bitacora
(usuario,
fecha,
tabla_afectada,
accion_efectuada,
columnas,
valores_ant,
valores_nuevos)
VALUES
(USER,
SYSDATE,
'ArticulosCat',
'Insert',
'I_ID_ARTICULO,V_DESCRIPCION_100,I_ID_UM,F_PRECIO,I_ID_IMPUESO',
'I_ID_ARTICULO: ' || :old.i_id_articulo || ',' || 'V_DESCRIPCION_100: ' || :old.v_descripcion_100 || ',' || 'I_ID_UM: ' || :old.i_id_um || ',' || 'F_PRECIO: ' || :old.f_precio || ',' || 'I_ID_IMPUESTO: ' || :old.i_id_impuesto,
'I_ID_ARTICULO: ' || :new.i_id_articulo || ',' || 'V_DESCRIPCION_100: ' || :new.v_descripcion_100 || ',' || 'I_ID_UM: ' || :new.i_id_um || ',' || 'F_PRECIO: ' || :new.f_precio || ',' || 'I_ID_IMPUESTO: ' || :new.i_id_impuesto);
END articuloscat_insert;
/
You were also missing a semi-colon (;) at the end of the insert, which I have added for you.

Selecting individual values from csv format in oracle pl sql

I have the following value in a column in Oracle db ('abc', 'xyz')
I want to extract the values separately like abc, xyz by removing ' and (). Is there a way to do it using INSTR and SUBSTR functions?
Thanks
Use this query:
with sample as (select '(''abc'', ''xyz'')' text from dual)
select substr(text,instr(text,'''',1,1) + 1,instr(text,'''',1,2) - instr(text,'''',1,1) - 1),
substr(text,instr(text,'''',1,3) + 1,instr(text,'''',1,4) - instr(text,'''',1,3) - 1)
from sample;
It would help to know what you want to do with the data once parsed. How it could be handled in SQL vs PL/SQL to achieve your requirement could be very different.
That said, here's one way to strip surrounding parens and remove single quotes at the same time during the select using the powerful regexp_replace(source_string, pattern_string, replace_string) :
WITH qry AS (SELECT '(' || '''abc''' || ',' || '''xyz''' || ')' orig_string
FROM dual
)
SELECT regexp_replace(orig_string, '[()'']', '' ) clean_string
FROM qry;
The regexp_replace pattern_string says to match a character class (defind by opening and closing square brackets) containing a left paren or a right paren or a single quote (quoted so Oracle sees it) and the replace_string replaces it with nothing.
Then, to parse the values remaining here's an example from by bag of tricks I got somewhere and tweaked for this case:
set serveroutput on
DECLARE
-- Build a string in the format "('abc','xyz')"
orig_string varchar2(20) := '(' || '''abc''' || ',' || '''xyz''' || ')';
CURSOR cur IS
WITH qry AS (SELECT regexp_replace(orig_string, '[()'']','' ) clean_string
FROM dual
)
SELECT regexp_substr(clean_string, '[^,]+', 1, ROWNUM) element
FROM qry
CONNECT BY LEVEL <= LENGTH(regexp_replace (clean_string, '[^,]+')) + 1;
BEGIN
FOR rec IN cur LOOP
dbms_output.put_line('Element:' || rec.element);
END LOOP;
END;
It basically loops through the elements and prints them. I'm sure you can adapt this to your situation.

how to do two sqlplus queries and display the result in single line

I do not know sqlplus. But trying to complete one task at work. The task is to logon to a schema and get following information in single line -
schema_name, database_name, database_link_name, user_name
I am able to get that information in TWO lines. I will be grateful if somebody suggests a simple way of getting results of two different select queries in a single line.
Following works but gives me required results in TWO lines. I want them in single line.
SQL> select * from
(select user || ' ' || sys_context('USERENV','DB_NAME') as Instance from dual),
(select DB_LINK || ' ' || username from user_db_links);
TSTSCRIPT2 ORADEV
MCCODEVTOMCCOSTG_TSTSCRIPT1 TSTSCRIPT1
Simply specify display format for a column. In your situation, as there several values are being concatenated it'll be aliases.
/* Here character value is 11 characters long "a11"
If you need it to be longer or shorter simply increase or decrease
the value of the constant, make it "a20", for instance
*/
SQL> column instance format a11;
SQL> column res2 format a11;
SQL> select user || ' ' || sys_context('USERENV','DB_NAME') as instance
, DB_LINK || ' ' || username as res2
from user_db_links t
Result:
INSTANCE RES2
----------- -----------
HR CDB NK1 HR
select (select user || ' ' || sys_context('USERENV','DB_NAME') as Instance from dual) as user_info,
(select DB_LINK || ' ' || username from user_db_links where rownum < 2) as db_link
from dual;

Concatenate string in Oracle SQL? (wm-concat)

I've got some SQL that I'd like to format correctly for a mailout (generated directly from SQL - don't ask!). The code is as follows:
SELECT wm_concat('<br>• ' || FIELD1 || ' ' || FIELD2 || ' : ' || FIELD 3 || ' text') AS "Team"
Okay, so this kinda works - but it places a comma at the end of each line. Silly question, and possibly quite trivial, but is there anyway at all to remove the comma please? I think it's being added by the wm_concat function
Thanks
Yes the WM_CONCAT function puts a comma between each value it concatenates.
If there are no commas in your data you could do this:
SELECT replace (wm_concat('<br>• ' || FIELD1 || ' ' || FIELD2 || ' : '
|| FIELD 3 || ' text'),
',', null) AS "Team"
If you are on 11G you can use the new LISTAGG function instead:
SELECT LISTAGG ('<br>• ' || FIELD1 || ' ' || FIELD2 || ' : '
|| FIELD 3 || ' text')
WITHIN GROUP (ORDER BY <something>) AS "Team"
That will produce a result without commas.
Just trim the string for trailing commas:
RTRIM( wm_concat(...), ',' )
Oracle 10g provides a very convenient function wm_concat used to solve line reclassified demand, very easy to use this function, but the function provides only ',' this kind of delimiter.
In fact, as long as some simple conversion you can use other delimiters separated, the first thought is replace function
with t as( select 'a' x from dual union select 'b' from dual )
select replace(wm_concat(x),',','-') from t;
But taking into account the string itself may contain ',' character, use the above SQL will lead to erroneous results, but also made some changes to the above SQL.
with t as( select 'a' x from dual union select 'b' y from dual)
select substr(replace(wm_concat('%'||x),',%','-'),2) from t;
In the above SQL by a '%' as a separator, and then replace the '%' to remove the error. The program assumes that the string does not exist within the '%' string to replace the '%' in the SQL can also use other special characters.
Source: http://www.databaseskill.com/3400944/
You can create your own aggregate functions in Oracle and use those to aggregate strings.
Or use the StrAgg function written by Tom Kyte: http://www.sqlsnippets.com/en/topic-11591.html
SELECT StrAgg('<br>• ' || FIELD1 || ' ' || FIELD2 || ' : ' || FIELD 3 || ' text') AS "Team"
FROM Abc